Strategy: Shards
Shards are separate upgradeable contracts that act as custody and execution endpoints for portfolio capital. They are registered with the strategy and can pull assets from it on demand, execute approved external calls (protocol interactions, bridges, DeFi integrations), and push assets back.
Shard vs Remote Shard
Shard | RemoteShard | |
|---|---|---|
| Strategy link | Yes, holds an IStrategy reference | No |
pullFromStrategy | Yes (SHARD_TRANSFER_ROLE) | No |
pushToStrategy | Yes (SHARD_TRANSFER_ROLE) | No |
Validator-gated exec | Yes | Yes |
Privileged exec bypass | Yes (PRIVILEGED_EXECUTOR_ROLE) | Yes (PRIVILEGED_EXECUTOR_ROLE) |
| ETH custody | Yes (receive) | Yes (receive) |
Use Shard when capital moves through the strategy funding pipe (requestFunding / pushToStrategy) and other interactions happen via validator-approved exec.
Use Remote Shard when the deployment has no need to call requestFunding. All interactions are purely external calls approved by the validator. A single Shard may coordinate multiple RemoteShard instances (e.g. one per chain).
Strategy funding pipe (Shard only)
// SHARD_TRANSFER_ROLE: pull tokens from the strategy to this shard
shard.pullFromStrategy(IERC20 token, uint256 amount);
// SHARD_TRANSFER_ROLE: push tokens from this shard back to the strategy
shard.pushToStrategy(IERC20 token, uint256 amount);pullFromStrategy calls strategy.requestFunding(token, amount) which causes the strategy to safeTransfer(token, shard, amount) directly. There is no ERC-20 approve/transferFrom pattern between strategy and shard.
pushToStrategy performs token.safeTransfer(address(strategy), amount). It accepts any ERC-20 (not only strategy.asset()), but the typical use case is returning asset() to fund pending redemption fulfilments.
Both emit PulledFromStrategy / PushedToStrategy events and bypass the validator.
Shard funding guards
When a registered shard calls requestFunding(token, amount):
- The caller must be a registered shard; reverts
Strategy_ShardNotRegisteredotherwise. tokenmust not be the strategy share token itself; revertsStrategy_ShareTransferNotAllowed.amountmust be non-zero; revertsStrategy_ZeroAmount.- The call is
nonReentrant.
External calls
Both Shard and RemoteShard expose two exec overloads for making arbitrary external calls.
Validator-gated
function exec(
address to,
uint256 value,
bytes calldata data,
bytes calldata validatorArgs
) external payable returns (bytes memory);- Calls
validator.isAllowed(msg.sender, to, value, data, validatorArgs). - Reverts
Shard_CallNotAllowedif the validator returnsfalse. - On approval, executes
payable(to).call{value: value}(data)and reverts if the call fails. - Emits
Executed(sender, to, value, data).
Privileged Executor
function exec(
address to,
uint256 value,
bytes calldata data
) external payable returns (bytes memory);- Requires
PRIVILEGED_EXECUTOR_ROLE. Validator is not consulted. - Otherwise identical to the validator-gated path.
- For emergency operations or patterns that cannot be expressed as Merkle rules.
Common behavior
payable:msg.valueis credited to the shard’s ETH balance before the forwarded call.- Requires
address(this).balance >= value; revertsShard_InvalidEthFundingotherwise. nonReentrant.to == address(this)is explicitly permitted (e.g. for granting roles to the shard itself viaexec). Policy should model this surface explicitly.
Replacing the validator
shard.setValidator(IShardExecValidator newValidator); // DEFAULT_ADMIN_ROLEReplaces the active validator for all future exec calls. Must not be address(0). Emits ValidatorUpdated(oldValidator, newValidator).
Shard registration
Before a shard can call requestFunding, it must be registered with the strategy:
strategy.registerShard(shardAddress); // DEFAULT_ADMIN_ROLE
strategy.deregisterShard(shardAddress); // MANAGER_ROLE
strategy.isRegisteredShard(shardAddress); // view
strategy.shards(); // view, returns all registered addressesRegistration does not establish any ERC-20 allowance; the strategy transfers directly on requestFunding.
Deregistering the current defaultShard automatically clears it to address(0).
Default shard
strategy.setDefaultShard(shardAddress); // MANAGER_ROLE (must be a registered shard or address(0))
strategy.defaultShard(); // viewWhen a defaultShard is configured, every successful deposit automatically forwards the net deposit tokens to that shard after minting shares. This is the primary mechanism for deploying freshly deposited capital without a separate operator step.
MerkleAllowlistValidator
MerkleAllowlistValidator is the standard IShardExecValidator implementation. It stores a single Merkle root on-chain and validates each exec call against a rule supplied with a Merkle proof. A zero root denies all calls.
For the complete rule encoding reference, full TypeScript examples, a step-by-step worked scenario, and security considerations see Shard Validators.