Skip to content

Commit

Permalink
feat(hardhat): add stargate jump
Browse files Browse the repository at this point in the history
  • Loading branch information
xykota committed Sep 26, 2023
1 parent 2032067 commit b6e8a26
Show file tree
Hide file tree
Showing 12 changed files with 480 additions and 39 deletions.
189 changes: 161 additions & 28 deletions packages/hardhat/contracts/facets/WarpLink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {LibCurve} from '../libraries/LibCurve.sol';
import {IPermit2} from '../interfaces/external/IPermit2.sol';
import {IAllowanceTransfer} from '../interfaces/external/IAllowanceTransfer.sol';
import {PermitParams} from '../libraries/PermitParams.sol';
import {IStargateRouter} from '../interfaces/external/IStargateRouter.sol';

abstract contract WarpLinkCommandTypes {
uint256 internal constant COMMAND_TYPE_WRAP = 1;
Expand All @@ -26,6 +27,7 @@ abstract contract WarpLinkCommandTypes {
uint256 internal constant COMMAND_TYPE_WARP_UNI_V3_LIKE_EXACT_INPUT_SINGLE = 6;
uint256 internal constant COMMAND_TYPE_WARP_UNI_V3_LIKE_EXACT_INPUT = 7;
uint256 internal constant COMMAND_TYPE_WARP_CURVE_EXACT_INPUT_SINGLE = 8;
uint256 internal constant COMMAND_TYPE_JUMP_STARGATE = 9;
}

contract WarpLink is IWarpLink, WarpLinkCommandTypes {
Expand Down Expand Up @@ -62,11 +64,33 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
bool underlying;
}

struct JumpStargateParams {
uint16 dstChainId;
uint256 srcPoolId;
uint256 dstPoolId;
}

struct TransientState {
address paramPartner;
uint16 paramFeeBps;
address paramRecipient;
uint256 paramAmountOut;
uint16 paramSlippageBps;
uint256 amount;
address payer;
address token;
uint48 deadline;
/**
* 0 or 1
*/
uint256 jumped;
/**
* The amount of native value not spent. The native value starts off as
* `msg.value - params.amount` and is decreased by spending money on jumps.
*
* Any leftover native value is returned to `msg.sender`
*/
uint256 nativeValueRemaining;
}

function processSplit(
Expand Down Expand Up @@ -106,6 +130,10 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {

tPart = engageInternal(stream, tPart);

if (tPart.jumped == 1) {
revert IllegalJumpInSplit();
}

if (partIndex == 0) {
firstPartPayerOut = tPart.payer;
firstPartTokenOut = tPart.token;
Expand Down Expand Up @@ -214,7 +242,7 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
TransientState memory t
) internal returns (TransientState memory) {
if (t.token == address(0)) {
revert EthNotSupportedForWarp();
revert NativeTokenNotSupported();
}

WarpUniV2LikeWarpSingleParams memory params;
Expand Down Expand Up @@ -395,7 +423,7 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
params.pool = stream.readAddress();

if (t.token == address(0)) {
revert EthNotSupportedForWarp();
revert NativeTokenNotSupported();
}

// NOTE: The pool is untrusted
Expand Down Expand Up @@ -608,6 +636,97 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
return t;
}

/**
* Jump to another chain using the Stargate bridge
*
* The token must not be ETH (0)
*
* After this operation, the token will be unchanged and `t.amount` will
* be how much was sent. `t.jumped` will be set to `1` to indicate
* that no more commands should be run
*
* The user may construct a command where `srcPoolId` is not for `t.token`. This is harmless
* because only `t.token` can be moved by Stargate.
*
* This command must not run inside of a split.
*
* A bridge fee must be paid in the native token. This fee is determined with
* `IStargateRouter.quoteLayerZeroFee`
*
* The value for `t.token` remains the same and is not chained.
*
* Params are read from the stream as:
* - dstChainId (uint16)
* - srcPoolId (uint8)
* - dstPoolId (uint8)
*/
function processJumpStargate(
uint256 stream,
TransientState memory t
) internal returns (TransientState memory) {
if (t.token == address(0)) {
// NOTE: There is a WETH pool
revert NativeTokenNotSupported();
}

JumpStargateParams memory params;
params.dstChainId = stream.readUint16();
params.srcPoolId = stream.readUint8();
params.dstPoolId = stream.readUint8();

// NOTE: It is not possible to know how many tokens were delivered. Therfore positive slippage
// is never charged
t.amount = LibKitty.calculateAndRegisterFee(
t.paramPartner,
t.token,
t.paramFeeBps,
t.amount,
t.amount
);

// Enforce minimum amount/max slippage
if (t.amount < LibWarp.applySlippage(t.paramAmountOut, t.paramSlippageBps)) {
revert InsufficientOutputAmount();
}

if (t.token != address(0)) {
if (t.payer != address(this)) {
// Transfer tokens from the sender to this contract
LibWarp.state().permit2.transferFrom(t.payer, address(this), (uint160)(t.amount), t.token);

// Update the payer to this contract
t.payer = address(this);
}

// Allow Stargate to transfer the tokens
IERC20(t.token).forceApprove(address(LibWarp.state().stargateRouter), t.amount);
}

t.jumped = 1;

LibWarp.state().stargateRouter.swap{value: t.nativeValueRemaining}({
_dstChainId: params.dstChainId,
_srcPoolId: params.srcPoolId,
_dstPoolId: params.dstPoolId,
// NOTE: There is no guarantee that `msg.sender` can handle receiving tokens/ETH
_refundAddress: payable(address(this)),
_amountLD: t.amount,
// Max 5% slippage
_minAmountLD: (t.amount * 95) / 100,
_lzTxParams: IStargateRouter.lzTxObj({
dstGasForCall: 0,
dstNativeAmount: 0,
dstNativeAddr: ''
}),
_to: abi.encodePacked(t.paramRecipient),
_payload: ''
});

t.nativeValueRemaining = 0;

return t;
}

function engageInternal(
uint256 stream,
TransientState memory t
Expand Down Expand Up @@ -635,6 +754,12 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
t = processWarpUniV3LikeExactInput(stream, t);
} else if (commandType == COMMAND_TYPE_WARP_CURVE_EXACT_INPUT_SINGLE) {
t = processWarpCurveExactInputSingle(stream, t);
} else if (commandType == COMMAND_TYPE_JUMP_STARGATE) {
if (commandIndex != commandCount - 1) {
revert JumpMustBeLastCommand();
}

t = processJumpStargate(stream, t);
} else {
revert UnhandledCommand();
}
Expand All @@ -648,7 +773,30 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
revert DeadlineExpired();
}

if (params.tokenIn != address(0)) {
TransientState memory t;
t.paramPartner = params.partner;
t.paramFeeBps = params.feeBps;
t.paramRecipient = params.recipient;
t.paramAmountOut = params.amountOut;
t.amount = params.amountIn;
t.token = params.tokenIn;
t.deadline = params.deadline;

if (params.tokenIn == address(0)) {
if (msg.value < params.amountIn) {
revert InsufficientEthValue();
}

t.nativeValueRemaining = msg.value - params.amountIn;

// The ETH has already been moved to this contract
t.payer = address(this);
} else {
// Tokens will initially moved from the sender
t.payer = msg.sender;

t.nativeValueRemaining = msg.value;

// Permit tokens / set allowance
LibWarp.state().permit2.permit(
msg.sender,
Expand All @@ -666,31 +814,6 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
);
}

TransientState memory t;
t.amount = params.amountIn;
t.token = params.tokenIn;
t.deadline = params.deadline;

if (msg.value == 0) {
if (params.tokenIn == address(0)) {
revert UnexpectedValueAndTokenCombination();
}

// Tokens will initially moved from the sender
t.payer = msg.sender;
} else {
if (params.tokenIn != address(0)) {
revert UnexpectedValueAndTokenCombination();
}

if (msg.value != params.amountIn) {
revert IncorrectEthValue();
}

// The ETH has already been moved to this contract
t.payer = address(this);
}

uint256 stream = Stream.createStream(params.commands);

t = engageInternal(stream, t);
Expand All @@ -707,6 +830,16 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
revert InsufficientOutputAmount();
}

if (t.nativeValueRemaining > 0) {
// TODO: Is this the correct recipient?
payable(msg.sender).transfer(t.nativeValueRemaining);
}

if (t.jumped == 1) {
// The coins have jumped away from this chain. Fees were collected before the jump
return;
}

// Collect fees
amountOut = LibKitty.calculateAndRegisterFee(
params.partner,
Expand Down
4 changes: 3 additions & 1 deletion packages/hardhat/contracts/init/InitLibWarp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ pragma solidity ^0.8.19;
import {IWETH} from '@uniswap/v2-periphery/contracts/interfaces/IWETH.sol';
import {LibWarp} from '../libraries/LibWarp.sol';
import {IPermit2} from '../interfaces/external/IPermit2.sol';
import {IStargateRouter} from '../interfaces/external/IStargateRouter.sol';

contract InitLibWarp {
function init(address weth, address permit2) public {
function init(address weth, address permit2, address router) public {
LibWarp.State storage s = LibWarp.state();

s.weth = IWETH(weth);
s.permit2 = IPermit2(permit2);
s.stargateRouter = IStargateRouter(router);
}
}
7 changes: 4 additions & 3 deletions packages/hardhat/contracts/interfaces/IWarpLink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {PermitParams} from '../libraries/PermitParams.sol';

interface IWarpLink {
error UnhandledCommand();
error IncorrectEthValue();
error InsufficientEthValue();
error InsufficientOutputAmount();
error InsufficientTokensDelivered();
error UnexpectedTokenForWrap();
Expand All @@ -15,10 +15,11 @@ interface IWarpLink {
error NotEnoughParts();
error InconsistentPartTokenOut();
error InconsistentPartPayerOut();
error UnexpectedValueAndTokenCombination();
error UnexpectedPayerForWrap();
error EthNotSupportedForWarp();
error NativeTokenNotSupported();
error DeadlineExpired();
error IllegalJumpInSplit();
error JumpMustBeLastCommand();

struct Params {
address partner;
Expand Down
24 changes: 24 additions & 0 deletions packages/hardhat/contracts/interfaces/external/IStargateRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.7.6;
pragma abicoder v2;

interface IStargateRouter {
struct lzTxObj {
uint256 dstGasForCall;
uint256 dstNativeAmount;
bytes dstNativeAddr;
}

function swap(
uint16 _dstChainId,
uint256 _srcPoolId,
uint256 _dstPoolId,
address payable _refundAddress,
uint256 _amountLD,
uint256 _minAmountLD,
lzTxObj memory _lzTxParams,
bytes calldata _to,
bytes calldata _payload
) external payable;
}
2 changes: 2 additions & 0 deletions packages/hardhat/contracts/libraries/LibWarp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ pragma solidity ^0.8.19;

import {IWETH} from '@uniswap/v2-periphery/contracts/interfaces/IWETH.sol';
import {IPermit2} from '../interfaces/external/IPermit2.sol';
import {IStargateRouter} from '../interfaces/external/IStargateRouter.sol';

library LibWarp {
bytes32 constant DIAMOND_STORAGE_SLOT = keccak256('diamond.storage.LibWarp');

struct State {
IWETH weth;
IPermit2 permit2;
IStargateRouter stargateRouter;
}

function state() internal pure returns (State storage s) {
Expand Down
7 changes: 6 additions & 1 deletion packages/hardhat/deploy-helpers/addresses.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
type AddressKey = 'uniswapV2Router02' | 'uniswapV2Factory' | 'weth' | 'permit2';
type AddressKey = 'uniswapV2Router02' | 'uniswapV2Factory' | 'weth' | 'permit2' | 'stargateRouter';

export const networkAddresses: Partial<Record<string, Partial<Record<AddressKey, string>>>> = {
mainnet: {
uniswapV2Router02: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
uniswapV2Factory: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f',
weth: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
permit2: '0x000000000022D473030F116dDEE9F6B43aC78BA3',
stargateRouter: '0x8731d54E9D02c286767d56ac03e8037C07e01e98',
},
goerli: {
uniswapV2Router02: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
uniswapV2Factory: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f',
weth: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
permit2: '0x000000000022D473030F116dDEE9F6B43aC78BA3',
stargateRouter: '0x7612aE2a34E5A363E137De748801FB4c86499152',
},
sepolia: {
uniswapV2Router02: '0xC532a74256D3Db42D0Bf7a0400fEFDbad7694008',
Expand All @@ -22,13 +24,16 @@ export const networkAddresses: Partial<Record<string, Partial<Record<AddressKey,
polygon: {
weth: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
permit2: '0x000000000022D473030F116dDEE9F6B43aC78BA3',
stargateRouter: '0x45A01E4e04F14f7A4a6702c74187c5F6222033cd',
},
arbitrum: {
weth: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
permit2: '0x000000000022D473030F116dDEE9F6B43aC78BA3',
stargateRouter: '0x53Bf833A5d6c4ddA888F69c22C88C9f356a41614',
},
optimism: {
weth: '0x4200000000000000000000000000000000000006',
permit2: '0x000000000022D473030F116dDEE9F6B43aC78BA3',
stargateRouter: '0x53Bf833A5d6c4ddA888F69c22C88C9f356a41614',
},
};
1 change: 1 addition & 0 deletions packages/hardhat/deploy/003_facets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const func = async function (hre: HardhatRuntimeEnvironment) {
calldata: initContract.interface.encodeFunctionData('init', [
addresses.weth,
addresses.permit2,
addresses.stargateRouter ?? '0x0000000000000000000000000000000000000000',
]),
};
},
Expand Down
Loading

0 comments on commit b6e8a26

Please sign in to comment.