Skip to Content

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

ShardRemoteShard
Strategy linkYes, holds an IStrategy referenceNo
pullFromStrategyYes (SHARD_TRANSFER_ROLE)No
pushToStrategyYes (SHARD_TRANSFER_ROLE)No
Validator-gated execYesYes
Privileged exec bypassYes (PRIVILEGED_EXECUTOR_ROLE)Yes (PRIVILEGED_EXECUTOR_ROLE)
ETH custodyYes (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_ShardNotRegistered otherwise.
  • token must not be the strategy share token itself; reverts Strategy_ShareTransferNotAllowed.
  • amount must be non-zero; reverts Strategy_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_CallNotAllowed if the validator returns false.
  • 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.value is credited to the shard’s ETH balance before the forwarded call.
  • Requires address(this).balance >= value; reverts Shard_InvalidEthFunding otherwise.
  • nonReentrant.
  • to == address(this) is explicitly permitted (e.g. for granting roles to the shard itself via exec). Policy should model this surface explicitly.

Replacing the validator

shard.setValidator(IShardExecValidator newValidator); // DEFAULT_ADMIN_ROLE

Replaces 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 addresses

Registration 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(); // view

When 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.

Last updated on