Deploying Capital
description: Operational runbook for the OPERATOR_ROLE holder deploying capital and funding redemptions.
Deploying capital
This page covers the highest-privilege operation in the protocol. Manager.execute makes arbitrary calls from the address custodying 100% of deposited assets. Within the whitelist there is no amount cap, no per-destination limit, and no rate limit. Read the whole page before your first call.
OPERATOR_ROLE holds three functions on the Manager:
execute(targets[], data[])
Deploy capital to whitelisted destinations
transferToVault(token, amount)
Return capital so redemptions can be paid
redeemFor(vault, user)
Settle a user's matured redemption on their behalf
Before you start
MANAGER=<manager proxy>
VAULT=<vault proxy>
ASSET=<underlying token>
RPC=<rpc url>execute — step by step
Confirm every target is whitelisted
Check this before building anything else. The whitelist is the only constraint on where capital can go, and an unlisted target reverts the entire batch with NotWhitelisted.
cast call $MANAGER "whitelistedAddresses(address)(bool)" <target> --rpc-url $RPCTo see the full authorised set rather than checking one address at a time, read the event history:
cast logs --address $MANAGER "WhitelistUpdated(address,address,bool)" \
--from-block <deployment block> --rpc-url $RPCBuild the calldata
Each entry in data[] is a complete ABI-encoded call to the matching entry in targets[].
# Approve a protocol to pull USDC from the Manager
cast calldata "approve(address,uint256)" <spender> <amount>
# Transfer USDC to a whitelisted destination
cast calldata "transfer(address,uint256)" <recipient> <amount>
# Any protocol function
cast calldata "deposit(uint256,address)" <amount> $MANAGERAmounts are in the token's own decimals — 6 for USDC, not 18. execute performs no validation of any kind on the calldata. A misplaced decimal moves 10¹²× the intended amount, and the only thing standing between that and a loss is whether the destination is willing to send it back.
Simulate the batch
cast call $MANAGER "execute(address[],bytes[])(bytes[])" \
"[<target1>,<target2>]" "[<data1>,<data2>]" \
--from <operator> --rpc-url $RPCThis returns each call's return data without sending anything. A successful simulation confirms the guards pass and no target reverts — it does not confirm the amounts are correct.
Simulate every batch. There is no dry-run mode, no confirmation step, and no way to recall a transfer once it lands.
Verify
# Manager's remaining balance
cast call $ASSET "balanceOf(address)(uint256)" $MANAGER --rpc-url $RPC
# The batch, with full targets and calldata
cast logs --address $MANAGER "BatchExecuted(address,address[],bytes[])" \
--from-block latest --rpc-url $RPCBatchExecuted records the complete calldata of every call, so each deployment is publicly auditable after the fact.
Behaviour to know before you rely on it
The batch is atomic. One failing call reverts the entire transaction — no partial application. Ordering within a batch is therefore safe for dependent operations (approve then deposit in one batch is fine).
Reverts bubble up raw. A failing target's revert data is re-thrown verbatim, so what you see is the target's error, not a Manager error. Decode it against the target's ABI, not the Manager's:
execute cannot send native ETH. It is not payable and passes no value, so every call carries msg.value == 0. Native ETH held by the Manager cannot be moved through this function at all.
There is no reentrancy guard. execute makes raw calls with no lock. Batching calls to a target that can call back into the Manager has no protection.
The whitelist cannot include the vault or Yield contract. setWhitelistedAddresses rejects both. This is load-bearing: without it, execute could reach mintVaultToken and updateTotalAssets and forge share supply and vault accounting directly.
transferToVault — funding redemptions
The return leg. This is the function that makes withdrawals actually payable.
This step is the protocol's withdrawal liveness. The vault holds no liquidity buffer, and _withdraw transfers only from the vault's own balance — it cannot pull from the Manager. A user whose cooldown has elapsed cannot exit until this call is made.
The protocol's invariant documentation records that "users can always eventually withdraw" does not hold precisely because this step is manual and offchain-triggered.
Two rough edges worth knowing:
It emits no event. Redemption funding is visible only as a plain ERC-20
Transferinto the vault. Do not build monitoring against atransferToVaultevent — there isn't one. (AssetsWithdrawnis declared inIManagerbut never emitted anywhere.)It has no zero-amount check and no NatSpec — the only function in
Managermissing both.
Anyone can also fund the vault with a plain ERC-20 transfer; the operator path is simply the intended one.
redeemFor — settling on a user's behalf
Bounded and safe: it pays only that user, only the amount already locked into their request. It cannot redirect funds or change amounts.
Requires the vault to be funded, exactly like a user-initiated redemption.
Every guard on execute
Caller holds OPERATOR_ROLE
AccessControlUnauthorizedAccount
targets.length == data.length
LengthMismatch
Every target whitelisted
NotWhitelisted
Every call succeeds
the target's own revert data, bubbled raw
That is the complete list. Note what is absent: no amount limit, no destination-specific cap, no rate limit, no daily ceiling, no pause check.
The trust boundary
execute is where capital stops being governed by code and starts being governed by an offchain agreement.
Enforced onchain: only whitelisted addresses can receive capital; only ADMIN can change that list; every deployment emits BatchExecuted with full calldata.
Not enforced onchain: how much leaves, when it returns, or whether it returns at all.
Operational discipline is the actual control here, not the contract. Practical guidance:
Simulate every batch, including ones identical to yesterday's.
Prefer exact approvals over infinite ones.
executecan grant unlimited allowances to whitelisted targets; a compromised or upgradeable target with a standing infinite approval can drain the Manager without any further operator action.Check the redemption backlog before deploying, not after. Deploying capital while matured redemptions sit unfunded strands users who have already given up their shares.
Reconcile deployed capital against reported P&L.
BatchExecutedtells you what left;DistributeYieldtells you what the strategy claims it earned. Nothing onchain connects the two — see Reporting P&L.
Last updated