SwappingYieldForwarder
Inherits: YieldForwarder
Title: SwappingYieldForwarder
Author: Golem Foundation
Extends YieldForwarder with an additional swap step before forwarding to the receiver.
Inherits reportAndForward() from YieldForwarder (no-swap fallback) and adds reportSwapAndForward() which swaps via a pluggable ISwapper before forwarding. This dual-mode design acts as a built-in circuit breaker: if the swap protocol is unavailable, the keeper simply calls the inherited non-swapping path instead. CALL CHAIN (with swap): Keeper EOA -> reportSwapAndForward() -> strategy.report() -> profit shares minted to this contract -> redeem shares to self -> swap via ISwapper -> target asset forwarded to receiver CALL CHAIN (without swap / fallback): Keeper EOA -> reportAndForward() [inherited] -> strategy.report() -> profit shares minted to this contract -> redeem shares directly to receiver DESIGN:
- Keeper-gated: only the designated keeper can trigger yield forwarding
- Single-purpose: assets can only flow to the hardcoded receiver
- Pluggable swap: ISwapper adapter handles DEX-specific logic; the adapter itself is settable by the vault's management() role so deeper pools or newer adapter versions can be adopted without redeploying the forwarder
- Dual-mode: keeper picks swap vs no-swap path at call-time
- Strategy is passed as a call-time parameter to avoid circular dependencies
Note: security-contact: [email protected]
Quick Start - What Matters Most
Use this when donation shares should be redeemed from the strategy, swapped into a target asset, and forwarded to a receiver.
Key functions to understand:
reportSwapAndForward(strategy, maxLoss, minAmountOut, deadline)- Reports, redeems, swaps, and forwardssetSwapper- Vault management changes the adaptersetMinSlippageBps- Vault management sets the global slippage floorforwardToken- Keeper or vault management can move residual tokens onward
State Variables
targetAsset
The desired output token after swapping
Set once at construction, cannot be changed
address public immutable targetAsset
vault
The governance source for this forwarder
Calls to vault.management() at swapper-management time authorize the caller. In a 1:1 forwarder-to-vault deployment this is the tokenized strategy whose profit shares flow through this forwarder; vault management is the same multisig that already controls keeper/dragon-router rotation.
address public immutable vault
swapper
The swap adapter used to convert underlying -> target asset
Settable by vault.management() to pivot to a different adapter (e.g. a new pool tier, a new DEX version) without redeploying.
ISwapper public swapper
MAX_BPS
Denominator for basis-point calculations (10_000 = 100%)
uint16 public constant MAX_BPS = 10_000
minSlippageBps
Floor on minAmountOut, denominated as basis points of assetsIn.
Set at construction so the deployment config explicitly chooses whether the floor is active from the first report. A value of 0 is an intentional opt-out for routes where management relies on the keeper's oracle-informed minAmountOut instead of a near-parity floor.
uint16 public minSlippageBps
Functions
onlyVaultManagement
Restricts a call to the vault's management() address. Reading this at call time (instead of caching a local admin) means rotation via the vault's own two-step transfer (setPendingManagement / acceptManagement) is picked up automatically.
modifier onlyVaultManagement() ;
constructor
Creates a new SwappingYieldForwarder
constructor(
address _receiver,
address _keeper,
address _targetAsset,
address _swapper,
address _vault,
uint16 _minSlippageBps
) YieldForwarder(_receiver, _keeper);
Parameters
| Name | Type | Description |
|---|---|---|
_receiver | address | Address that will receive all forwarded assets |
_keeper | address | Address authorized to call reportAndForward / reportSwapAndForward |
_targetAsset | address | Address of the desired output token after swapping |
_swapper | address | Initial ISwapper implementation for DEX routing |
_vault | address | Vault contract whose management() controls swapper rotation |
_minSlippageBps | uint16 | Initial floor in basis points of assetsIn (0 disables) |
setSwapper
Replace the active swap adapter
Authorized by vault.management(). The new adapter must expose the ISwapper pull-pattern semantic; the forwarder enforces its own minAmountOut re-check inside reportSwapAndForward as a defence-in-depth guard against a buggy adapter.
function setSwapper(address _newSwapper) external onlyVaultManagement;
Parameters
| Name | Type | Description |
|---|---|---|
_newSwapper | address | New ISwapper implementation |
setMinSlippageBps
Update the slippage floor applied to the keeper's minAmountOut
Authorized by vault.management(). Passing 0 disables the floor.
function setMinSlippageBps(uint16 _bps) external onlyVaultManagement;
Parameters
| Name | Type | Description |
|---|---|---|
_bps | uint16 | New floor in basis points of assetsIn (max 10_000) |
reportSwapAndForward
Calls report() on the strategy, redeems profit shares, swaps the underlying asset to the target asset via the configured ISwapper, and forwards to the receiver
The swap path: redeem to self -> approve swapper -> swapper.swap() -> receiver.
The swapper enforces minAmountOut internally; the forwarder additionally
re-checks the reported output against minAmountOut as defence-in-depth
in case a future/buggy adapter forgets its own check.
If report() produces no profit shares, the function returns 0 without reverting.
If redemption returns 0 assets, the function returns 0 without attempting a swap.
The caller MUST supply a fresh deadline (seconds since epoch).
The underlying Uniswap V3 adapter uses block.timestamp as the router deadline,
which always passes at inclusion and therefore adds no real time bound between
the keeper's quote and settlement. Enforcing an explicit expiry at the forwarder
prevents stale-slippage execution when a queued transaction lands in a later
block against a moved market. There is no 0 = disabled sentinel: keepers
always pass block.timestamp + buffer at transaction assembly time.
function reportSwapAndForward(address strategy, uint256 maxLoss, uint256 minAmountOut, uint256 deadline)
external
nonReentrant
returns (uint256 assetsOut);
Parameters
| Name | Type | Description |
|---|---|---|
strategy | address | Address of the strategy contract (must implement IReportable, IRedeemable, IERC20, IERC4626Asset) |
maxLoss | uint256 | Maximum acceptable loss in basis points for the redemption |
minAmountOut | uint256 | Minimum acceptable amount of target asset after swap (slippage protection) |
deadline | uint256 | Unix timestamp (seconds) past which the call reverts with ExpiredDeadline |
Returns
| Name | Type | Description |
|---|---|---|
assetsOut | uint256 | Amount of target asset forwarded to receiver (0 if no profit shares) |
_authorizeForwardToken
Authorizes forwardToken callers. Derived forwarders can extend this when they have an additional governance source.
function _authorizeForwardToken() internal view override;
Events
YieldSwappedAndForwarded
Emitted when shares are redeemed, swapped to target asset, and forwarded
event YieldSwappedAndForwarded(
address indexed strategy, address indexed receiver, uint256 shares, uint256 assetsIn, uint256 assetsOut
);
Parameters
| Name | Type | Description |
|---|---|---|
strategy | address | Address of the strategy whose shares were redeemed |
receiver | address | Address that received the target assets |
shares | uint256 | Amount of shares redeemed |
assetsIn | uint256 | Amount of underlying assets redeemed (swap input) |
assetsOut | uint256 | Amount of target assets forwarded (swap output) |
SwapperUpdated
Emitted when the swap adapter is replaced by vault management
event SwapperUpdated(address indexed oldSwapper, address indexed newSwapper);
Parameters
| Name | Type | Description |
|---|---|---|
oldSwapper | address | The previous ISwapper implementation |
newSwapper | address | The newly installed ISwapper implementation |
MinSlippageBpsUpdated
Emitted when the admin-set slippage floor is updated
event MinSlippageBpsUpdated(uint16 oldBps, uint16 newBps);
Parameters
| Name | Type | Description |
|---|---|---|
oldBps | uint16 | Previous floor (basis points of assetsIn) |
newBps | uint16 | New floor |
Errors
InvalidSwapper
Thrown when the swapper address is zero
error InvalidSwapper();
InvalidTargetAsset
Thrown when the target asset address is zero
error InvalidTargetAsset();
InvalidVault
Thrown when the vault address is zero
error InvalidVault();
OnlyVaultManagement
Thrown when a swapper-management function is called by a non-management address
error OnlyVaultManagement();
InsufficientSwapOutput
Thrown when a swapper reports an output below the enforced minimum
error InsufficientSwapOutput(uint256 expected, uint256 actual);
Parameters
| Name | Type | Description |
|---|---|---|
expected | uint256 | Minimum amount expected |
actual | uint256 | Amount actually reported |
InvalidSlippageBps
Thrown when setMinSlippageBps is called with a value above MAX_BPS
error InvalidSlippageBps();
SlippageFloorTooLoose
Thrown when the caller's minAmountOut falls below the admin-set floor
error SlippageFloorTooLoose(uint256 floor, uint256 supplied);
Parameters
| Name | Type | Description |
|---|---|---|
floor | uint256 | Required minimum (assetsIn * minSlippageBps / MAX_BPS) |
supplied | uint256 | The minAmountOut the keeper passed in |
ExpiredDeadline
Thrown when reportSwapAndForward is called past the caller-supplied deadline
error ExpiredDeadline(uint256 deadline, uint256 blockTimestamp);
Parameters
| Name | Type | Description |
|---|---|---|
deadline | uint256 | The keeper-chosen expiry timestamp (seconds since epoch) |
blockTimestamp | uint256 | The block time at which the call landed |