From c550512ac29fbf2e13679f606ea1800b45f369a8 Mon Sep 17 00:00:00 2001 From: thal0x Date: Mon, 28 Oct 2024 08:34:57 -0500 Subject: [PATCH 01/13] update cosmwasm tests to use bech32 address for mailbox address --- .../fast-transfer-gateway/tests/common/mod.rs | 18 +++++++- .../tests/test_initiate_settlement.rs | 18 ++++++-- .../tests/test_initiate_timeout.rs | 11 +++-- .../tests/test_refund_orders.rs | 34 +++++++++++--- .../tests/test_settle_orders.rs | 44 ++++++++++++++++--- 5 files changed, 105 insertions(+), 20 deletions(-) diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/common/mod.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/common/mod.rs index 419743c..b3912cb 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/common/mod.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/common/mod.rs @@ -6,10 +6,12 @@ use cosmwasm_std::{ }; use go_fast::{ gateway::{Config, ExecuteMsg}, + helpers::keccak256_hash, FastTransferOrder, }; use go_fast_transfer_cw::{ error::ContractResponse, + helpers::bech32_encode, state::{CONFIG, LOCAL_DOMAIN, NONCE, REMOTE_DOMAINS}, }; use hyperlane::mailbox::{DefaultHookResponse, QueryMsg as HplQueryMsg, RequiredHookResponse}; @@ -28,7 +30,12 @@ pub fn default_instantiate() -> (OwnedDeps, &Config { token_denom: "uusdc".to_string(), address_prefix: "osmo".to_string(), - mailbox_addr: "mailbox_contract_address".into(), + mailbox_addr: bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), }, ) .unwrap(); @@ -49,7 +56,14 @@ pub fn default_instantiate() -> (OwnedDeps, let wasm_handler = |query: &WasmQuery| -> QuerierResult { match query { WasmQuery::Smart { contract_addr, msg } => { - if contract_addr == "mailbox_contract_address" { + if contract_addr + == &bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string() + { let msg: HplQueryMsg = from_json(msg).unwrap(); match msg { HplQueryMsg::Hook(_) => todo!(), diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_settlement.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_settlement.rs index 82033f6..8573d84 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_settlement.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_settlement.rs @@ -1,8 +1,8 @@ use common::default_instantiate; use cosmwasm_std::{testing::mock_info, to_json_binary, Addr, HexBinary, ReplyOn, SubMsg, WasmMsg}; -use go_fast::gateway::ExecuteMsg; +use go_fast::{gateway::ExecuteMsg, helpers::keccak256_hash}; use go_fast_transfer_cw::{ - helpers::{bech32_decode, left_pad_bytes}, + helpers::{bech32_decode, bech32_encode, left_pad_bytes}, state::{self, REMOTE_DOMAINS}, }; use hyperlane::mailbox::{DispatchMsg, ExecuteMsg as MailboxExecuteMsg}; @@ -45,7 +45,12 @@ fn test_initiate_settlement() { SubMsg { id: 0, msg: WasmMsg::Execute { - contract_addr: "mailbox_contract_address".into(), + contract_addr: bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), msg: to_json_binary(&MailboxExecuteMsg::Dispatch(DispatchMsg { dest_domain: 2, recipient_addr: HexBinary::from_hex( @@ -117,7 +122,12 @@ fn test_initiate_settlement_multiple_orders() { SubMsg { id: 0, msg: WasmMsg::Execute { - contract_addr: "mailbox_contract_address".into(), + contract_addr: bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), msg: to_json_binary(&MailboxExecuteMsg::Dispatch(DispatchMsg { dest_domain: 2, recipient_addr: HexBinary::from_hex( diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs index 59ebe6d..97d58e1 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs @@ -2,9 +2,9 @@ use common::default_instantiate; use cosmwasm_std::{ testing::mock_info, to_json_binary, HexBinary, ReplyOn, SubMsg, Uint128, WasmMsg, }; -use go_fast::{gateway::ExecuteMsg, FastTransferOrder}; +use go_fast::{gateway::ExecuteMsg, helpers::keccak256_hash, FastTransferOrder}; use go_fast_transfer_cw::{ - helpers::{bech32_decode, left_pad_bytes}, + helpers::{bech32_decode, bech32_encode, left_pad_bytes}, state::{self}, }; use hyperlane::mailbox::{DispatchMsg, ExecuteMsg as MailboxExecuteMsg}; @@ -69,7 +69,12 @@ fn test_initiate_timeout() { SubMsg { id: 0, msg: WasmMsg::Execute { - contract_addr: "mailbox_contract_address".into(), + contract_addr: bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), msg: to_json_binary(&MailboxExecuteMsg::Dispatch(DispatchMsg { dest_domain: 2, recipient_addr: HexBinary::from_hex( diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_refund_orders.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_refund_orders.rs index 7e752df..f4d8ad3 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_refund_orders.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_refund_orders.rs @@ -1,9 +1,9 @@ use crate::common::default_instantiate; use common::submit_order; use cosmwasm_std::{coin, testing::mock_info, BankMsg, HexBinary, ReplyOn, SubMsg, Uint128}; -use go_fast::{gateway::ExecuteMsg, FastTransferOrder}; +use go_fast::{gateway::ExecuteMsg, helpers::keccak256_hash, FastTransferOrder}; use go_fast_transfer_cw::{ - helpers::{bech32_decode, left_pad_bytes}, + helpers::{bech32_decode, bech32_encode, left_pad_bytes}, msg::{OrderStatus, TimeoutOrdersMessage}, state::{ORDER_STATUSES, REMOTE_DOMAINS}, }; @@ -76,7 +76,15 @@ fn test_refund_orders() { order_ids: vec![order_a.id(), order_b.id()], }; - let info = mock_info("mailbox_contract_address", &[]); + let info = mock_info( + &bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), + &[], + ); let execute_msg = ExecuteMsg::Handle(HandleMsg { origin: order_a.destination_domain, @@ -184,7 +192,15 @@ fn test_refund_orders_fails_on_unknown_order_id() { order_ids: vec![order_a.id(), order_b.id()], }; - let info = mock_info("mailbox_contract_address", &[]); + let info = mock_info( + &bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), + &[], + ); let execute_msg = ExecuteMsg::Handle(HandleMsg { origin: order_a.destination_domain, @@ -275,7 +291,15 @@ fn test_refund_orders_fails_if_orders_destination_domain_is_not_domain_message_o order_ids: vec![order_a.id(), order_b.id()], }; - let info = mock_info("mailbox_contract_address", &[]); + let info = mock_info( + &bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), + &[], + ); let execute_msg = ExecuteMsg::Handle(HandleMsg { origin: order_a.destination_domain, diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_settle_orders.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_settle_orders.rs index d64fdc8..767dc06 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_settle_orders.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_settle_orders.rs @@ -1,9 +1,9 @@ use crate::common::default_instantiate; use common::submit_order; use cosmwasm_std::{coin, testing::mock_info, BankMsg, HexBinary, ReplyOn, SubMsg, Uint128}; -use go_fast::{gateway::ExecuteMsg, FastTransferOrder}; +use go_fast::{gateway::ExecuteMsg, helpers::keccak256_hash, FastTransferOrder}; use go_fast_transfer_cw::{ - helpers::{bech32_decode, left_pad_bytes}, + helpers::{bech32_decode, bech32_encode, left_pad_bytes}, msg::{OrderStatus, SettleOrdersMessage}, state::{ORDER_STATUSES, REMOTE_DOMAINS}, }; @@ -80,7 +80,15 @@ fn test_settle_orders() { repayment_address: solver_hex.clone(), }; - let info = mock_info("mailbox_contract_address", &[]); + let info = mock_info( + &bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), + &[], + ); let execute_msg = ExecuteMsg::Handle(HandleMsg { origin: order_a.destination_domain, @@ -200,7 +208,15 @@ fn test_settle_orders_ignores_settled_orders_but_does_not_fail() { repayment_address: solver_hex.clone(), }; - let info = mock_info("mailbox_contract_address", &[]); + let info = mock_info( + &bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), + &[], + ); let execute_msg = ExecuteMsg::Handle(HandleMsg { origin: order_a.destination_domain, @@ -291,7 +307,15 @@ fn test_settle_orders_fails_on_unknown_order() { repayment_address: solver_hex.clone(), }; - let info = mock_info("mailbox_contract_address", &[]); + let info = mock_info( + &bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), + &[], + ); let execute_msg = ExecuteMsg::Handle(HandleMsg { origin: order_a.destination_domain, @@ -386,7 +410,15 @@ fn test_settle_orders_fails_if_orders_destination_domain_is_not_domain_message_o repayment_address: solver_hex.clone(), }; - let info = mock_info("mailbox_contract_address", &[]); + let info = mock_info( + &bech32_encode( + "osmo", + &keccak256_hash("mailbox_contract_address".as_bytes()), + ) + .unwrap() + .into_string(), + &[], + ); let execute_msg = ExecuteMsg::Handle(HandleMsg { origin: order_a.destination_domain, From 399ac9598bde7dea71642c7d5ae9e10af456f6c5 Mon Sep 17 00:00:00 2001 From: thal0x Date: Mon, 28 Oct 2024 08:41:24 -0500 Subject: [PATCH 02/13] handle arbitrary calls through separate router contract and don't allow hyperlane mailbox as order recipient --- .../fast-transfer-gateway/src/error.rs | 3 + .../fast-transfer-gateway/src/execute.rs | 4 + .../tests/test_fill_order.rs | 54 +++++++- solidity/src/FastTransferGateway.sol | 17 ++- solidity/src/GoFastMulticall.sol | 22 +++ .../src/interfaces/hyperlane/IMailbox.sol | 7 - solidity/test/ERC7683.t.sol | 8 +- solidity/test/FastTransferGateway.t.sol | 129 +++++++++++++++++- 8 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 solidity/src/GoFastMulticall.sol diff --git a/cosmwasm/contracts/fast-transfer-gateway/src/error.rs b/cosmwasm/contracts/fast-transfer-gateway/src/error.rs index 11ccd53..1c9d17c 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/src/error.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/src/error.rs @@ -44,6 +44,9 @@ pub enum ContractError { #[error("Incorrect domain for settlement")] IncorrectDomainForSettlement, + + #[error("Order recipient cannot be mailbox")] + OrderRecipientCannotBeMailbox, } pub type ContractResult = Result; diff --git a/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs b/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs index b286b20..72dac8a 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs @@ -61,6 +61,10 @@ pub fn fill_order( let recipient_address = bech32_encode(&config.address_prefix, &order.recipient)?; + if recipient_address == config.mailbox_addr { + return Err(ContractError::OrderRecipientCannotBeMailbox); + } + let msg: CosmosMsg = match order.data { Some(data) => WasmMsg::Execute { contract_addr: recipient_address.clone().to_string(), diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs index 5b8a10b..ad24873 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs @@ -8,7 +8,10 @@ use go_fast::{ gateway::{ExecuteMsg, OrderFill, QueryMsg}, FastTransferOrder, }; -use go_fast_transfer_cw::helpers::{bech32_decode, left_pad_bytes}; +use go_fast_transfer_cw::{ + helpers::{bech32_decode, left_pad_bytes}, + state::CONFIG, +}; pub mod common; @@ -167,6 +170,55 @@ fn test_fill_order_with_data() { ); } +#[test] +fn test_fill_order_fails_when_order_recipient_is_mailbox() { + let (mut deps, env) = default_instantiate(); + + let user_address = deps.api.with_prefix("osmo").addr_make("user"); + + let test_payload = to_json_binary(&BankMsg::Send { + to_address: "solver".to_string().into(), + amount: vec![coin(98_000_000, "uusdc")], + }) + .unwrap(); + + let mailbox_address = CONFIG.load(deps.as_ref().storage).unwrap().mailbox_addr; + + let order = FastTransferOrder { + sender: HexBinary::from(left_pad_bytes( + bech32_decode(user_address.as_str()).unwrap(), + 32, + )), + recipient: HexBinary::from(left_pad_bytes( + bech32_decode(mailbox_address.as_str()).unwrap(), + 32, + )), + amount_in: Uint128::new(100_000_000), + amount_out: Uint128::new(98_000_000), + nonce: 1, + source_domain: 2, + destination_domain: 1, + timeout_timestamp: env.block.time.seconds() + 1000, + data: Some(HexBinary::from(test_payload.clone())), + }; + + let execute_msg = ExecuteMsg::FillOrder { + filler: Addr::unchecked("solver"), + order: order.clone(), + }; + + let res = go_fast_transfer_cw::contract::execute( + deps.as_mut(), + env.clone(), + mock_info("solver", &[coin(order.amount_out.u128(), "uusdc")]), + execute_msg.clone(), + ) + .unwrap_err() + .to_string(); + + assert_eq!(res, "Order recipient cannot be mailbox"); +} + #[test] fn test_fill_order_fails_on_incorrect_amount() { let (mut deps, env) = default_instantiate(); diff --git a/solidity/src/FastTransferGateway.sol b/solidity/src/FastTransferGateway.sol index c245317..8e703a0 100644 --- a/solidity/src/FastTransferGateway.sol +++ b/solidity/src/FastTransferGateway.sol @@ -9,12 +9,14 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import {console} from "forge-std/console.sol"; + import {TypeCasts} from "./libraries/TypeCasts.sol"; import {OrderEncoder} from "./libraries/OrderEncoder.sol"; import {IPermit2} from "./interfaces/IPermit2.sol"; import {IMailbox} from "./interfaces/hyperlane/IMailbox.sol"; - +import {GoFastCaller} from "./GoFastMulticall.sol"; // Structure that contains the order details required to settle or refund an order struct SettlementDetails { @@ -90,6 +92,8 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab mapping(bytes32 => OrderFill) public orderFills; + GoFastCaller public goFastCaller; + constructor() { _disableInitializers(); } @@ -100,7 +104,8 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab address _token, address _mailbox, address _interchainSecurityModule, - address _permit2 + address _permit2, + address _goFastCaller ) external initializer { __Ownable_init(_owner); @@ -110,6 +115,7 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab localDomain = _localDomain; PERMIT2 = IPermit2(_permit2); nonce = 1; + goFastCaller = GoFastCaller(_goFastCaller); } /// @dev Emitted when an order is submitted @@ -237,13 +243,14 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab address recipient = TypeCasts.bytes32ToAddress(order.recipient); + require(recipient != address(mailbox), "FastTransferGateway: order recipient cannot be mailbox"); + orderStatuses[orderID] = OrderStatus.FILLED; orderFills[orderID] = OrderFill(orderID, filler, order.sourceDomain); if (order.data.length > 0) { - SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), order.amountOut); - IERC20(token).approve(address(recipient), order.amountOut); - (bool success,) = address(recipient).call(order.data); + SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(goFastCaller), order.amountOut); + (bool success,) = goFastCaller.execute(address(recipient), token, order.amountOut, order.data); if (!success) { assembly { returndatacopy(0, 0, returndatasize()) diff --git a/solidity/src/GoFastMulticall.sol b/solidity/src/GoFastMulticall.sol new file mode 100644 index 0000000..b7a0fe1 --- /dev/null +++ b/solidity/src/GoFastMulticall.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract GoFastCaller { + function execute(address _target, address _token, uint256 _amount, bytes memory _data) + external + returns (bool, bytes memory) + { + IERC20(_token).approve(_target, _amount); + + (bool success, bytes memory returnData) = _target.call(_data); + if (!success) { + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } + return (success, returnData); + } +} diff --git a/solidity/src/interfaces/hyperlane/IMailbox.sol b/solidity/src/interfaces/hyperlane/IMailbox.sol index be4fd4d..0a9378b 100644 --- a/solidity/src/interfaces/hyperlane/IMailbox.sol +++ b/solidity/src/interfaces/hyperlane/IMailbox.sol @@ -48,13 +48,6 @@ interface IMailbox { view returns (uint256 fee); - function dispatch( - uint32 destinationDomain, - bytes32 recipientAddress, - bytes calldata body, - bytes calldata defaultHookMetadata - ) external payable returns (bytes32 messageId); - function quoteDispatch( uint32 destinationDomain, bytes32 recipientAddress, diff --git a/solidity/test/ERC7683.t.sol b/solidity/test/ERC7683.t.sol index 1abc916..f36631c 100644 --- a/solidity/test/ERC7683.t.sol +++ b/solidity/test/ERC7683.t.sol @@ -12,6 +12,7 @@ import {IPermit2} from "../src/interfaces/IPermit2.sol"; import {OnchainCrossChainOrder, GaslessCrossChainOrder} from "../src/erc7683/ERC7683.sol"; import {GoFastERC7683, OrderData} from "../src/GoFastERC7683.sol"; import {OrderEncoder} from "../src/libraries/OrderEncoder.sol"; +import {GoFastCaller} from "../src/GoFastMulticall.sol"; interface IUniswapV2Router02 { function swapExactTokensForTokens( @@ -55,17 +56,20 @@ contract ERC7683Test is Test { solver = address(2); mailbox = address(0x979Ca5202784112f4738403dBec5D0F3B9daabB9); + GoFastCaller goFastCaller = new GoFastCaller(); + FastTransferGateway gatewayImpl = new FastTransferGateway(); ERC1967Proxy gatewayProxy = new ERC1967Proxy( address(gatewayImpl), abi.encodeWithSignature( - "initialize(uint32,address,address,address,address,address)", + "initialize(uint32,address,address,address,address,address,address)", 1, address(this), address(usdc), mailbox, 0x3d0BE14dFbB1Eb736303260c1724B6ea270c8Dc4, - address(permit2) + address(permit2), + address(goFastCaller) ) ); gateway = FastTransferGateway(address(gatewayProxy)); diff --git a/solidity/test/FastTransferGateway.t.sol b/solidity/test/FastTransferGateway.t.sol index e520699..e914621 100644 --- a/solidity/test/FastTransferGateway.t.sol +++ b/solidity/test/FastTransferGateway.t.sol @@ -10,6 +10,8 @@ import {FastTransferGateway, FastTransferOrder, OrderFill} from "../src/FastTran import {TypeCasts} from "../src/libraries/TypeCasts.sol"; import {OrderEncoder} from "../src/libraries/OrderEncoder.sol"; import {IPermit2} from "../src/interfaces/IPermit2.sol"; +import {IMailbox} from "../src/interfaces/hyperlane/IMailbox.sol"; +import {GoFastCaller} from "../src/GoFastMulticall.sol"; interface IUniswapV2Router02 { function swapExactTokensForTokens( @@ -41,6 +43,7 @@ contract FastTransferGatewayTest is Test { address user; address solver; address mailbox; + address goFastCaller; function setUp() public { arbitrumFork = vm.createFork(vm.envString("RPC_URL")); @@ -52,17 +55,20 @@ contract FastTransferGatewayTest is Test { solver = address(2); mailbox = address(0x979Ca5202784112f4738403dBec5D0F3B9daabB9); + GoFastCaller _goFastCaller = new GoFastCaller(); + goFastCaller = address(_goFastCaller); FastTransferGateway gatewayImpl = new FastTransferGateway(); ERC1967Proxy gatewayProxy = new ERC1967Proxy( address(gatewayImpl), abi.encodeWithSignature( - "initialize(uint32,address,address,address,address,address)", + "initialize(uint32,address,address,address,address,address,address)", 1, address(this), address(usdc), mailbox, 0x3d0BE14dFbB1Eb736303260c1724B6ea270c8Dc4, - address(permit2) + address(permit2), + goFastCaller ) ); gateway = FastTransferGateway(address(gatewayProxy)); @@ -516,6 +522,125 @@ contract FastTransferGatewayTest is Test { assertEq(orderFiller, solver); } + function test_revertFillOrderCantTransferTokensFromAnotherUser() public { + uint256 amountIn = 100_000000; + uint256 amountOut = 98_000000; + uint32 sourceDomain = 1; + bytes32 sourceContract = TypeCasts.addressToBytes32(address(0xB)); + + deal(address(usdc), solver, amountOut, true); + deal(address(usdc), user, 5 ether, true); + + uint256 userBalanceBefore = usdc.balanceOf(user); + + bytes memory data = + abi.encodeWithSelector(IERC20.transferFrom.selector, user, address(0x1337), uint256(5 ether)); + + gateway.setRemoteDomain(sourceDomain, sourceContract); + + FastTransferOrder memory order = FastTransferOrder({ + sender: TypeCasts.addressToBytes32(address(0xB)), + recipient: TypeCasts.addressToBytes32(address(usdc)), + amountIn: amountIn, + amountOut: amountOut, + nonce: 1, + sourceDomain: sourceDomain, + destinationDomain: 1, + timeoutTimestamp: block.timestamp + 1 days, + data: data + }); + + vm.prank(user); + usdc.approve(address(gateway), 5 ether); + + vm.startPrank(solver); + usdc.approve(address(gateway), amountOut); + + vm.expectRevert("ERC20: transfer amount exceeds allowance"); + gateway.fillOrder(solver, order); + vm.stopPrank(); + + assertEq(usdc.balanceOf(user), userBalanceBefore); + } + + function test_revertFillOrderCantTransferTokensFromGateway() public { + uint256 amountIn = 100_000000; + uint256 amountOut = 98_000000; + uint32 sourceDomain = 1; + bytes32 sourceContract = TypeCasts.addressToBytes32(address(0xB)); + + deal(address(usdc), solver, 5 ether); + deal(address(usdc), address(gateway), 5 ether, true); + + bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, address(0x1337), uint256(5 ether)); + + gateway.setRemoteDomain(sourceDomain, sourceContract); + + FastTransferOrder memory order = FastTransferOrder({ + sender: TypeCasts.addressToBytes32(address(0xB)), + recipient: TypeCasts.addressToBytes32(address(usdc)), + amountIn: amountIn, + amountOut: amountOut, + nonce: 1, + sourceDomain: sourceDomain, + destinationDomain: 1, + timeoutTimestamp: block.timestamp + 1 days, + data: data + }); + + uint256 gatewayBalanceBefore = usdc.balanceOf(address(gateway)); + + vm.startPrank(solver); + usdc.approve(address(gateway), amountOut); + + vm.expectRevert("ERC20: transfer amount exceeds balance"); + gateway.fillOrder(solver, order); + vm.stopPrank(); + + uint256 gatewayBalanceAfter = usdc.balanceOf(address(gateway)); + assertEq(gatewayBalanceAfter, gatewayBalanceBefore); + } + + function test_revertFillOrderCantCallMailbox() public { + uint256 amountIn = 100_000000; + uint256 amountOut = 98_000000; + uint32 sourceDomain = 1; + bytes32 sourceContract = TypeCasts.addressToBytes32(address(0xB)); + + deal(address(usdc), solver, 5 ether); + deal(address(usdc), address(gateway), 5 ether, true); + + bytes memory data = abi.encodeWithSelector( + IMailbox.dispatch.selector, 1, TypeCasts.addressToBytes32(address(0x1337)), bytes("") + ); + + gateway.setRemoteDomain(sourceDomain, sourceContract); + + FastTransferOrder memory order = FastTransferOrder({ + sender: TypeCasts.addressToBytes32(address(0xB)), + recipient: TypeCasts.addressToBytes32(address(mailbox)), + amountIn: amountIn, + amountOut: amountOut, + nonce: 1, + sourceDomain: sourceDomain, + destinationDomain: 1, + timeoutTimestamp: block.timestamp + 1 days, + data: data + }); + + uint256 gatewayBalanceBefore = usdc.balanceOf(address(gateway)); + + vm.startPrank(solver); + usdc.approve(address(gateway), amountOut); + + vm.expectRevert("FastTransferGateway: order recipient cannot be mailbox"); + gateway.fillOrder(solver, order); + vm.stopPrank(); + + uint256 gatewayBalanceAfter = usdc.balanceOf(address(gateway)); + assertEq(gatewayBalanceAfter, gatewayBalanceBefore); + } + function test_revertFillOrderWhenOrderExpired() public { uint256 amountIn = 100_000000; uint256 amountOut = 98_000000; From c81f846660a01a7846c4bd939d865a119dc70d3e Mon Sep 17 00:00:00 2001 From: thal0x Date: Mon, 28 Oct 2024 08:49:21 -0500 Subject: [PATCH 03/13] when initiating timeouts, validate orders destination domain is the local domain --- .../fast-transfer-gateway/src/execute.rs | 1 + .../tests/test_initiate_timeout.rs | 57 +++++++++++++++++++ solidity/src/FastTransferGateway.sol | 3 + solidity/test/FastTransferGateway.t.sol | 32 +++++++++++ 4 files changed, 93 insertions(+) diff --git a/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs b/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs index 72dac8a..21391cd 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs @@ -151,6 +151,7 @@ pub fn initiate_timeout( for order in &orders { assert_order_is_expired(&env, order)?; assert_order_not_filled(deps.as_ref(), order.id())?; + assert_local_domain(deps.as_ref(), order.destination_domain)?; } let order_ids = orders diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs index 97d58e1..82d862c 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs @@ -318,3 +318,60 @@ fn test_initiate_timeout_fails_if_source_domain_is_unknown() { assert_eq!(res, "Unknown remote domain"); } + +#[test] +fn test_initiate_timeout_fails_if_destination_domain_is_not_the_local_domain() { + let (mut deps, env) = default_instantiate(); + + let solver_address = deps.api.with_prefix("osmo").addr_make("solver"); + + let user_address = deps.api.with_prefix("osmo").addr_make("user"); + + let order_a = FastTransferOrder { + sender: HexBinary::from(left_pad_bytes( + bech32_decode(user_address.as_str()).unwrap(), + 32, + )), + recipient: HexBinary::from(left_pad_bytes( + bech32_decode(user_address.as_str()).unwrap(), + 32, + )), + amount_in: Uint128::new(100_000_000), + amount_out: Uint128::new(98_000_000), + nonce: 1, + source_domain: 2, + destination_domain: 3, + timeout_timestamp: env.block.time.seconds() - 1000, + data: None, + }; + + let order_b = FastTransferOrder { + sender: HexBinary::from(left_pad_bytes( + bech32_decode(user_address.as_str()).unwrap(), + 32, + )), + recipient: HexBinary::from(left_pad_bytes( + bech32_decode(user_address.as_str()).unwrap(), + 32, + )), + amount_in: Uint128::new(100_000_000), + amount_out: Uint128::new(98_000_000), + nonce: 1, + source_domain: 2, + destination_domain: 1, + timeout_timestamp: env.block.time.seconds() - 1000, + data: None, + }; + + let execute_msg = ExecuteMsg::InitiateTimeout { + orders: vec![order_a, order_b], + }; + + let info = mock_info(solver_address.as_str(), &[]); + + let res = go_fast_transfer_cw::contract::execute(deps.as_mut(), env, info, execute_msg.clone()) + .unwrap_err() + .to_string(); + + assert_eq!(res, "Invalid local domain"); +} diff --git a/solidity/src/FastTransferGateway.sol b/solidity/src/FastTransferGateway.sol index 8e703a0..e3c3e27 100644 --- a/solidity/src/FastTransferGateway.sol +++ b/solidity/src/FastTransferGateway.sol @@ -295,6 +295,9 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab uint32 sourceDomain; for (uint256 i = 0; i < orders.length; i++) { FastTransferOrder memory order = orders[i]; + + require(order.destinationDomain == localDomain, "FastTransferGateway: invalid local domain"); + bytes32 orderID = _orderID(order); OrderStatus status = orderStatuses[orderID]; diff --git a/solidity/test/FastTransferGateway.t.sol b/solidity/test/FastTransferGateway.t.sol index e914621..12f4059 100644 --- a/solidity/test/FastTransferGateway.t.sol +++ b/solidity/test/FastTransferGateway.t.sol @@ -1041,6 +1041,38 @@ contract FastTransferGatewayTest is Test { vm.stopPrank(); } + function test_initiateTimeoutRevertsIfDestinationDomainIsNotTheLocalDomain() public { + uint32 sourceDomain = 8453; + bytes32 sourceContract = TypeCasts.addressToBytes32(address(0xB)); + + gateway.setRemoteDomain(sourceDomain, sourceContract); + + FastTransferOrder memory orderA = FastTransferOrder({ + sender: TypeCasts.addressToBytes32(address(0xB)), + recipient: TypeCasts.addressToBytes32(address(0xC)), + amountIn: 100_000000, + amountOut: 98_000000, + nonce: 1, + sourceDomain: sourceDomain, + destinationDomain: 3, + timeoutTimestamp: block.timestamp - 1 hours, + data: bytes("") + }); + + deal(address(usdc), solver, orderA.amountOut, true); + deal(solver, 1 ether); + + FastTransferOrder[] memory orders = new FastTransferOrder[](1); + orders[0] = orderA; + + uint256 hyperlaneFee = gateway.quoteInitiateTimeout(sourceDomain, orders); + + vm.startPrank(solver); + vm.expectRevert("FastTransferGateway: invalid local domain"); + gateway.initiateTimeout{value: hyperlaneFee}(orders); + vm.stopPrank(); + } + function _submitOrder(uint256 amountIn, uint256 amountOut, uint32 destinationDomain, bytes memory data) internal returns (bytes32) From 882a9716f9bc255425d5758aed468d90a51934de Mon Sep 17 00:00:00 2001 From: thal0x Date: Mon, 28 Oct 2024 09:11:29 -0500 Subject: [PATCH 04/13] when calling initiateSettlement, revert on duplicate order id --- .../fast-transfer-gateway/src/error.rs | 3 ++ .../fast-transfer-gateway/src/execute.rs | 4 ++ .../tests/test_initiate_settlement.rs | 34 ++++++++++++++++ solidity/src/FastTransferGateway.sol | 22 +++++++++++ solidity/test/FastTransferGateway.t.sol | 39 +++++++++++++++++++ 5 files changed, 102 insertions(+) diff --git a/cosmwasm/contracts/fast-transfer-gateway/src/error.rs b/cosmwasm/contracts/fast-transfer-gateway/src/error.rs index 1c9d17c..ad1cb73 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/src/error.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/src/error.rs @@ -47,6 +47,9 @@ pub enum ContractError { #[error("Order recipient cannot be mailbox")] OrderRecipientCannotBeMailbox, + + #[error("Duplicate order")] + DuplicateOrder, } pub type ContractResult = Result; diff --git a/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs b/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs index 21391cd..580200a 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/src/execute.rs @@ -100,6 +100,10 @@ pub fn initiate_settlement( return Err(ContractError::Unauthorized); } + if fills_to_settle.contains(&order_fill) { + return Err(ContractError::DuplicateOrder); + } + fills_to_settle.push(order_fill); } diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_settlement.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_settlement.rs index 8573d84..c10fb9d 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_settlement.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_settlement.rs @@ -319,3 +319,37 @@ fn test_initiate_settlement_multiple_orders_fails_if_source_domains_are_differen assert_eq!(res, "Source domains must match"); } + +#[test] +fn test_initiate_settlement_fails_if_orders_are_duplicate() { + let (mut deps, env) = default_instantiate(); + + let solver_address = deps.api.with_prefix("osmo").addr_make("solver"); + + let order_id = HexBinary::from_hex("1234").unwrap(); + + state::order_fills() + .create_order_fill( + deps.as_mut().storage, + order_id.clone(), + solver_address.clone(), + 2, + ) + .unwrap(); + + let execute_msg = ExecuteMsg::InitiateSettlement { + order_ids: vec![order_id.clone(), order_id.clone()], + repayment_address: HexBinary::from(left_pad_bytes( + bech32_decode(solver_address.as_str()).unwrap(), + 32, + )), + }; + + let info = mock_info(solver_address.as_str(), &[]); + + let res = go_fast_transfer_cw::contract::execute(deps.as_mut(), env, info, execute_msg.clone()) + .unwrap_err() + .to_string(); + + assert_eq!(res, "Duplicate order"); +} diff --git a/solidity/src/FastTransferGateway.sol b/solidity/src/FastTransferGateway.sol index e3c3e27..a22cc9d 100644 --- a/solidity/src/FastTransferGateway.sol +++ b/solidity/src/FastTransferGateway.sol @@ -266,6 +266,8 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab /// @param repaymentAddress The address to repay the orders to /// @param orderIDs The IDs of the orders to settle function initiateSettlement(bytes32 repaymentAddress, bytes memory orderIDs) public payable { + _checkForDuplicateOrders(orderIDs); + uint32 sourceDomain; for (uint256 pos = 0; pos < orderIDs.length; pos += 32) { bytes32 orderID; @@ -501,5 +503,25 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab return orderFill; } + function _checkForDuplicateOrders(bytes memory orderIDs) internal pure { + for (uint256 pos = 0; pos < orderIDs.length; pos += 32) { + bytes32 orderID; + assembly { + orderID := mload(add(orderIDs, add(0x20, pos))) + } + + for (uint256 pos2 = pos + 32; pos2 < orderIDs.length; pos2 += 32) { + bytes32 orderID2; + assembly { + orderID2 := mload(add(orderIDs, add(0x20, pos2))) + } + + if (orderID2 == orderID) { + revert("FastTransferGateway: duplicate order"); + } + } + } + } + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} } diff --git a/solidity/test/FastTransferGateway.t.sol b/solidity/test/FastTransferGateway.t.sol index 12f4059..0cd2938 100644 --- a/solidity/test/FastTransferGateway.t.sol +++ b/solidity/test/FastTransferGateway.t.sol @@ -894,6 +894,45 @@ contract FastTransferGatewayTest is Test { vm.stopPrank(); } + function test_revertInitiateSettlementOnDuplicateOrder() public { + uint32 sourceDomain = 8453; + bytes32 sourceContract = TypeCasts.addressToBytes32(address(0xB)); + + gateway.setRemoteDomain(sourceDomain, sourceContract); + + FastTransferOrder memory orderA = FastTransferOrder({ + sender: TypeCasts.addressToBytes32(address(0xB)), + recipient: TypeCasts.addressToBytes32(address(0xC)), + amountIn: 100_000000, + amountOut: 98_000000, + nonce: 1, + sourceDomain: sourceDomain, + destinationDomain: 1, + timeoutTimestamp: block.timestamp + 1 days, + data: bytes("") + }); + + deal(address(usdc), solver, orderA.amountOut + orderA.amountOut, true); + deal(solver, 1 ether); + + bytes memory orderIDs; + orderIDs = bytes.concat(orderIDs, OrderEncoder.id(orderA)); + orderIDs = bytes.concat(orderIDs, OrderEncoder.id(orderA)); + + uint256 hyperlaneFee = + gateway.quoteInitiateSettlement(sourceDomain, TypeCasts.addressToBytes32(solver), orderIDs); + + vm.startPrank(solver); + + usdc.approve(address(gateway), orderA.amountOut + orderA.amountOut); + + gateway.fillOrder(solver, orderA); + + vm.expectRevert("FastTransferGateway: duplicate order"); + gateway.initiateSettlement{value: hyperlaneFee}(TypeCasts.addressToBytes32(solver), orderIDs); + vm.stopPrank(); + } + function test_initiateTimeout() public { uint32 sourceDomain = 8453; bytes32 sourceContract = TypeCasts.addressToBytes32(address(0xB)); From 5fb710a6d0650f4b9ab3b85bc0e9cefd630beda7 Mon Sep 17 00:00:00 2001 From: thal0x Date: Tue, 29 Oct 2024 03:10:48 -0500 Subject: [PATCH 05/13] add script to compare order encoding between cosmwasm and solidity --- test/.gitignore | 175 +++++++++++++++++++++++++++++++++++++++++++++ test/README.md | 15 ++++ test/bun.lockb | Bin 0 -> 3125 bytes test/index.ts | 40 +++++++++++ test/package.json | 11 +++ test/tsconfig.json | 27 +++++++ 6 files changed, 268 insertions(+) create mode 100644 test/.gitignore create mode 100644 test/README.md create mode 100755 test/bun.lockb create mode 100644 test/index.ts create mode 100644 test/package.json create mode 100644 test/tsconfig.json diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..4056ba5 --- /dev/null +++ b/test/README.md @@ -0,0 +1,15 @@ +# test + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.1.30. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/test/bun.lockb b/test/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..def1d786f860cc2cb8145c65407dee2009c28488 GIT binary patch literal 3125 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_lNfzZLs8 ztLGh568qBXAjR8k+cfWuE2GTLuq5+7vqoN#rQAS8K)?>67&y@A1}HxprhuU&wYUT% z$OXX-UO<|YfuUhKkOmsoa0f{90O>xcIFJIVZE)q~xstoBO70Y!=3Z}u#xK=x($97X zy;hp2AJwb2)z*?T-{#n>z*)N&pPo@UKkHKFeANaW?bg&JMvws%08l7^fCvzq0o?&I z4}^hkBnrUv2Ln}GBk5NlN;d-oO#dRFdMluQkUKzj^O30k1yH>sP(Le>mI7iBO$>n9 zuL<af zkWHSxfBgSI22m3L&0}PMn2T&QOOX2p)z^YNfB!M&evv${xL>+s`|IP4ZQ(P6yE02_ zj@2;CIeMAt?DZ6Dseh*Xbk!Y_1m8^S6q>zv>FTvd6FY9MKr$EPR~TT?m(V-jq;CCn zVy^55H!$93kIUi|#_b;E^wsU7E;bdDyOYG*a)8O8hW6t9gr!J5Wi=BuDr)0??vkS%b zcB@M=6ax*8GEO{PY}Y>{(}dY>8f$rBl=dtnb6H@H1=1{P0X^2w7nV42tqLrhyhuK* zK_o20`0Y{aGxiU^mX)kFU$If~S-@1kvnu*~TV_O7vc~9l)NGoh`2FF2Z_%_@RZEb} zMNZc&_by+Y^w(vZ!^?9KM%qd?OS0Kg82bg6-rMbPu7ARb+moWchuvjtk4-2$z$+%Y zcCujZ*L~OGA`X^1YjrrQTrr%8WG*b7ft=0KW-fFsBYwBWj>vqDs2k_s?mE0Ccg_y8 zyvbTO)~c;rSlHVvEA-x6&ff1)z+0A-8} z^bC#kEMP4SSjzz@W2$Flre}ghj~2vZ3>{D(o`CxlIZBIDbIVeT^js@SQj78ubAn5X zGV{{y6buo8U;z(=lHz2rnFoH0EG?QcK?Kww1Bvn9TWs@Aws2QTadB!9(6rdg3XMT& z-alf2kqBxRwOE**VXWZ$_XFyJxzH9EFbx31+Yo1PmsA#{7AF^F7L+)Enfkz1Uq(qu zL9vy-eo<}8| zhB4R-sPW~+P)1&UN-7@XkW>K;1S!^qM53Vy$VH`jDVfQc1RaH}3}_58R~H=frXa%z rJEOc9sJpya7o5T%K?06ENQB|hkB9@H0SFGzax*<+kloN!Hs~k-L0~-r literal 0 HcmV?d00001 diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..25d0a94 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,40 @@ +import { $ } from "bun"; + +async function getOrderIDFromSolidity() { + const response = await $`forge script ./script/PrintOrderID.s.sol` + .cwd("../solidity") + .quiet(); + + const parts = response.text().split("== Logs =="); + + return parts[1].trim(); +} + +async function getOrderIDFromCosmwasm() { + const response = await $`cargo run ./bin/print-order-id` + .cwd("../cosmwasm") + .quiet(); + + const parts = response.text().split("== Output =="); + + return `0x${parts[1].trim()}`; +} + +async function main() { + const solidityOrderID = await getOrderIDFromSolidity(); + const cosmwasmOrderID = await getOrderIDFromCosmwasm(); + + if (!solidityOrderID || !cosmwasmOrderID) { + throw new Error("Failed to get order IDs"); + } + + if (solidityOrderID !== cosmwasmOrderID) { + throw new Error( + `Order IDs do not match\nSolidity: ${solidityOrderID}\nCosmwasm: ${cosmwasmOrderID}` + ); + } + + console.log("Order IDs match"); +} + +main(); diff --git a/test/package.json b/test/package.json new file mode 100644 index 0000000..9a45dea --- /dev/null +++ b/test/package.json @@ -0,0 +1,11 @@ +{ + "name": "test", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} \ No newline at end of file diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} From e2041975e4d686cfb1369547ae4551c297c2d6a5 Mon Sep 17 00:00:00 2001 From: thal0x Date: Tue, 29 Oct 2024 03:10:55 -0500 Subject: [PATCH 06/13] add script to compare order encoding between cosmwasm and solidity --- cosmwasm/Cargo.lock | 8 ++++++++ cosmwasm/Cargo.toml | 1 + cosmwasm/bin/print-order-id/Cargo.toml | 8 ++++++++ cosmwasm/bin/print-order-id/src/main.rs | 19 +++++++++++++++++++ solidity/script/PrintOrderID.s.sol | 25 +++++++++++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 cosmwasm/bin/print-order-id/Cargo.toml create mode 100644 cosmwasm/bin/print-order-id/src/main.rs create mode 100644 solidity/script/PrintOrderID.s.sol diff --git a/cosmwasm/Cargo.lock b/cosmwasm/Cargo.lock index e6bad9f..5e41e78 100644 --- a/cosmwasm/Cargo.lock +++ b/cosmwasm/Cargo.lock @@ -555,6 +555,14 @@ dependencies = [ "spki", ] +[[package]] +name = "print-order-id" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "go-fast", +] + [[package]] name = "proc-macro2" version = "1.0.86" diff --git a/cosmwasm/Cargo.toml b/cosmwasm/Cargo.toml index 1c1af9a..b3b49ce 100644 --- a/cosmwasm/Cargo.toml +++ b/cosmwasm/Cargo.toml @@ -4,6 +4,7 @@ members = [ "contracts/fast-transfer-gateway", "contracts/cw7683", "packages/*", + "bin/print-order-id", ] [profile.release] diff --git a/cosmwasm/bin/print-order-id/Cargo.toml b/cosmwasm/bin/print-order-id/Cargo.toml new file mode 100644 index 0000000..6c18804 --- /dev/null +++ b/cosmwasm/bin/print-order-id/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "print-order-id" +version = "0.1.0" +edition = "2021" + +[dependencies] +cosmwasm-std = { workspace = true } +go-fast = { workspace = true } diff --git a/cosmwasm/bin/print-order-id/src/main.rs b/cosmwasm/bin/print-order-id/src/main.rs new file mode 100644 index 0000000..34220ca --- /dev/null +++ b/cosmwasm/bin/print-order-id/src/main.rs @@ -0,0 +1,19 @@ +use cosmwasm_std::{HexBinary, Uint128}; +use go_fast::{helpers::keccak256_hash, FastTransferOrder}; + +fn main() { + let order = FastTransferOrder { + sender: keccak256_hash("order_sender".as_bytes()), + recipient: keccak256_hash("order_recipient".as_bytes()), + amount_in: Uint128::new(1_000000), + amount_out: Uint128::new(2_000000), + nonce: 5, + source_domain: 1, + destination_domain: 2, + timeout_timestamp: 1234567890, + data: Some(HexBinary::from("order_data".as_bytes())), + }; + + println!("== Output =="); + println!("{}", order.id()); +} diff --git a/solidity/script/PrintOrderID.s.sol b/solidity/script/PrintOrderID.s.sol new file mode 100644 index 0000000..38a1943 --- /dev/null +++ b/solidity/script/PrintOrderID.s.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; + +import {FastTransferOrder} from "../src/FastTransferGateway.sol"; +import {OrderEncoder} from "../src/libraries/OrderEncoder.sol"; + +contract PrintOrderID is Script { + function run() public pure { + FastTransferOrder memory order = FastTransferOrder({ + sender: keccak256("order_sender"), + recipient: keccak256("order_recipient"), + amountIn: 1_000000, + amountOut: 2_000000, + nonce: 5, + sourceDomain: 1, + destinationDomain: 2, + timeoutTimestamp: 1234567890, + data: bytes("order_data") + }); + + console.logBytes32(OrderEncoder.id(order)); + } +} From b2461f98e7180e42b915a71ad1c2b0b64b0645db Mon Sep 17 00:00:00 2001 From: thal0x Date: Tue, 29 Oct 2024 03:37:44 -0500 Subject: [PATCH 07/13] use uint32 for nonce and uint64 for timeout timestamp --- .../tests/test_initiate_timeout.rs | 2 +- cosmwasm/packages/gofast/src/lib.rs | 14 +++--- solidity/script/7683/SubmitOrder.s.sol | 2 +- solidity/script/SubmitOrder.s.sol | 2 +- solidity/src/FastTransferGateway.sol | 10 ++-- solidity/src/GoFastERC7683.sol | 8 +-- solidity/src/libraries/OrderEncoder.sol | 10 ++-- solidity/test/ERC7683.t.sol | 6 +-- solidity/test/FastTransferGateway.t.sol | 50 +++++++++---------- 9 files changed, 51 insertions(+), 53 deletions(-) diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs index 82d862c..43c6103 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_initiate_timeout.rs @@ -82,7 +82,7 @@ fn test_initiate_timeout() { ) .unwrap(), msg_body: HexBinary::from_hex( - "0148497add4581c6bb69bbc7ce250379c4238cf24cfcf4334ef99c35afd5a5effa48497add4581c6bb69bbc7ce250379c4238cf24cfcf4334ef99c35afd5a5effa" + "01b52d0eadcef62db278b39caf9af717fb004d9dc610849c083120e1d477c75f8eb52d0eadcef62db278b39caf9af717fb004d9dc610849c083120e1d477c75f8e" ) .unwrap(), hook: Some( diff --git a/cosmwasm/packages/gofast/src/lib.rs b/cosmwasm/packages/gofast/src/lib.rs index 4f8772d..58db5a7 100644 --- a/cosmwasm/packages/gofast/src/lib.rs +++ b/cosmwasm/packages/gofast/src/lib.rs @@ -54,11 +54,9 @@ impl From for HexBinary { .chain(order.amount_in.to_be_bytes().iter()) .chain([0u8; 16].iter()) .chain(order.amount_out.to_be_bytes().iter()) - .chain([0u8; 28].iter()) .chain(order.nonce.to_be_bytes().iter()) .chain(order.source_domain.to_be_bytes().iter()) .chain(order.destination_domain.to_be_bytes().iter()) - .chain([0u8; 24].iter()) .chain(order.timeout_timestamp.to_be_bytes().iter()) .chain(data.iter()) .cloned() @@ -73,12 +71,12 @@ impl From for FastTransferOrder { let recipient = HexBinary::from(value[32..64].to_vec()); let amount_in = Uint128::new(u128::from_be_bytes(value[80..96].try_into().unwrap())); let amount_out = Uint128::new(u128::from_be_bytes(value[112..128].try_into().unwrap())); - let nonce = u32::from_be_bytes(value[156..160].try_into().unwrap()); - let source_domain = u32::from_be_bytes(value[160..164].try_into().unwrap()); - let destination_domain = u32::from_be_bytes(value[164..168].try_into().unwrap()); - let timeout_timestamp = u64::from_be_bytes(value[192..200].try_into().unwrap()); - let data = if value.len() > 200 { - Some(HexBinary::from(value[200..].to_vec())) + let nonce = u32::from_be_bytes(value[128..132].try_into().unwrap()); + let source_domain = u32::from_be_bytes(value[132..136].try_into().unwrap()); + let destination_domain = u32::from_be_bytes(value[136..140].try_into().unwrap()); + let timeout_timestamp = u64::from_be_bytes(value[140..148].try_into().unwrap()); + let data = if value.len() > 148 { + Some(HexBinary::from(value[148..].to_vec())) } else { None }; diff --git a/solidity/script/7683/SubmitOrder.s.sol b/solidity/script/7683/SubmitOrder.s.sol index 2e7788e..853c5ea 100644 --- a/solidity/script/7683/SubmitOrder.s.sol +++ b/solidity/script/7683/SubmitOrder.s.sol @@ -41,7 +41,7 @@ contract DeployScript is Script { amount, // amountOut localDomain, // sourceDomain destinationDomain, // destinationDomain - block.timestamp + 1 days, // timeoutTimestamp + uint64(block.timestamp + 1 days), // timeoutTimestamp data ) ) diff --git a/solidity/script/SubmitOrder.s.sol b/solidity/script/SubmitOrder.s.sol index e6c965e..30c1611 100644 --- a/solidity/script/SubmitOrder.s.sol +++ b/solidity/script/SubmitOrder.s.sol @@ -28,7 +28,7 @@ contract DeployScript is Script { amount, amount, destinationDomain, - block.timestamp + 1 days, + uint64(block.timestamp + 1 days), data ); diff --git a/solidity/src/FastTransferGateway.sol b/solidity/src/FastTransferGateway.sol index a22cc9d..41275ea 100644 --- a/solidity/src/FastTransferGateway.sol +++ b/solidity/src/FastTransferGateway.sol @@ -42,13 +42,13 @@ struct FastTransferOrder { // The amount of tokens the user is receiving on the destination domain uint256 amountOut; // Nonce of the order - uint256 nonce; + uint32 nonce; // Source domain of the order uint32 sourceDomain; // Destination domain of the order uint32 destinationDomain; // Deadline that the order must be filled on the destination domain by - uint256 timeoutTimestamp; + uint64 timeoutTimestamp; // Optional calldata passed on to the recipient on the destination domain when the order is filled bytes data; } @@ -162,7 +162,7 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab uint256 amountIn, uint256 amountOut, uint32 destinationDomain, - uint256 timeoutTimestamp, + uint64 timeoutTimestamp, bytes calldata data ) public returns (bytes32) { FastTransferOrder memory order = FastTransferOrder( @@ -203,7 +203,7 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab uint256 amountIn, uint256 amountOut, uint32 destinationDomain, - uint256 timeoutTimestamp, + uint64 timeoutTimestamp, uint256 permitDeadline, bytes calldata data, bytes calldata signature @@ -470,7 +470,7 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab emit OrderRefunded(orderID); } - function _permitTransferFrom(uint256 amount, uint256 deadline, uint256 orderNonce, bytes calldata signature) + function _permitTransferFrom(uint256 amount, uint256 deadline, uint32 orderNonce, bytes calldata signature) internal { PERMIT2.permitTransferFrom( diff --git a/solidity/src/GoFastERC7683.sol b/solidity/src/GoFastERC7683.sol index 5a42d0f..d3b25e1 100644 --- a/solidity/src/GoFastERC7683.sol +++ b/solidity/src/GoFastERC7683.sol @@ -32,7 +32,7 @@ struct OrderData { uint256 amountOut; uint32 sourceDomain; uint32 destinationDomain; - uint256 timeoutTimestamp; + uint64 timeoutTimestamp; bytes data; } @@ -52,7 +52,7 @@ contract GoFastERC7683 is Initializable, UUPSUpgradeable, OwnableUpgradeable, Re "uint256 amountOut,", "uint32 sourceDomain,", "uint32 destinationDomain,", - "uint256 timeoutTimestamp,", + "uint64 timeoutTimestamp,", "bytes data)" ); @@ -109,7 +109,7 @@ contract GoFastERC7683 is Initializable, UUPSUpgradeable, OwnableUpgradeable, Re address inputToken = TypeCasts.bytes32ToAddress(bytes32(orderData.inputToken)); - _permitTransferFrom(inputToken, orderData.amountIn, order.openDeadline, order.nonce, signature); + _permitTransferFrom(inputToken, orderData.amountIn, order.openDeadline, uint32(order.nonce), signature); IERC20(inputToken).approve(gateway, orderData.amountIn); @@ -247,7 +247,7 @@ contract GoFastERC7683 is Initializable, UUPSUpgradeable, OwnableUpgradeable, Re address token, uint256 amount, uint256 deadline, - uint256 nonce, + uint32 nonce, bytes calldata signature ) internal { PERMIT2.permitTransferFrom( diff --git a/solidity/src/libraries/OrderEncoder.sol b/solidity/src/libraries/OrderEncoder.sol index ccdf111..b07f657 100644 --- a/solidity/src/libraries/OrderEncoder.sol +++ b/solidity/src/libraries/OrderEncoder.sol @@ -28,11 +28,11 @@ library OrderEncoder { order.recipient = bytes32(orderBytes[32:64]); order.amountIn = uint256(bytes32(orderBytes[64:96])); order.amountOut = uint256(bytes32(orderBytes[96:128])); - order.nonce = uint256(bytes32(orderBytes[128:160])); - order.sourceDomain = uint32(bytes4(orderBytes[160:164])); - order.destinationDomain = uint32(bytes4(orderBytes[164:168])); - order.timeoutTimestamp = uint256(bytes32(orderBytes[168:200])); - order.data = bytes(orderBytes[200:]); + order.nonce = uint32(bytes4(orderBytes[128:132])); + order.sourceDomain = uint32(bytes4(orderBytes[132:136])); + order.destinationDomain = uint32(bytes4(orderBytes[136:140])); + order.timeoutTimestamp = uint64(bytes8(orderBytes[140:148])); + order.data = bytes(orderBytes[148:]); return order; } diff --git a/solidity/test/ERC7683.t.sol b/solidity/test/ERC7683.t.sol index f36631c..b37cd20 100644 --- a/solidity/test/ERC7683.t.sol +++ b/solidity/test/ERC7683.t.sol @@ -107,7 +107,7 @@ contract ERC7683Test is Test { amountOut, // amountOut 1, // sourceDomain 2, // destinationDomain - block.timestamp + 1 days, // timeoutTimestamp + uint64(block.timestamp + 1 days), // timeoutTimestamp bytes("") // data ) ) @@ -170,7 +170,7 @@ contract ERC7683Test is Test { amountOut, // amountOut 1, // sourceDomain 2, // destinationDomain - block.timestamp + 1 days, // timeoutTimestamp + uint64(block.timestamp + 1 days), // timeoutTimestamp bytes("") // data ) ) @@ -213,7 +213,7 @@ contract ERC7683Test is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); diff --git a/solidity/test/FastTransferGateway.t.sol b/solidity/test/FastTransferGateway.t.sol index 0cd2938..92cd2b3 100644 --- a/solidity/test/FastTransferGateway.t.sol +++ b/solidity/test/FastTransferGateway.t.sol @@ -198,7 +198,7 @@ contract FastTransferGatewayTest is Test { amountIn, amountOut, 3, - block.timestamp + 1 days, + uint64(block.timestamp + 1 days), bytes("") ); @@ -406,8 +406,8 @@ contract FastTransferGatewayTest is Test { amountIn, amountOut, destinationDomain, - block.timestamp + 1 days, - block.timestamp + 1 days, + uint64(block.timestamp + 1 days), + uint64(block.timestamp + 1 days), bytes(""), sig ); @@ -440,7 +440,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -486,7 +486,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: abi.encodeWithSelector( uniswapV2Router.swapExactTokensForTokens.selector, amountOut, @@ -546,7 +546,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: data }); @@ -584,7 +584,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: data }); @@ -624,7 +624,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: data }); @@ -659,7 +659,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp - 1 days, + timeoutTimestamp: uint64(block.timestamp - 1 days), data: bytes("") }); @@ -685,7 +685,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -697,7 +697,7 @@ contract FastTransferGatewayTest is Test { nonce: 2, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -736,7 +736,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -748,7 +748,7 @@ contract FastTransferGatewayTest is Test { nonce: 2, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -792,7 +792,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -804,7 +804,7 @@ contract FastTransferGatewayTest is Test { nonce: 2, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -856,7 +856,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -868,7 +868,7 @@ contract FastTransferGatewayTest is Test { nonce: 2, sourceDomain: 1, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -908,7 +908,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 days, + timeoutTimestamp: uint64(block.timestamp + 1 days), data: bytes("") }); @@ -947,7 +947,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp - 1 hours, + timeoutTimestamp: uint64(block.timestamp - 1 hours), data: bytes("") }); @@ -978,7 +978,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 hours, + timeoutTimestamp: uint64(block.timestamp + 1 hours), data: bytes("") }); @@ -1010,7 +1010,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp + 1 hours, + timeoutTimestamp: uint64(block.timestamp + 1 hours), data: bytes("") }); @@ -1049,7 +1049,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 1, - timeoutTimestamp: block.timestamp - 1 hours, + timeoutTimestamp: uint64(block.timestamp - 1 hours), data: bytes("") }); @@ -1061,7 +1061,7 @@ contract FastTransferGatewayTest is Test { nonce: 2, sourceDomain: 1, destinationDomain: 1, - timeoutTimestamp: block.timestamp - 1 hours, + timeoutTimestamp: uint64(block.timestamp - 1 hours), data: bytes("") }); @@ -1094,7 +1094,7 @@ contract FastTransferGatewayTest is Test { nonce: 1, sourceDomain: sourceDomain, destinationDomain: 3, - timeoutTimestamp: block.timestamp - 1 hours, + timeoutTimestamp: uint64(block.timestamp - 1 hours), data: bytes("") }); @@ -1128,7 +1128,7 @@ contract FastTransferGatewayTest is Test { amountIn, amountOut, destinationDomain, - block.timestamp + 1 days, + uint64(block.timestamp + 1 days), data ); From 0b90eba94ad0237af9507fdff0c1744a70aa707d Mon Sep 17 00:00:00 2001 From: thal0x Date: Tue, 29 Oct 2024 06:12:43 -0500 Subject: [PATCH 08/13] use forceApprove for erc20 token allowances --- solidity/src/GoFastMulticall.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/solidity/src/GoFastMulticall.sol b/solidity/src/GoFastMulticall.sol index b7a0fe1..46522e9 100644 --- a/solidity/src/GoFastMulticall.sol +++ b/solidity/src/GoFastMulticall.sol @@ -2,13 +2,16 @@ pragma solidity ^0.8.13; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract GoFastCaller { + using SafeERC20 for IERC20; + function execute(address _target, address _token, uint256 _amount, bytes memory _data) external returns (bool, bytes memory) { - IERC20(_token).approve(_target, _amount); + IERC20(_token).forceApprove(_target, _amount); (bool success, bytes memory returnData) = _target.call(_data); if (!success) { From b01d1cd34ffadc834e930bb2bf41c7b12e216773 Mon Sep 17 00:00:00 2001 From: thal0x Date: Tue, 29 Oct 2024 06:17:00 -0500 Subject: [PATCH 09/13] make evm and cosmwasm logic consistent for timeout checks --- .../fast-transfer-gateway/src/helpers.rs | 6 +-- .../tests/test_fill_order.rs | 41 +++++++++++++++++++ solidity/test/FastTransferGateway.t.sol | 30 ++++++++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/cosmwasm/contracts/fast-transfer-gateway/src/helpers.rs b/cosmwasm/contracts/fast-transfer-gateway/src/helpers.rs index d448c53..2177937 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/src/helpers.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/src/helpers.rs @@ -48,12 +48,8 @@ pub fn assert_order_is_expired(env: &Env, order: &FastTransferOrder) -> Contract } pub fn assert_order_is_not_expired(env: &Env, order: &FastTransferOrder) -> ContractResult<()> { - if order.timeout_timestamp == 0 { - return Ok(()); - } - let timeout_timestamp = Timestamp::from_seconds(order.timeout_timestamp); - if env.block.time.seconds() > timeout_timestamp.seconds() { + if env.block.time.seconds() >= timeout_timestamp.seconds() { return Err(ContractError::OrderTimedOut); } diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs index ad24873..2560b2b 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs @@ -522,3 +522,44 @@ fn test_fill_order_fails_on_timed_out_order() { assert_eq!(res, "Order timed out"); } + +#[test] +fn test_fill_order_fails_on_timed_out_order_exact() { + let (mut deps, env) = default_instantiate(); + + let user_address = deps.api.with_prefix("osmo").addr_make("user"); + + let order = FastTransferOrder { + sender: HexBinary::from(left_pad_bytes( + bech32_decode(user_address.as_str()).unwrap(), + 32, + )), + recipient: HexBinary::from(left_pad_bytes( + bech32_decode(user_address.as_str()).unwrap(), + 32, + )), + amount_in: Uint128::new(100_000_000), + amount_out: Uint128::new(98_000_000), + nonce: 1, + source_domain: 2, + destination_domain: 1, + timeout_timestamp: env.block.time.seconds(), + data: None, + }; + + let execute_msg = ExecuteMsg::FillOrder { + filler: Addr::unchecked("solver"), + order: order.clone(), + }; + + let res = go_fast_transfer_cw::contract::execute( + deps.as_mut(), + env.clone(), + mock_info("solver", &[coin(order.amount_out.u128(), "uusdc")]), + execute_msg.clone(), + ) + .unwrap_err() + .to_string(); + + assert_eq!(res, "Order timed out"); +} diff --git a/solidity/test/FastTransferGateway.t.sol b/solidity/test/FastTransferGateway.t.sol index 92cd2b3..20f8483 100644 --- a/solidity/test/FastTransferGateway.t.sol +++ b/solidity/test/FastTransferGateway.t.sol @@ -671,6 +671,36 @@ contract FastTransferGatewayTest is Test { vm.stopPrank(); } + function test_revertFillOrderWhenOrderExpiredExact() public { + uint256 amountIn = 100_000000; + uint256 amountOut = 98_000000; + uint32 sourceDomain = 1; + bytes32 sourceContract = TypeCasts.addressToBytes32(address(0xB)); + + deal(address(usdc), solver, amountOut, true); + + gateway.setRemoteDomain(sourceDomain, sourceContract); + + FastTransferOrder memory order = FastTransferOrder({ + sender: TypeCasts.addressToBytes32(address(0xB)), + recipient: TypeCasts.addressToBytes32(address(0xC)), + amountIn: amountIn, + amountOut: amountOut, + nonce: 1, + sourceDomain: sourceDomain, + destinationDomain: 1, + timeoutTimestamp: uint64(block.timestamp), + data: bytes("") + }); + + vm.startPrank(solver); + usdc.approve(address(gateway), amountOut); + + vm.expectRevert("FastTransferGateway: order expired"); + gateway.fillOrder(solver, order); + vm.stopPrank(); + } + function test_initiateSettlement() public { uint32 sourceDomain = 8453; bytes32 sourceContract = TypeCasts.addressToBytes32(address(0xB)); From d970de8f18ef8cbf60041f7cd780357461b492d4 Mon Sep 17 00:00:00 2001 From: thal0x Date: Tue, 29 Oct 2024 11:16:46 -0500 Subject: [PATCH 10/13] address clippy issue --- .../contracts/fast-transfer-gateway/tests/test_fill_order.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs b/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs index 2560b2b..28e79be 100644 --- a/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs +++ b/cosmwasm/contracts/fast-transfer-gateway/tests/test_fill_order.rs @@ -177,7 +177,7 @@ fn test_fill_order_fails_when_order_recipient_is_mailbox() { let user_address = deps.api.with_prefix("osmo").addr_make("user"); let test_payload = to_json_binary(&BankMsg::Send { - to_address: "solver".to_string().into(), + to_address: "solver".to_string(), amount: vec![coin(98_000_000, "uusdc")], }) .unwrap(); From 733fb25544dd811261544812aeb2db52f47343fd Mon Sep 17 00:00:00 2001 From: thal0x Date: Wed, 30 Oct 2024 10:14:18 -0500 Subject: [PATCH 11/13] remove second loop in the EVM settleOrders implementation --- solidity/src/FastTransferGateway.sol | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/solidity/src/FastTransferGateway.sol b/solidity/src/FastTransferGateway.sol index 41275ea..d461906 100644 --- a/solidity/src/FastTransferGateway.sol +++ b/solidity/src/FastTransferGateway.sol @@ -416,19 +416,6 @@ contract FastTransferGateway is Initializable, UUPSUpgradeable, OwnableUpgradeab ); amountToRepay += orderSettlementDetails.amount; - } - - // effects - for (uint256 pos = 0; pos < orderIDs.length; pos += 32) { - bytes32 orderID; - assembly { - orderID := mload(add(orderIDs, add(0x20, pos))) - } - - if (orderStatuses[orderID] != OrderStatus.UNFILLED) { - emit OrderAlreadySettled(orderID); - continue; - } orderStatuses[orderID] = OrderStatus.FILLED; emit OrderSettled(orderID); From f9ccbef5e118bde6cb0335904d4c9122887c792f Mon Sep 17 00:00:00 2001 From: thal0x Date: Thu, 31 Oct 2024 06:15:14 -0500 Subject: [PATCH 12/13] forge install: skip-go-evm-contracts --- .gitmodules | 3 +++ solidity/lib/skip-go-evm-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 solidity/lib/skip-go-evm-contracts diff --git a/.gitmodules b/.gitmodules index 8730be1..aa8dcce 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "solidity/lib/openzeppelin-contracts-upgradeable"] path = solidity/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "solidity/lib/skip-go-evm-contracts"] + path = solidity/lib/skip-go-evm-contracts + url = https://github.com/skip-mev/skip-go-evm-contracts diff --git a/solidity/lib/skip-go-evm-contracts b/solidity/lib/skip-go-evm-contracts new file mode 160000 index 0000000..a95690a --- /dev/null +++ b/solidity/lib/skip-go-evm-contracts @@ -0,0 +1 @@ +Subproject commit a95690a60023ba54a28b24b8442b5a896086ed5a From f12611f4df0bc981fb32d5549e4d0155a578010e Mon Sep 17 00:00:00 2001 From: thal0x Date: Wed, 6 Nov 2024 08:48:06 -0600 Subject: [PATCH 13/13] only allow gateway as caller in GoFastCaller --- solidity/src/FastTransferGateway.sol | 3 ++- .../{GoFastMulticall.sol => GoFastCaller.sol} | 18 +++++++++++++++++- solidity/test/ERC7683.t.sol | 6 ++++-- solidity/test/FastTransferGateway.t.sol | 6 ++++-- 4 files changed, 27 insertions(+), 6 deletions(-) rename solidity/src/{GoFastMulticall.sol => GoFastCaller.sol} (60%) diff --git a/solidity/src/FastTransferGateway.sol b/solidity/src/FastTransferGateway.sol index d461906..090561f 100644 --- a/solidity/src/FastTransferGateway.sol +++ b/solidity/src/FastTransferGateway.sol @@ -16,7 +16,8 @@ import {OrderEncoder} from "./libraries/OrderEncoder.sol"; import {IPermit2} from "./interfaces/IPermit2.sol"; import {IMailbox} from "./interfaces/hyperlane/IMailbox.sol"; -import {GoFastCaller} from "./GoFastMulticall.sol"; +import {GoFastCaller} from "./GoFastCaller.sol"; + // Structure that contains the order details required to settle or refund an order struct SettlementDetails { diff --git a/solidity/src/GoFastMulticall.sol b/solidity/src/GoFastCaller.sol similarity index 60% rename from solidity/src/GoFastMulticall.sol rename to solidity/src/GoFastCaller.sol index 46522e9..44dd486 100644 --- a/solidity/src/GoFastMulticall.sol +++ b/solidity/src/GoFastCaller.sol @@ -3,12 +3,28 @@ pragma solidity ^0.8.13; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -contract GoFastCaller { +contract GoFastCaller is Ownable { using SafeERC20 for IERC20; + address public gateway; + + constructor(address _owner) Ownable(_owner) {} + + function setGateway(address _gateway) external onlyOwner { + gateway = _gateway; + } + + modifier onlyGateway() { + require(gateway != address(0), "GoFastCaller: gateway not set"); + require(msg.sender == gateway, "GoFastCaller: sender not gateway"); + _; + } + function execute(address _target, address _token, uint256 _amount, bytes memory _data) external + onlyGateway returns (bool, bytes memory) { IERC20(_token).forceApprove(_target, _amount); diff --git a/solidity/test/ERC7683.t.sol b/solidity/test/ERC7683.t.sol index b37cd20..74f03cd 100644 --- a/solidity/test/ERC7683.t.sol +++ b/solidity/test/ERC7683.t.sol @@ -12,7 +12,7 @@ import {IPermit2} from "../src/interfaces/IPermit2.sol"; import {OnchainCrossChainOrder, GaslessCrossChainOrder} from "../src/erc7683/ERC7683.sol"; import {GoFastERC7683, OrderData} from "../src/GoFastERC7683.sol"; import {OrderEncoder} from "../src/libraries/OrderEncoder.sol"; -import {GoFastCaller} from "../src/GoFastMulticall.sol"; +import {GoFastCaller} from "../src/GoFastCaller.sol"; interface IUniswapV2Router02 { function swapExactTokensForTokens( @@ -56,7 +56,7 @@ contract ERC7683Test is Test { solver = address(2); mailbox = address(0x979Ca5202784112f4738403dBec5D0F3B9daabB9); - GoFastCaller goFastCaller = new GoFastCaller(); + GoFastCaller goFastCaller = new GoFastCaller(address(this)); FastTransferGateway gatewayImpl = new FastTransferGateway(); ERC1967Proxy gatewayProxy = new ERC1967Proxy( @@ -74,6 +74,8 @@ contract ERC7683Test is Test { ); gateway = FastTransferGateway(address(gatewayProxy)); + goFastCaller.setGateway(address(gateway)); + GoFastERC7683 goFastERC7683Impl = new GoFastERC7683(); ERC1967Proxy goFastERC7683Proxy = new ERC1967Proxy( address(goFastERC7683Impl), diff --git a/solidity/test/FastTransferGateway.t.sol b/solidity/test/FastTransferGateway.t.sol index 20f8483..197981b 100644 --- a/solidity/test/FastTransferGateway.t.sol +++ b/solidity/test/FastTransferGateway.t.sol @@ -11,7 +11,7 @@ import {TypeCasts} from "../src/libraries/TypeCasts.sol"; import {OrderEncoder} from "../src/libraries/OrderEncoder.sol"; import {IPermit2} from "../src/interfaces/IPermit2.sol"; import {IMailbox} from "../src/interfaces/hyperlane/IMailbox.sol"; -import {GoFastCaller} from "../src/GoFastMulticall.sol"; +import {GoFastCaller} from "../src/GoFastCaller.sol"; interface IUniswapV2Router02 { function swapExactTokensForTokens( @@ -55,7 +55,7 @@ contract FastTransferGatewayTest is Test { solver = address(2); mailbox = address(0x979Ca5202784112f4738403dBec5D0F3B9daabB9); - GoFastCaller _goFastCaller = new GoFastCaller(); + GoFastCaller _goFastCaller = new GoFastCaller(address(this)); goFastCaller = address(_goFastCaller); FastTransferGateway gatewayImpl = new FastTransferGateway(); ERC1967Proxy gatewayProxy = new ERC1967Proxy( @@ -72,6 +72,8 @@ contract FastTransferGatewayTest is Test { ) ); gateway = FastTransferGateway(address(gatewayProxy)); + + _goFastCaller.setGateway(address(gateway)); } function test_submitAndSettle() public {