Skip to content

Commit

Permalink
feat: Synapse Intent Previewer
Browse files Browse the repository at this point in the history
  • Loading branch information
ChiTimesChi committed Dec 6, 2024
1 parent 4106cd5 commit f3404cc
Show file tree
Hide file tree
Showing 2 changed files with 299 additions and 3 deletions.
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
300 changes: 298 additions & 2 deletions packages/contracts-rfq/contracts/router/SynapseIntentPreviewer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,30 @@ pragma solidity 0.8.24;

// ════════════════════════════════════════════════ INTERFACES ═════════════════════════════════════════════════════

import {ISynapseIntentPreviewer} from "../interfaces/ISynapseIntentPreviewer.sol";
import {ISynapseIntentRouter} from "../interfaces/ISynapseIntentRouter.sol";
import {ISwapQuoter} from "../legacy/rfq/interfaces/ISwapQuoter.sol";
import {IDefaultExtendedPool, IDefaultPool} from "../legacy/router/interfaces/IDefaultExtendedPool.sol";
import {IWETH9} from "../legacy/router/interfaces/IWETH9.sol";

contract SynapseIntentPreviewer {
// ═════════════════════════════════════════════ INTERNAL IMPORTS ══════════════════════════════════════════════════

import {Action, DefaultParams, LimitedToken, SwapQuery} from "../legacy/router/libs/Structs.sol";
import {ZapDataV1} from "../libs/ZapDataV1.sol";

contract SynapseIntentPreviewer is ISynapseIntentPreviewer {
/// @notice The address reserved for the native gas token (ETH on Ethereum and most L2s, AVAX on Avalanche, etc.).
address public constant NATIVE_GAS_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

/// @dev Amount value that signals that the Zap step should be performed using the full ZapRecipient balance.
uint256 internal constant FULL_BALANCE = type(uint256).max;

error SIP__PoolTokenMismatch();
error SIP__PoolZeroAddress();
error SIP__RawParamsEmpty();
error SIP__TokenNotNative();

/// @inheritdoc ISynapseIntentPreviewer
function previewIntent(
address swapQuoter,
address tokenIn,
Expand All @@ -16,6 +37,281 @@ contract SynapseIntentPreviewer {
view
returns (uint256 amountOut, ISynapseIntentRouter.StepParams[] memory steps)
{
// TODO: implement
// First, check if the intent is a no-op.
if (tokenIn == tokenOut) {
return (amountIn, new ISynapseIntentRouter.StepParams[](0));
}

// Obtain the swap quote, don't put any restrictions on the actions allowed to complete the intent.
SwapQuery memory query = ISwapQuoter(swapQuoter).getAmountOut(
LimitedToken({token: tokenIn, actionMask: type(uint256).max}), tokenOut, amountIn
);

// Check if a quote was returned.
amountOut = query.minAmountOut;
if (amountOut == 0) {
return (0, new ISynapseIntentRouter.StepParams[](0));
}

// At this point we have a quote for a non-trivial action, therefore `query.rawParams` is not empty.
if (query.rawParams.length == 0) revert SIP__RawParamsEmpty();
DefaultParams memory params = abi.decode(query.rawParams, (DefaultParams));

// Create the steps for the intent based on the action type.
if (params.action == Action.Swap) {
steps = _createSwapSteps(tokenIn, tokenOut, amountIn, params);
} else if (params.action == Action.AddLiquidity) {
steps = _createAddLiquiditySteps(tokenIn, tokenOut, params);
} else if (params.action == Action.RemoveLiquidity) {
steps = _createRemoveLiquiditySteps(tokenIn, tokenOut, params);
} else {
steps = _createHandleHativeSteps(tokenIn, tokenOut, amountIn);
}
}

/// @notice Helper function to create steps for a swap.
function _createSwapSteps(
address tokenIn,
address tokenOut,
uint256 amountIn,
DefaultParams memory params
)
internal
view
returns (ISynapseIntentRouter.StepParams[] memory steps)
{
address pool = params.pool;
if (pool == address(0)) revert SIP__PoolZeroAddress();
// Default Pools can only host wrapped native tokens.
// Check if we start from the native gas token.
if (tokenIn == NATIVE_GAS_TOKEN) {
// Get the address of the wrapped native token.
address wrappedNative = IDefaultPool(pool).getToken(params.tokenIndexFrom);
// Sanity check tokenOut vs tokenIndexTo.
if (IDefaultPool(pool).getToken(params.tokenIndexTo) != tokenOut) revert SIP__PoolTokenMismatch();
// Native => WrappedNative + WrappedNative => TokenOut.
return _toStepsArray(
_createWrapNativeStep({wrappedNative: wrappedNative, amountIn: amountIn}),
_createSwapStep({tokenIn: wrappedNative, params: params})
);
}

// Sanity check tokenIn vs tokenIndexFrom.
if (IDefaultPool(pool).getToken(params.tokenIndexFrom) != tokenIn) revert SIP__PoolTokenMismatch();

// Check if we end with the native gas token.
if (tokenOut == NATIVE_GAS_TOKEN) {
// Get the address of the wrapped native token.
address wrappedNative = IDefaultPool(pool).getToken(params.tokenIndexTo);
// TokenIn => WrappedNative + WrappedNative => Native.
return _toStepsArray(
_createSwapStep({tokenIn: tokenIn, params: params}),
_createUnwrapNativeStep({wrappedNative: wrappedNative})
);
}

// Sanity check tokenOut vs tokenIndexTo.
if (IDefaultPool(pool).getToken(params.tokenIndexTo) != tokenOut) revert SIP__PoolTokenMismatch();

// TokenIn => TokenOut.
return _toStepsArray(_createSwapStep({tokenIn: tokenIn, params: params}));
}

/// @notice Helper function to create steps for adding liquidity.
function _createAddLiquiditySteps(
address tokenIn,
address tokenOut,
DefaultParams memory params
)
internal
view
returns (ISynapseIntentRouter.StepParams[] memory steps)
{
address pool = params.pool;
if (pool == address(0)) revert SIP__PoolZeroAddress();
// Sanity check tokenIn vs tokenIndexFrom.
if (IDefaultPool(pool).getToken(params.tokenIndexFrom) != tokenIn) revert SIP__PoolTokenMismatch();
// Sanity check tokenOut vs pool's LP token.
_verifyLpToken(pool, tokenOut);
// Figure out how many tokens does the pool support.
uint256[] memory amounts;
for (uint8 i = 0;; i++) {
// solhint-disable-next-line no-empty-blocks
try IDefaultExtendedPool(pool).getToken(i) returns (address) {
// Token exists, continue.
} catch {
// No more tokens, allocate the array using the correct size.
amounts = new uint256[](i);
break;
}
}
return _toStepsArray(
ISynapseIntentRouter.StepParams({
token: tokenIn,
amount: FULL_BALANCE,
msgValue: 0,
zapData: ZapDataV1.encodeV1({
target_: pool,
// addLiquidity(amounts, minToMint, deadline)
payload_: abi.encodeCall(IDefaultExtendedPool.addLiquidity, (amounts, 0, type(uint256).max)),
// amountIn is encoded within `amounts` at `TOKEN_IN_INDEX`, `amounts` is encoded after
// (amounts.offset, minToMint, deadline, amounts.length).
amountPosition_: 4 + 32 * 4 + 32 * uint16(params.tokenIndexFrom)
})
})
);
}

Check warning

Code scanning / Slither

Unused return Medium


/// @notice Helper function to create steps for removing liquidity.
function _createRemoveLiquiditySteps(
address tokenIn,
address tokenOut,
DefaultParams memory params
)
internal
view
returns (ISynapseIntentRouter.StepParams[] memory steps)
{
address pool = params.pool;
if (pool == address(0)) revert SIP__PoolZeroAddress();
// Sanity check tokenIn vs pool's LP token.
_verifyLpToken(pool, tokenIn);
// Sanity check tokenOut vs tokenIndexTo.
if (IDefaultPool(pool).getToken(params.tokenIndexTo) != tokenOut) revert SIP__PoolTokenMismatch();
return _toStepsArray(
ISynapseIntentRouter.StepParams({
token: tokenIn,
amount: FULL_BALANCE,
msgValue: 0,
zapData: ZapDataV1.encodeV1({
target_: pool,
// removeLiquidityOneToken(tokenAmount, tokenIndex, minAmount, deadline)
payload_: abi.encodeCall(
IDefaultExtendedPool.removeLiquidityOneToken, (0, params.tokenIndexTo, 0, type(uint256).max)
),
// amountIn is encoded as the first parameter: tokenAmount
amountPosition_: 4
})
})
);
}

function _verifyLpToken(address pool, address token) internal view {
(,,,,,, address lpToken) = IDefaultExtendedPool(pool).swapStorage();
if (lpToken != token) revert SIP__PoolTokenMismatch();
}

/// @notice Helper function to create steps for wrapping or unwrapping native gas tokens.
function _createHandleHativeSteps(
address tokenIn,
address tokenOut,
uint256 amountIn
)
internal
pure
returns (ISynapseIntentRouter.StepParams[] memory steps)
{
if (tokenIn == NATIVE_GAS_TOKEN) {
// tokenOut is Wrapped Native
return _toStepsArray(_createWrapNativeStep({wrappedNative: tokenOut, amountIn: amountIn}));
}
// Sanity check tokenOut
if (tokenOut != NATIVE_GAS_TOKEN) revert SIP__TokenNotNative();
// tokenIn is Wrapped Native
return _toStepsArray(_createUnwrapNativeStep({wrappedNative: tokenIn}));
}

/// @notice Helper function to create a single step for a swap.
function _createSwapStep(
address tokenIn,
DefaultParams memory params
)
internal
pure
returns (ISynapseIntentRouter.StepParams memory)
{
return ISynapseIntentRouter.StepParams({
token: tokenIn,
amount: FULL_BALANCE,
msgValue: 0,
zapData: ZapDataV1.encodeV1({
target_: params.pool,
// swap(tokenIndexFrom, tokenIndexTo, dx, minDy, deadline)
payload_: abi.encodeCall(
IDefaultPool.swap, (params.tokenIndexFrom, params.tokenIndexTo, 0, 0, type(uint256).max)
),
// amountIn is encoded as the third parameter: `dx`
amountPosition_: 4 + 32 * 2
})
});
}

/// @notice Helper function to create a single step for wrapping native gas tokens.
function _createWrapNativeStep(
address wrappedNative,
uint256 amountIn
)
internal
pure
returns (ISynapseIntentRouter.StepParams memory)
{
return ISynapseIntentRouter.StepParams({
token: NATIVE_GAS_TOKEN,
amount: FULL_BALANCE,
msgValue: amountIn,
zapData: ZapDataV1.encodeV1({
target_: wrappedNative,
// deposit()
payload_: abi.encodeCall(IWETH9.deposit, ()),
// amountIn is not encoded
amountPosition_: ZapDataV1.AMOUNT_NOT_PRESENT
})
});
}

/// @notice Helper function to create a single step for unwrapping native gas tokens.
function _createUnwrapNativeStep(address wrappedNative)
internal
pure
returns (ISynapseIntentRouter.StepParams memory)
{
return ISynapseIntentRouter.StepParams({
token: wrappedNative,
amount: FULL_BALANCE,
msgValue: 0,
zapData: ZapDataV1.encodeV1({
target_: wrappedNative,
// withdraw(amount)
payload_: abi.encodeCall(IWETH9.withdraw, (0)),
// amountIn encoded as the first parameter
amountPosition_: 4
})
});
}

/// @notice Helper function to construct an array of steps having a single step.
function _toStepsArray(ISynapseIntentRouter.StepParams memory step0)
internal
pure
returns (ISynapseIntentRouter.StepParams[] memory)
{
ISynapseIntentRouter.StepParams[] memory steps = new ISynapseIntentRouter.StepParams[](1);
steps[0] = step0;
return steps;
}

/// @notice Helper function to construct an array of steps having two steps.
function _toStepsArray(
ISynapseIntentRouter.StepParams memory step0,
ISynapseIntentRouter.StepParams memory step1
)
internal
pure
returns (ISynapseIntentRouter.StepParams[] memory)
{
ISynapseIntentRouter.StepParams[] memory steps = new ISynapseIntentRouter.StepParams[](2);
steps[0] = step0;
steps[1] = step1;
return steps;
}
}

0 comments on commit f3404cc

Please sign in to comment.