Skip to content

Commit

Permalink
feat(contracts-rfq): Synapse Intent Router (#3433)
Browse files Browse the repository at this point in the history
* feat: scaffold IntentRouter

* feat: initial implementation

* test: single step

* feat: scaffold checked/unchecked `completeIntent`

* feat: implement balance checks

* test: add cases where TokenZap has non-zero initial amount

* test: double step cases (erc20 and/or native combos)

* feat: SIR, TokenZap deployment script

* deploy: test deploys for SIR

* test: update to #3434 changes

* feat: scaffold SIP

* test: coverage for SIP

* feat: Synapse Intent Previewer

* feat: update deploy script, deploy SIP

* fix(contracts-rfq): add `forwardTo` to ZapData for Zap Actions that don't forward assets to the user (#3451)

* feat: scaffold finalToken, forwardTo in ZapDataV1

* feat: add finalToken, forwardTo

* feat: scaffold TokenZap.encodeZapData

* test: update existing tests

* test: forwardTo scenarios

* feat: token zeor address checks

* feat:scaffold token forwarding

* test: more revert cases

* feat: final token forwarding

* test: forwardTo behaviour in SIP

* feat: support optional `forwardTo` in SIP

* deploy: redeploy SIP, TokenZap
  • Loading branch information
ChiTimesChi authored Dec 10, 2024
1 parent cb43466 commit 9900167
Show file tree
Hide file tree
Showing 29 changed files with 4,526 additions and 158 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {ISynapseIntentRouter} from "./ISynapseIntentRouter.sol";

interface ISynapseIntentPreviewer {
/// @notice Preview the completion of a user intent.
/// @dev Will not revert if the intent cannot be completed, returns empty values instead.
/// @dev Returns (amountIn, []) if the intent is a no-op (tokenIn == tokenOut).
/// @param swapQuoter Peripheral contract to use for swap quoting
/// @param forwardTo The address to which the proceeds of the intent should be forwarded to.
/// Note: if no forwarding is required (or done within the intent), use address(0).
/// @param tokenIn Initial token for the intent
/// @param tokenOut Final token for the intent
/// @param amountIn Initial amount of tokens to use for the intent
/// @return amountOut Final amount of tokens to receive. Zero if the intent cannot be completed.
/// @return steps Steps to use in SynapseIntentRouter in order to complete the intent.
/// Empty if the intent cannot be completed, or if intent is a no-op (tokenIn == tokenOut).
function previewIntent(
address swapQuoter,
address forwardTo,
address tokenIn,
address tokenOut,
uint256 amountIn
)
external
view
returns (uint256 amountOut, ISynapseIntentRouter.StepParams[] memory steps);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

interface ISynapseIntentRouter {
/// @notice Parameters for a single Zap step.
/// @param token Address of the token to use for the step
/// @param amount Amount of tokens to use for the step (type(uint256).max to use the full ZapRecipient balance)
/// @param msgValue Amount of native token to supply for the step, out of the total `msg.value` used for the
/// `fulfillIntent` call (could differ from `amount` regardless of the token type)
/// @param zapData Instructions for the ZapRecipient contract on how to execute the Zap
struct StepParams {
address token;
uint256 amount;
uint256 msgValue;
bytes zapData;
}

/// @notice Kindly ask SIR to complete the provided intent by completing a series of Zap steps using the
/// provided ZapRecipient contract.
/// - Each step is verified to be a correct Zap as per `IZapRecipient` specification.
/// - The amounts used for each step can be predetermined or based on the proceeds from the previous steps.
/// - SIR does not perform any checks on the Zap Data; the user is responsible for ensuring correct encoding.
/// - The user is responsible for selecting the correct ZapRecipient for their intent: ZapRecipient must be
/// able to modify the Zap Data to adjust to possible changes in the passed amount value.
/// - SIR checks that the ZapRecipient balance for every token in `steps` has not increased after the last step.
/// @dev Typical workflow involves a series of preparation steps followed by the last step representing the user
/// intent such as bridging, depositing, or a simple transfer to the final recipient. The ZapRecipient must be
/// the funds recipient for the preparation steps, while the final recipient must be used for the last step.
/// @dev This function will revert in any of the following cases:
/// - The deadline has passed.
/// - The array of StepParams is empty.
/// - The amount of tokens to use for the last step is below the specified minimum.
/// - Any step fails.
/// - `msg.value` does not match `sum(steps[i].msgValue)`.
/// @param zapRecipient Address of the IZapRecipient contract to use for the Zap steps
/// @param amountIn Initial amount of tokens (steps[0].token) to transfer into ZapRecipient
/// @param minLastStepAmountIn Minimum amount of tokens (steps[N-1].token) to use for the last step
/// @param deadline Deadline for the intent to be completed
/// @param steps Parameters for each step. Use amount = type(uint256).max for steps that
/// should use the full ZapRecipient balance.
function completeIntentWithBalanceChecks(
address zapRecipient,
uint256 amountIn,
uint256 minLastStepAmountIn,
uint256 deadline,
StepParams[] memory steps
)
external
payable;

/// @notice Kindly ask SIR to complete the provided intent by completing a series of Zap steps using the
/// provided ZapRecipient contract.
/// @dev This function is identical to `completeIntentWithBalanceChecks` except that it does not verify that
/// the ZapRecipient balance for every token in `steps` has not increased after the last Zap.
/// Anyone using this function must validate that the funds are fully spent by ZapRecipient
/// using other means like separate on-chain checks or off-chain simulation.
function completeIntent(
address zapRecipient,
uint256 amountIn,
uint256 minLastStepAmountIn,
uint256 deadline,
StepParams[] memory steps
)
external
payable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

interface ISynapseIntentRouterErrors {
error SIR__AmountInsufficient();
error SIR__DeadlineExceeded();
error SIR__MsgValueIncorrect();
error SIR__StepsNotProvided();
error SIR__TokenNotContract();
error SIR__UnspentFunds();
error SIR__ZapIncorrectReturnValue();
error SIR__ZapNoReturnValue();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
pragma solidity ^0.8.17;

import {IDefaultPool} from "./IDefaultPool.sol";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
pragma solidity ^0.8.17;

interface IDefaultPool {
function swap(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
pragma solidity ^0.8.17;

interface IWETH9 {
function deposit() external payable;
Expand Down
40 changes: 35 additions & 5 deletions packages/contracts-rfq/contracts/libs/ZapDataV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ library ZapDataV1 {
// Offsets of the fields in the packed ZapData struct
// uint16 version [000 .. 002)
// uint16 amountPosition [002 .. 004)
// address target [004 .. 024)
// bytes payload [024 .. ***)
// address finalToken [004 .. 024)
// address forwardTo [024 .. 044)
// address target [044 .. 064)
// bytes payload [064 .. ***)

// forgefmt: disable-start
uint256 private constant OFFSET_AMOUNT_POSITION = 2;
uint256 private constant OFFSET_TARGET = 4;
uint256 private constant OFFSET_PAYLOAD = 24;
uint256 private constant OFFSET_FINAL_TOKEN = 4;
uint256 private constant OFFSET_FORWARD_TO = 24;
uint256 private constant OFFSET_TARGET = 44;
uint256 private constant OFFSET_PAYLOAD = 64;
// forgefmt: disable-end

error ZapDataV1__InvalidEncoding();
Expand All @@ -44,13 +48,23 @@ library ZapDataV1 {
/// This will usually be `4 + 32 * n`, where `n` is the position of the token amount in
/// the list of parameters of the target function (starting from 0).
/// Or `AMOUNT_NOT_PRESENT` if the token amount is not encoded within `payload_`.
/// @param finalToken_ The token produced as a result of the Zap action (ERC20 or native gas token).
/// A zero address value signals that the Zap action doesn't result in any asset per se,
/// like bridging or depositing into a vault without an LP token.
/// Note: this parameter must be set to a non-zero value if the `forwardTo_` parameter is
/// set to a non-zero value.
/// @param forwardTo_ The address to which `finalToken` should be forwarded. This parameter is required only
/// if the Zap action does not automatically transfer the token to the intended recipient.
/// Otherwise, it must be set to address(0).
/// @param target_ Address of the target contract.
/// @param payload_ ABI-encoded calldata to be used for the `target_` contract call.
/// If the target function has the token amount as an argument, any placeholder amount value
/// can be used for the original ABI encoding of `payload_`. The placeholder amount will
/// be replaced with the actual amount, when the Zap Data is decoded.
function encodeV1(
uint16 amountPosition_,
address finalToken_,
address forwardTo_,
address target_,
bytes memory payload_
)
Expand All @@ -63,7 +77,7 @@ library ZapDataV1 {
if (amountPosition_ != AMOUNT_NOT_PRESENT && (uint256(amountPosition_) + 32 > payload_.length)) {
revert ZapDataV1__InvalidEncoding();
}
return abi.encodePacked(VERSION, amountPosition_, target_, payload_);
return abi.encodePacked(VERSION, amountPosition_, finalToken_, forwardTo_, target_, payload_);
}

/// @notice Extracts the version from the encoded Zap Data.
Expand All @@ -74,6 +88,22 @@ library ZapDataV1 {
}
}

/// @notice Extracts the finalToken address from the encoded Zap Data.
function finalToken(bytes calldata encodedZapData) internal pure returns (address finalToken_) {
// Load 32 bytes from the offset and shift it 96 bits to the right to get the highest 160 bits.
assembly {
finalToken_ := shr(96, calldataload(add(encodedZapData.offset, OFFSET_FINAL_TOKEN)))
}
}

/// @notice Extracts the forwardTo address from the encoded Zap Data.
function forwardTo(bytes calldata encodedZapData) internal pure returns (address forwardTo_) {
// Load 32 bytes from the offset and shift it 96 bits to the right to get the highest 160 bits.
assembly {
forwardTo_ := shr(96, calldataload(add(encodedZapData.offset, OFFSET_FORWARD_TO)))
}
}

/// @notice Extracts the target address from the encoded Zap Data.
function target(bytes calldata encodedZapData) internal pure returns (address target_) {
// Load 32 bytes from the offset and shift it 96 bits to the right to get the highest 160 bits.
Expand Down
Loading

0 comments on commit 9900167

Please sign in to comment.