Skip to content

Commit

Permalink
feat(hardhat): support post-jump commands for Stargate
Browse files Browse the repository at this point in the history
feat(hardhat): add jumpwarp test assertion

fix double charging fees

add test script

use updated sg testnet routers

minor jump test improve

minor net fix

fix broken test
  • Loading branch information
xykota committed Sep 29, 2023
1 parent d7433cf commit fe5af0b
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 49 deletions.
2 changes: 2 additions & 0 deletions packages/hardhat/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ typechain-types

# Hardhat files
cache
cache_forge/
artifacts
gas-report.txt
solidity-files-cache.json
deployments/sepolia
deployments/goerli
deployments/optimismGoerli
results.sarif
broadcast/
133 changes: 106 additions & 27 deletions packages/hardhat/contracts/facets/WarpLink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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';
import {IStargateReceiver} from '../interfaces/external/IStargateReceiver.sol';

abstract contract WarpLinkCommandTypes {
uint256 internal constant COMMAND_TYPE_WRAP = 1;
Expand All @@ -30,7 +31,7 @@ abstract contract WarpLinkCommandTypes {
uint256 internal constant COMMAND_TYPE_JUMP_STARGATE = 9;
}

contract WarpLink is IWarpLink, WarpLinkCommandTypes {
contract WarpLink is IWarpLink, IStargateReceiver, WarpLinkCommandTypes {
using SafeERC20 for IERC20;
using Stream for uint256;

Expand Down Expand Up @@ -68,6 +69,8 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
uint16 dstChainId;
uint256 srcPoolId;
uint256 dstPoolId;
uint256 dstGasForCall;
bytes payload;
}

struct TransientState {
Expand Down Expand Up @@ -635,6 +638,69 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
return t;
}

/**
* Cross-chain callback from Stargate
*
* The tokens have already been received by this contract, `t.payer` is set to this contract
* before `sgReceive` is called by the router.
*
* The `_nonce` field is not checked since it's assumed that LayerZero will not deliver the
* same message more than once.
*
* The Stargate router is trusted, meaning `_token` and `amountLD` is not verified. Should the
* Stargate router be compromised, an attacker can drain this contract.
*
* If the payload can not be decoded, tokens are left in this contract.
* If execution runs out of gas, tokens are left in this contract.
*
* If an error occurs during engage, such as insufficient output amount, tokens are refunded
* to the recipient.
*
* See https://stargateprotocol.gitbook.io/stargate/interfaces/evm-solidity-interfaces/istargatereceiver.sol
*/
function sgReceive(
uint16, // _srcChainId
bytes memory _srcAddress,
uint256, // _nonce
address _token,
uint256 amountLD,
bytes memory payload
) external {
if (msg.sender != address(LibWarp.state().stargateRouter)) {
revert InvalidSgReceiverSender();
}

address srcAddress = abi.decode(_srcAddress, (address));

if (srcAddress != address(this)) {
// NOTE: This assumed that this contract is deployed at the same address on every chain
revert InvalidSgReceiveSrcAddress();
}

Params memory params = abi.decode(payload, (Params));

try
IWarpLink(this).warpLinkEngage(
Params({
partner: params.partner,
feeBps: params.feeBps,
slippageBps: params.slippageBps,
recipient: params.recipient,
tokenIn: _token,
tokenOut: params.tokenOut,
amountIn: amountLD,
amountOut: params.amountOut,
deadline: params.deadline,
commands: params.commands
}),
PermitParams({nonce: 0, signature: ''})
)
{} catch {
// Refund tokens to the recipient
IERC20(_token).safeTransfer(params.recipient, amountLD);
}
}

/**
* Jump to another chain using the Stargate bridge
*
Expand Down Expand Up @@ -668,20 +734,27 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
revert NativeTokenNotSupported();
}

// TODO: Does this use the same gas than (a, b, c,) = (stream.read, ...)?
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 = LibStarVault.calculateAndRegisterFee(
t.paramPartner,
t.token,
t.paramFeeBps,
t.amount,
t.amount
);
params.dstGasForCall = stream.readUint32();
params.payload = stream.readBytes();

// If the tokens are being delivered directly to the recipient without a second
// WarpLink engage, the fee is charged on this chain
if (params.payload.length == 0) {
// NOTE: It is not possible to know how many tokens were delivered. Therfore positive slippage
// is never charged
t.amount = LibStarVault.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)) {
Expand Down Expand Up @@ -713,12 +786,14 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
// Max 5% slippage
_minAmountLD: (t.amount * 95) / 100,
_lzTxParams: IStargateRouter.lzTxObj({
dstGasForCall: 0,
dstGasForCall: params.dstGasForCall,
dstNativeAmount: 0,
dstNativeAddr: ''
}),
_to: abi.encodePacked(t.paramRecipient),
_payload: ''
// NOTE: This assumes the contract is deployed at the same address on every chain.
// If this is not the case, a new param needs to be added with the next WarpLink address
_to: abi.encodePacked(params.payload.length > 0 ? address(this) : t.paramRecipient),
_payload: params.payload
});

t.nativeValueRemaining = 0;
Expand Down Expand Up @@ -775,6 +850,7 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
TransientState memory t;
t.paramPartner = params.partner;
t.paramFeeBps = params.feeBps;
t.paramSlippageBps = params.slippageBps;
t.paramRecipient = params.recipient;
t.paramAmountOut = params.amountOut;
t.paramSlippageBps = params.slippageBps;
Expand All @@ -797,20 +873,23 @@ contract WarpLink is IWarpLink, WarpLinkCommandTypes {
t.nativeValueRemaining = msg.value;

// Permit tokens / set allowance
LibWarp.state().permit2.permit(
msg.sender,
IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: params.tokenIn,
amount: (uint160)(params.amountIn),
expiration: (uint48)(params.deadline),
nonce: (uint48)(permit.nonce)
// The signature is omitted when `warpLinkEngage` is called from `sgReceive`
if (permit.signature.length > 0) {
LibWarp.state().permit2.permit(
msg.sender,
IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: params.tokenIn,
amount: (uint160)(params.amountIn),
expiration: (uint48)(params.deadline),
nonce: (uint48)(permit.nonce)
}),
spender: address(this),
sigDeadline: (uint256)(params.deadline)
}),
spender: address(this),
sigDeadline: (uint256)(params.deadline)
}),
permit.signature
);
permit.signature
);
}
}

uint256 stream = Stream.createStream(params.commands);
Expand Down
2 changes: 2 additions & 0 deletions packages/hardhat/contracts/interfaces/IWarpLink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ interface IWarpLink {
error DeadlineExpired();
error IllegalJumpInSplit();
error JumpMustBeLastCommand();
error InvalidSgReceiverSender();
error InvalidSgReceiveSrcAddress();

struct Params {
address partner;
Expand Down
4 changes: 2 additions & 2 deletions packages/hardhat/deploy-helpers/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ export const networkAddresses: Partial<Record<string, Partial<Record<AddressKey,
uniswapV2Factory: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f',
weth: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
permit2: '0x000000000022D473030F116dDEE9F6B43aC78BA3',
stargateRouter: '0x7612aE2a34E5A363E137De748801FB4c86499152',
stargateRouter: '0x7C5B3F4865b41b9d2B6dE65fdfbB47af06AC41f0',
},
optimismGoerli: {
weth: '0x4200000000000000000000000000000000000006',
stargateRouter: '0x95461eF0e0ecabC049a5c4a6B98Ca7B335FAF068',
stargateRouter: '0xb82E8737e7BA953CB4462561639f32Fd7F0974c4',
},
sepolia: {
uniswapV2Router02: '0xC532a74256D3Db42D0Bf7a0400fEFDbad7694008',
Expand Down
8 changes: 8 additions & 0 deletions packages/hardhat/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ allow_paths = ["../../node_modules"]
# See https://github.com/foundry-rs/foundry/issues/4988#issuecomment-1556331314
evm_version = 'paris'
solc_version = '0.8.19'

[rpc_endpoints]
goerli = "${GOERLI_RPC_URL}"
optimism_goerli = "${OPTIMISM_GOERLI_RPC_URL}"

[etherscan]
goerli = { key = "${ETHERSCAN_API_KEY}" }
optimism_goerli = { key = "${ETHERSCAN_API_KEY}" }
Loading

0 comments on commit fe5af0b

Please sign in to comment.