├── deployments ├── bnb │ └── .chainId ├── xdai │ └── .chainId ├── hardhat │ └── .chainId ├── optimism │ └── .chainId ├── polygon │ └── .chainId └── arbitrum │ └── .chainId ├── .yarnrc.yml ├── .prettierrc ├── .env.example ├── foundry.toml ├── tsconfig.json ├── contracts ├── integration │ ├── Instadapp │ │ ├── interfaces │ │ │ └── IDSA.sol │ │ ├── InstadappTarget.sol │ │ └── InstadappAdapter.sol │ ├── LockboxAdapterBlast.sol │ └── LockboxAdapter.sol ├── shared │ ├── Swap │ │ ├── interfaces │ │ │ ├── IWETH9.sol │ │ │ ├── ISwapper.sol │ │ │ ├── ISmartRouter.sol │ │ │ └── IPancakeSmartRouter.sol │ │ ├── libraries │ │ │ └── BytesLib.sol │ │ ├── SwapAdapter.sol │ │ ├── Uniswap │ │ │ ├── UniV2Swapper.sol │ │ │ └── UniV3Swapper.sol │ │ ├── OneInch │ │ │ └── OneInchUniswapV3.sol │ │ └── Pancakeswap │ │ │ └── PancakeV3Swapper.sol │ ├── ownership │ │ ├── ProposedOwnableUpgradeable.sol │ │ └── ProposedOwnable.sol │ └── IXERC20 │ │ ├── IXERC20Lockbox.sol │ │ └── IXERC20.sol ├── test │ ├── interfaces │ │ └── IBridgeToken.sol │ ├── utils │ │ ├── Greeter.sol │ │ └── TestHelper.sol │ ├── TestERC20.sol │ └── unit │ │ ├── shared │ │ └── Swap │ │ │ ├── OneInch │ │ │ └── OneInchUniswapV3.t.sol │ │ │ └── SwapAdapter.t.sol │ │ └── integration │ │ └── Instadapp │ │ ├── InstadappTarget.t.sol │ │ └── InstadappAdapter.t.sol ├── example │ └── XSwapAndGreet │ │ └── XSwapAndGreetTarget.sol ├── destination │ └── xreceivers │ │ ├── Swap │ │ └── SwapForwarderXReceiver.sol │ │ └── ForwarderXReceiver.sol ├── origin │ └── Swap │ │ ├── ExampleSource.sol │ │ └── SwapAndXCall.sol └── xtoken │ └── XERC20Upgradeable.sol ├── test ├── instadapp │ ├── InstadappTarget.spec.ts │ └── InstaTargetAuth.spec.ts ├── helpers │ ├── index.ts │ └── abis │ │ └── instaindex.json ├── cli.ts ├── meanFinance │ ├── MeanFinanceAdapter.spec.ts │ ├── MeanFinanceTarget.spec.ts │ └── MeanFinanceSource.spec.ts ├── swapAdapter │ └── SwapAdapter.spec.ts └── uniswap │ └── UniswapAdapter.spec.ts ├── .vscode ├── extensions.json ├── settings.json ├── launch.json └── tasks.json ├── script ├── DeployLockboxAdapterBlast.s.sol └── DeployLockboxAdapter.s.sol ├── .gitignore ├── .gitmodules ├── deploy ├── export.ts ├── univ2swapper.ts ├── swapandxcall.ts ├── MeanFinance │ ├── deploy_source.ts │ └── deploy_target.ts ├── MidasProtocol │ └── index.ts ├── univ3swapper.ts └── index.ts ├── .github └── workflows │ └── build-and-test.yml ├── tasks ├── addSwapper.ts ├── removeSwapper.ts └── xdeposit.ts ├── README.md ├── utils └── interval-utils.ts ├── package.json ├── hardhat.config.ts └── broadcast ├── DeployLockboxAdapterBlast.s.sol └── 1 │ └── run-latest.json └── DeployLockboxAdapter.s.sol └── 1 └── run-latest.json /deployments/bnb/.chainId: -------------------------------------------------------------------------------- 1 | 56 -------------------------------------------------------------------------------- /deployments/xdai/.chainId: -------------------------------------------------------------------------------- 1 | 100 -------------------------------------------------------------------------------- /deployments/hardhat/.chainId: -------------------------------------------------------------------------------- 1 | 31337 -------------------------------------------------------------------------------- /deployments/optimism/.chainId: -------------------------------------------------------------------------------- 1 | 10 -------------------------------------------------------------------------------- /deployments/polygon/.chainId: -------------------------------------------------------------------------------- 1 | 137 -------------------------------------------------------------------------------- /deployments/arbitrum/.chainId: -------------------------------------------------------------------------------- 1 | 42161 -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.4.1.cjs 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "semi": true, 4 | "singleQuote": false, 5 | "printWidth": 120, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC= 2 | ETHERSCAN_API_KEY= 3 | OPTIMISM_ETHERSCAN_API_KEY= # optional, otherwise verifying fails 4 | POLYGONSCAN_API_KEY= # optional, otherwise verifying fails 5 | ARBISCAN_API_KEY= # optional, otherwise verifying fails -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | libs = ['node_modules', 'lib'] 5 | test = 'test' 6 | cache_path = 'cache_forge' 7 | solc = "0.8.20" 8 | via_ir = true 9 | 10 | remappings = [ 11 | '@defi-wonderland/xerc20/=lib/xerc20/' 12 | ] -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/integration/Instadapp/interfaces/IDSA.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IDSA { 5 | function cast( 6 | string[] calldata _targetNames, 7 | bytes[] calldata _datas, 8 | address _origin 9 | ) external payable returns (bytes32); 10 | 11 | function isAuth(address user) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /test/instadapp/InstadappTarget.spec.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | const deployInstaTarget = async (connextAddress: string, authority: string) => { 4 | const instaTagetFactory = await ethers.getContractFactory("InstadappTarget"); 5 | const contractInstance = await instaTagetFactory.deploy(connextAddress, authority); 6 | await contractInstance.deployed(); 7 | return contractInstance 8 | } -------------------------------------------------------------------------------- /contracts/shared/Swap/interfaces/IWETH9.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.17; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | /// @title Interface for WETH9 7 | interface IWETH9 is IERC20 { 8 | /// @notice Deposit ether to get wrapped ether 9 | function deposit() external payable; 10 | 11 | /// @notice Withdraw wrapped ether to get ether 12 | function withdraw(uint256) external; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/test/interfaces/IBridgeToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 5 | 6 | interface IBridgeToken is IERC20Metadata { 7 | function burn(address _from, uint256 _amnt) external; 8 | 9 | function mint(address _to, uint256 _amnt) external; 10 | 11 | function setDetails(string calldata _name, string calldata _symbol) external; 12 | } 13 | -------------------------------------------------------------------------------- /contracts/shared/Swap/interfaces/ISwapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | pragma solidity ^0.8.17; 4 | 5 | interface ISwapper { 6 | function swap( 7 | uint256 _amountIn, 8 | address _tokenIn, 9 | address _tokenOut, 10 | bytes calldata _swapData 11 | ) external returns (uint256 amountOut); 12 | 13 | function swapETH( 14 | uint256 _amountIn, 15 | address _tokenOut, 16 | bytes calldata _swapData 17 | ) external payable returns (uint256 amountOut); 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"], 7 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 8 | "unwantedRecommendations": [] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "[typescript]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | }, 7 | "[solidity]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | "[terraform]": { 11 | "editor.defaultFormatter": "hashicorp.terraform" 12 | }, 13 | "[sql]": { 14 | "editor.defaultFormatter": "mtxr.sqltools" 15 | }, 16 | "solidity.compileUsingRemoteVersion": "v0.8.17+commit.8df45f5f" 17 | } 18 | -------------------------------------------------------------------------------- /contracts/test/utils/Greeter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.19; 3 | 4 | import {IGreeter} from "../../example/XSwapAndGreet/XSwapAndGreetTarget.sol"; 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | contract Greeter is IGreeter { 8 | string public greeting; 9 | 10 | function greetWithTokens(address _token, uint256 _amount, string calldata _greeting) external override { 11 | IERC20(_token).transferFrom(msg.sender, address(this), _amount); 12 | greeting = _greeting; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /script/DeployLockboxAdapterBlast.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import {LockboxAdapterBlast} from "../contracts/integration/LockboxAdapterBlast.sol"; 6 | 7 | contract DeployLockboxAdapterBlast is Script { 8 | function run() public { 9 | vm.startBroadcast(); 10 | 11 | new LockboxAdapterBlast( 12 | address(0x697402166Fbf2F22E970df8a6486Ef171dbfc524), // blastStandardBridge 13 | address(0xBf29A2D67eFb6766E44c163B19C6F4118b164702) // registry 14 | ); 15 | 16 | vm.stopBroadcast(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/shared/Swap/interfaces/ISmartRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.17; 3 | pragma abicoder v2; 4 | 5 | import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; 6 | import {IPeripheryPayments} from "@uniswap/v3-periphery/contracts/interfaces/IPeripheryPayments.sol"; 7 | import {IPeripheryImmutableState} from "@uniswap/v3-periphery/contracts/interfaces/IPeripheryImmutableState.sol"; 8 | 9 | /// @title Router token swapping functionality 10 | interface ISmartRouter is ISwapRouter, IPeripheryPayments, IPeripheryImmutableState { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # directories 2 | .yarn/* 3 | !.yarn/patches 4 | !.yarn/releases 5 | !.yarn/plugins 6 | !.yarn/sdks 7 | !.yarn/versions 8 | **/cache 9 | **/node_modules 10 | **/out 11 | 12 | # files 13 | *.env 14 | *.log 15 | .DS_Store 16 | .pnp.* 17 | lcov.info 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | # Keep the latest deployment only 22 | broadcast/* 23 | !broadcast/**/ 24 | broadcast/**/*.json 25 | !broadcast/**/run-latest.json 26 | 27 | node_modules 28 | .env 29 | coverage 30 | coverage.json 31 | typechain 32 | typechain-types 33 | 34 | # Hardhat files 35 | cache 36 | artifacts 37 | deployments/*/solcInputs 38 | artifacts_forge 39 | cache_forge/ 40 | -------------------------------------------------------------------------------- /script/DeployLockboxAdapter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import {LockboxAdapter} from "../contracts/integration/LockboxAdapter.sol"; 6 | 7 | contract DeployLockboxAdapter is Script { 8 | function run() public { 9 | // Retrieve the addresses from the command line arguments 10 | address connext = vm.envAddress("CONNEXT"); 11 | address registry = vm.envAddress("REGISTRY"); 12 | uint256 deployer = vm.envUint("DEPLOYER_PRIVATE_KEY"); 13 | 14 | vm.startBroadcast(deployer); 15 | 16 | new LockboxAdapter(connext, registry); 17 | 18 | vm.stopBroadcast(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "hello_foundry/lib/forge-std"] 2 | path = hello_foundry/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | branch = v1.5.1 8 | [submodule "lib/vulcan"] 9 | path = lib/vulcan 10 | url = https://github.com/nomoixyz/vulcan 11 | branch = alpha-1 12 | [submodule "lib/connext-interfaces"] 13 | path = lib/connext-interfaces 14 | url = https://github.com/connext/interfaces 15 | [submodule "lib/openzeppelin-contracts-upgradeable"] 16 | path = lib/openzeppelin-contracts-upgradeable 17 | url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable 18 | [submodule "lib/xerc20"] 19 | path = lib/xerc20 20 | url = https://github.com/defi-wonderland/xerc20 21 | -------------------------------------------------------------------------------- /deploy/export.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { DeployFunction } from "hardhat-deploy/types"; 3 | 4 | /** 5 | * Hardhat task defining the contract deployments for Connext 6 | * 7 | * @param hre Hardhat environment to deploy to 8 | */ 9 | const func: DeployFunction = async ( 10 | hre: HardhatRuntimeEnvironment 11 | ): Promise => { 12 | console.log( 13 | "\n============================= Exporting + Verifying Deployments ===============================" 14 | ); 15 | await hre.run("export", { 16 | exportAll: "./deployments.json", 17 | }); 18 | 19 | // wait 10s for explorer db 20 | await new Promise((resolve) => setTimeout(() => resolve(), 10_000)); 21 | 22 | await hre.run("etherscan-verify", { 23 | solcInput: true, 24 | }); 25 | }; 26 | 27 | export default func; 28 | func.tags = ["export", "prod"]; 29 | -------------------------------------------------------------------------------- /contracts/shared/ownership/ProposedOwnableUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {Initializable} from "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; 5 | 6 | import {ProposedOwnable} from "./ProposedOwnable.sol"; 7 | 8 | abstract contract ProposedOwnableUpgradeable is Initializable, ProposedOwnable { 9 | /** 10 | * @dev Initializes the contract setting the deployer as the initial 11 | */ 12 | function __ProposedOwnable_init() internal onlyInitializing { 13 | __ProposedOwnable_init_unchained(); 14 | } 15 | 16 | function __ProposedOwnable_init_unchained() internal onlyInitializing { 17 | _setOwner(msg.sender); 18 | } 19 | 20 | /** 21 | * @dev This empty reserved space is put in place to allow future versions to add new 22 | * variables without shifting down storage in the inheritance chain. 23 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 24 | */ 25 | uint256[47] private __GAP; 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build And Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | check: 9 | strategy: 10 | fail-fast: true 11 | 12 | name: CI/CD 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: onbjerg/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Setup Node 26 | uses: actions/setup-node@v2 27 | with: 28 | node-version: "18" 29 | cache: "yarn" 30 | 31 | - name: Yarn install 32 | run: yarn install 33 | 34 | - name: Forge install 35 | run: yarn forge:install 36 | 37 | - name: Forge build 38 | run: yarn forge:build 39 | 40 | - name: Run Forge tests 41 | env: 42 | OPTIMISM_RPC_URL: ${{ secrets.OPTIMISM_RPC_URL }} 43 | ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }} 44 | POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }} 45 | run: yarn forge:test 46 | -------------------------------------------------------------------------------- /test/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { 3 | BigNumberish, 4 | constants, 5 | Contract, 6 | providers, 7 | Wallet, 8 | } from "ethers"; 9 | import { ERC20_ABI } from "@0xgafu/common-abi"; 10 | 11 | export const deploy = async (contractName: string) => { 12 | // Contracts are deployed using the first signer/account by default 13 | const [owner, otherAccount] = await ethers.getSigners(); 14 | const contract = await ethers.getContractFactory(contractName); 15 | const instance = await contract.deploy(); 16 | await instance.deployed(); 17 | 18 | return { instance, owner, otherAccount }; 19 | }; 20 | 21 | export const fund = async ( 22 | asset: string, 23 | wei: BigNumberish, 24 | from: Wallet, 25 | to: string 26 | ): Promise => { 27 | if (asset === constants.AddressZero) { 28 | const tx = await from.sendTransaction({ to, value: wei }); 29 | // send eth 30 | return await tx.wait(); 31 | } 32 | 33 | // send tokens 34 | const token = new Contract(asset, ERC20_ABI, from); 35 | const tx = await token.transfer(to, wei); 36 | return await tx.wait(); 37 | }; 38 | 39 | export { ERC20_ABI }; 40 | 41 | 42 | -------------------------------------------------------------------------------- /tasks/addSwapper.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "ethers"; 2 | import { task } from "hardhat/config"; 3 | 4 | type TaskArgs = { 5 | target?: string; 6 | swapper?: string; 7 | }; 8 | 9 | export default task("add-swapper", "Add swapper contract address to Midas Target") 10 | .addOptionalParam("target", "The address of the Midas Target") 11 | .addOptionalParam("swapper", "The address of the swapper") 12 | .setAction(async ({ target: _target, swapper: _swappper }: TaskArgs, { deployments, ethers }) => { 13 | // Get the deployer 14 | const [deployer] = await ethers.getSigners(); 15 | if (!deployer) { 16 | throw new Error(`Cannot find signer to deploy with`); 17 | } 18 | 19 | console.log("deployer: ", deployer.address); 20 | 21 | const deployment = await deployments.get("MidasProtocolTarget"); 22 | const target = _target ?? deployment.address; 23 | console.log("MidasProtocolTarget address: ", target); 24 | 25 | const swapper = _swappper ?? (await deployments.get("UniV3Swapper")).address; 26 | console.log("Swapper address: ", swapper); 27 | 28 | const instance = new Contract(target, deployment.abi, deployer); 29 | 30 | const tx = await instance.addSwapper(swapper); 31 | const receipt = await tx.wait(); 32 | 33 | console.log(receipt); 34 | }); 35 | -------------------------------------------------------------------------------- /tasks/removeSwapper.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "ethers"; 2 | import { task } from "hardhat/config"; 3 | 4 | type TaskArgs = { 5 | target?: string; 6 | swapper?: string; 7 | }; 8 | 9 | export default task("remove-swapper", "Remove swapper contract address to Midas Target") 10 | .addOptionalParam("target", "The address of the Midas Target") 11 | .addOptionalParam("swapper", "The address of the swapper") 12 | .setAction(async ({ target: _target, swapper: _swappper }: TaskArgs, { deployments, ethers }) => { 13 | // Get the deployer 14 | const [deployer] = await ethers.getSigners(); 15 | if (!deployer) { 16 | throw new Error(`Cannot find signer to deploy with`); 17 | } 18 | 19 | console.log("deployer: ", deployer.address); 20 | 21 | const deployment = await deployments.get("MidasProtocolTarget"); 22 | const target = _target ?? deployment.address; 23 | console.log("MidasProtocolTarget address: ", target); 24 | 25 | const swapper = _swappper ?? (await deployments.get("UniV3Swapper")).address; 26 | console.log("Swapper address: ", swapper); 27 | 28 | const instance = new Contract(target, deployment.abi, deployer); 29 | 30 | const tx = await instance.removeSwapper(swapper); 31 | const receipt = await tx.wait(); 32 | 33 | console.log(receipt); 34 | }); 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chain Abstraction Integration 2 | 3 | Contracts for Chain Abstraction integration 4 | 5 | # Setting up environment 6 | 7 | ### 1. Node.js environment - Prerequisite 8 | 9 | You can `skip` this step if you are using node.js version `>=16.0` 10 | command to check your node.js version 11 | 12 | ``` 13 | node -v 14 | ``` 15 | 16 | - `Installing Node.js`: https://hardhat.org/tutorial/setting-up-the-environment#installing-node.js 17 | - `Upgrading your Node.js installation`: https://hardhat.org/tutorial/setting-up-the-environment#upgrading-your-node.js-installation 18 | 19 | ### 2. Foundry - Prerequisite 20 | 21 | Command to check if foundry is already installed in your system - 22 | 23 | ``` 24 | forge --version 25 | ``` 26 | 27 | - `Installing foundry`: https://book.getfoundry.sh/getting-started/installation 28 | 29 | ### 3. Install Dependencies - Prerequisite 30 | 31 | 32 | - Adding all the dependency - 33 | ``` 34 | yarn 35 | ``` 36 | - To install the modpacks - 37 | ``` 38 | forge install 39 | ``` 40 | - To build - 41 | ``` 42 | forge build 43 | ``` 44 | 45 | ### Running the tests - 46 | 47 | Try following command to run the contarct tests - 48 | ``` 49 | yarn run test 50 | ``` 51 | or 52 | ``` 53 | forge test --via-ir 54 | ``` 55 | 56 | # Reference 57 | 58 | - https://hardhat.org/tutorial/setting-up-the-environment 59 | -------------------------------------------------------------------------------- /contracts/example/XSwapAndGreet/XSwapAndGreetTarget.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 5 | 6 | import {SwapForwarderXReceiver} from "../../destination/xreceivers/Swap/SwapForwarderXReceiver.sol"; 7 | 8 | interface IGreeter { 9 | function greetWithTokens(address _token, uint256 _amount, string calldata _greeting) external; 10 | } 11 | 12 | contract XSwapAndGreetTarget is SwapForwarderXReceiver { 13 | IGreeter public immutable greeter; 14 | 15 | constructor(address _greeter, address _connext) SwapForwarderXReceiver(_connext) { 16 | greeter = IGreeter(_greeter); 17 | } 18 | 19 | /// INTERNAL 20 | function _forwardFunctionCall( 21 | bytes memory _preparedData, 22 | bytes32 /*_transferId*/, 23 | uint256 /*_amount*/, 24 | address /*_asset*/ 25 | ) internal override returns (bool) { 26 | (bytes memory _forwardCallData, uint256 _amountOut, , address _toAsset) = abi.decode( 27 | _preparedData, 28 | (bytes, uint256, address, address) 29 | ); 30 | 31 | // Decode calldata 32 | string memory greeting = abi.decode(_forwardCallData, (string)); 33 | 34 | // Forward the call 35 | TransferHelper.safeApprove(_toAsset, address(greeter), _amountOut); 36 | greeter.greetWithTokens(_toAsset, _amountOut, greeting); 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /deploy/univ2swapper.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { DeployFunction } from "hardhat-deploy/types"; 3 | import { config as envConfig } from "dotenv"; 4 | import { DEFAULT_ARGS } from "./index"; 5 | 6 | envConfig(); 7 | 8 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { 9 | // Get the chain id 10 | const chainId = +(await hre.getChainId()); 11 | console.log("chainId", chainId); 12 | 13 | if (!DEFAULT_ARGS[chainId]) { 14 | throw new Error(`No defaults provided for ${chainId}`); 15 | } 16 | 17 | // Get the constructor args 18 | const args = [process.env.UNIV2_ROUTER ?? DEFAULT_ARGS[chainId].UNIV2_ROUTER]; 19 | 20 | // Get the deployer 21 | const [deployer] = await hre.ethers.getSigners(); 22 | if (!deployer) { 23 | throw new Error(`Cannot find signer to deploy with`); 24 | } 25 | console.log("\n============================= Deploying UniV2Swapper ==============================="); 26 | console.log("deployer: ", deployer.address); 27 | console.log("constructorArgs:", args); 28 | 29 | const adapter = await hre.deployments.deploy("UniV2Swapper", { 30 | from: deployer.address, 31 | args: args, 32 | skipIfAlreadyDeployed: true, 33 | log: true, 34 | // deterministicDeployment: true, 35 | }); 36 | console.log(`UniV2Swapper deployed to ${adapter.address}`); 37 | }; 38 | export default func; 39 | func.tags = ["univ2swapper", "test", "prod"]; 40 | -------------------------------------------------------------------------------- /deploy/swapandxcall.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { DeployFunction } from "hardhat-deploy/types"; 3 | import { config as envConfig } from "dotenv"; 4 | import { DEFAULT_ARGS } from "./index"; 5 | 6 | envConfig(); 7 | 8 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { 9 | // Get the chain id 10 | const chainId = +(await hre.getChainId()); 11 | console.log("chainId", chainId); 12 | 13 | if (!DEFAULT_ARGS[chainId]) { 14 | throw new Error(`No defaults provided for ${chainId}`); 15 | } 16 | 17 | // Get the constructor args 18 | const args = [process.env.CONNEXT ?? DEFAULT_ARGS[chainId].CONNEXT]; 19 | 20 | // Get the deployer 21 | const [deployer] = await hre.ethers.getSigners(); 22 | if (!deployer) { 23 | throw new Error(`Cannot find signer to deploy with`); 24 | } 25 | console.log("\n============================= Deploying SwapAndXCall ==============================="); 26 | console.log("deployer: ", deployer.address); 27 | console.log("constructorArgs:", args); 28 | 29 | // Deploy contract 30 | const adapter = await hre.deployments.deploy("SwapAndXCall", { 31 | from: deployer.address, 32 | args: args, 33 | skipIfAlreadyDeployed: true, 34 | log: true, 35 | // deterministicDeployment: true, 36 | }); 37 | console.log(`SwapAndXCall deployed to ${adapter.address}`); 38 | }; 39 | export default func; 40 | func.tags = ["swapandxcall", "test", "prod"]; 41 | -------------------------------------------------------------------------------- /deploy/MeanFinance/deploy_source.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { DeployFunction } from "hardhat-deploy/types"; 3 | import { config as envConfig } from "dotenv"; 4 | import { DEFAULT_ARGS } from "../index"; 5 | 6 | envConfig(); 7 | 8 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { 9 | // Get the chain id 10 | const chainId = +(await hre.getChainId()); 11 | console.log("chainId", chainId); 12 | 13 | if (!DEFAULT_ARGS[chainId]) { 14 | throw new Error(`No defaults provided for ${chainId}`); 15 | } 16 | 17 | // Get the constructor args 18 | const args = [process.env.CONNEXT ?? DEFAULT_ARGS[chainId].CONNEXT, process.env.WETH ?? DEFAULT_ARGS[chainId].WETH]; 19 | 20 | // Get the deployer 21 | const [deployer] = await hre.ethers.getSigners(); 22 | if (!deployer) { 23 | throw new Error(`Cannot find signer to deploy with`); 24 | } 25 | console.log("\n============================= Deploying MeanFinanceSource ==============================="); 26 | console.log("deployer: ", deployer.address); 27 | console.log("constructorArgs:", args); 28 | 29 | // Deploy contract 30 | const adapter = await hre.deployments.deploy("MeanFinanceSource", { 31 | from: deployer.address, 32 | args: args, 33 | skipIfAlreadyDeployed: true, 34 | log: true, 35 | // deterministicDeployment: true, 36 | }); 37 | console.log(`MeanFinanceSource deployed to ${adapter.address}`); 38 | }; 39 | export default func; 40 | func.tags = ["meanfinancesource", "test", "prod"]; 41 | -------------------------------------------------------------------------------- /deploy/MeanFinance/deploy_target.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { DeployFunction } from "hardhat-deploy/types"; 3 | import { config as envConfig } from "dotenv"; 4 | import { DEFAULT_ARGS, MEAN_CONFIG } from "../index"; 5 | 6 | envConfig(); 7 | 8 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { 9 | // Get the chain id 10 | const chainId = +(await hre.getChainId()); 11 | console.log("chainId", chainId); 12 | 13 | if (!DEFAULT_ARGS[chainId] || !MEAN_CONFIG[chainId].HUB) { 14 | throw new Error(`No defaults provided for ${chainId}`); 15 | } 16 | 17 | // Get the constructor args 18 | const args = [process.env.CONNEXT ?? DEFAULT_ARGS[chainId].CONNEXT, MEAN_CONFIG[chainId].HUB]; 19 | 20 | // Get the deployer 21 | const [deployer] = await hre.ethers.getSigners(); 22 | if (!deployer) { 23 | throw new Error(`Cannot find signer to deploy with`); 24 | } 25 | console.log("\n============================= Deploying MeanFinanceTarget ==============================="); 26 | console.log("deployer: ", deployer.address); 27 | console.log("constructorArgs:", args); 28 | 29 | // Deploy contract 30 | const adapter = await hre.deployments.deploy("MeanFinanceTarget", { 31 | from: deployer.address, 32 | args: args, 33 | skipIfAlreadyDeployed: true, 34 | log: true, 35 | // deterministicDeployment: true, 36 | }); 37 | console.log(`MeanFinanceTarget deployed to ${adapter.address}`); 38 | }; 39 | export default func; 40 | func.tags = ["meanfinancetarget", "test", "prod"]; 41 | -------------------------------------------------------------------------------- /deploy/MidasProtocol/index.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { DeployFunction } from "hardhat-deploy/types"; 3 | import { config as envConfig } from "dotenv"; 4 | import { DEFAULT_ARGS, MIDAS_CONFIG } from "../index"; 5 | 6 | envConfig(); 7 | 8 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { 9 | // Get the chain id 10 | const chainId = +(await hre.getChainId()); 11 | console.log("chainId", chainId); 12 | 13 | if (!MIDAS_CONFIG[chainId]) { 14 | throw new Error(`No defaults provided for ${chainId}`); 15 | } 16 | 17 | // Get the constructor args 18 | const args = [ 19 | process.env.CONNEXT ?? DEFAULT_ARGS[chainId].CONNEXT, 20 | process.env.COMPTROLLER ?? MIDAS_CONFIG[chainId].COMPTROLLER, 21 | ]; 22 | 23 | // Get the deployer 24 | const [deployer] = await hre.ethers.getSigners(); 25 | if (!deployer) { 26 | throw new Error(`Cannot find signer to deploy with`); 27 | } 28 | console.log("\n============================= Deploying MidasProtocolTarget ==============================="); 29 | console.log("deployer: ", deployer.address); 30 | console.log("constructorArgs:", args); 31 | 32 | // Deploy contract 33 | const adapter = await hre.deployments.deploy("MidasProtocolTarget", { 34 | from: deployer.address, 35 | args: args, 36 | skipIfAlreadyDeployed: true, 37 | log: true, 38 | // deterministicDeployment: true, 39 | }); 40 | console.log(`MidasProtocolTarget deployed to ${adapter.address}`); 41 | }; 42 | export default func; 43 | func.tags = ["midasprotocoltarget", "test", "prod"]; 44 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | 7 | // Settings that apply to all configs. 8 | // These can be overridden in individual configs. 9 | "console": "integratedTerminal", 10 | "internalConsoleOptions": "neverOpen", 11 | "skipFiles": ["/**"], 12 | 13 | // NOTE: Debugging requires that .js files exist on disk. 14 | // Build packages before debugging, or no breakpoints will be hit. 15 | 16 | "configurations": [ 17 | // ========== Debug a server or cli package ========== 18 | // Use this configuration to debug a server or cli. 19 | // Replace __PACKAGE_NAME__ and __PACKAGE_DIR__ with their values. 20 | // { 21 | // "name": "Debug __PACKAGE_NAME__", 22 | // "type": "node", 23 | // "request": "launch", 24 | // "cwd": "${workspaceFolder}", 25 | // "args": [ 26 | // "${workspaceFolder}/packages/__PACKAGE_DIR__/lib/index.js" 27 | // // Add any arguments or flags to be passed to your CLI here. 28 | // ] 29 | // }, 30 | // 31 | // ========== Debug tests ========== 32 | // Use this configuration to debug tests for a package. 33 | // Replace __PACKAGE_NAME__ and __PACKAGE_DIR__ with their values. 34 | // { 35 | // "name": "Debug __PACKAGE_NAME__ tests", 36 | // "type": "node", 37 | // "request": "launch", 38 | // "cwd": "${workspaceFolder}/packages/__PACKAGE_DIR__", 39 | // "program": "${workspaceFolder}/node_modules/jest/bin/jest.js", 40 | // "args": ["--verbose", "-i", "--no-cache"] 41 | // } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /tasks/xdeposit.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "ethers"; 2 | import { task } from "hardhat/config"; 3 | 4 | import { DEFAULT_ARGS } from "../deploy"; 5 | 6 | type callDataParams = { 7 | poolFee: number; 8 | amountOutMin: string; 9 | from: string; 10 | to: string; 11 | amountOfSwaps: number; 12 | swapInterval: string; 13 | owner: string; 14 | permissions: any; 15 | }; 16 | type TaskArgs = { 17 | target: string; 18 | destination: string; 19 | inputAsset: string; 20 | connextAsset: string; 21 | amountIn: string; 22 | sourceAmountOutMin: string; 23 | sourcePoolFee?: number; 24 | connextSlippage?: number; 25 | }; 26 | 27 | export default task( 28 | "xdeposit", 29 | "Mean finance create position via xcall" 30 | ).setAction(async ({}: TaskArgs, { getChainId, deployments, ethers }) => { 31 | // Get the deployer 32 | const [deployer] = await ethers.getSigners(); 33 | if (!deployer) { 34 | throw new Error(`Cannot find signer to deploy with`); 35 | } 36 | 37 | console.log("deployer: ", deployer.address); 38 | 39 | const deployment = await deployments.get("MeanFinanceSource"); 40 | const instance = new Contract(deployment.address, deployment.abi, deployer); 41 | console.log("MeanFinanceSource Address: ", deployment.address); 42 | 43 | // prepare params 44 | const { WETH, USDC, CONNEXT, DOMAIN } = DEFAULT_ARGS[10]; 45 | 46 | const { WETH: pWETH , USDC: pUSDC, CONNEXT: pCONNEXT, DOMAIN: pDOMAIN } = DEFAULT_ARGS[137]; 47 | 48 | // Hardhat coding Target for testing 49 | 50 | const target = "0x3E64213564cc30107Beb81cd0DCEd3F18dF79B35"; 51 | const destinationDomain = pDOMAIN; 52 | 53 | 54 | // const approveTx = await inputAsset 55 | // .connect(wallet) 56 | // .approve(source.address, inputBalance); 57 | 58 | // const approveReceipt = await approveTx.wait(); 59 | }); 60 | -------------------------------------------------------------------------------- /contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "./OZERC20.sol"; 5 | import {IERC20Metadata, IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 6 | import {IBridgeToken} from "./interfaces/IBridgeToken.sol"; 7 | 8 | /** 9 | * @notice This token is ONLY useful for testing 10 | * @dev Anybody can mint as many tokens as they like 11 | * @dev Anybody can burn anyone else's tokens 12 | */ 13 | contract TestERC20 is ERC20, IBridgeToken { 14 | constructor(string memory _name, string memory _symbol) ERC20(18, _name, _symbol, "1") { 15 | _mint(msg.sender, 1000000 ether); 16 | } 17 | 18 | // ============ Bridge functions =============== 19 | function setDetails(string calldata _newName, string calldata _newSymbol) external override { 20 | // Does nothing, in practice will update the details to match the hash in message 21 | // not the autodeployed results 22 | _name = _newName; 23 | _symbol = _newSymbol; 24 | } 25 | 26 | // ============ Token functions =============== 27 | function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) { 28 | return ERC20.balanceOf(account); 29 | } 30 | 31 | function mint(address account, uint256 amount) external { 32 | _mint(account, amount); 33 | } 34 | 35 | function burn(address account, uint256 amount) external { 36 | _burn(account, amount); 37 | } 38 | 39 | function symbol() public view override(ERC20, IERC20Metadata) returns (string memory) { 40 | return ERC20.symbol(); 41 | } 42 | 43 | function name() public view override(ERC20, IERC20Metadata) returns (string memory) { 44 | return ERC20.name(); 45 | } 46 | 47 | function decimals() public view override(ERC20, IERC20Metadata) returns (uint8) { 48 | return ERC20.decimals(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /deploy/univ3swapper.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { DeployFunction } from "hardhat-deploy/types"; 3 | import { config as envConfig } from "dotenv"; 4 | import { DEFAULT_ARGS } from "./index"; 5 | 6 | envConfig(); 7 | 8 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { 9 | // Get the chain id 10 | const chainId = +(await hre.getChainId()); 11 | console.log("chainId", chainId); 12 | 13 | if (!DEFAULT_ARGS[chainId]) { 14 | throw new Error(`No defaults provided for ${chainId}`); 15 | } 16 | 17 | // Get the constructor args 18 | const args = [process.env.UNIV3_ROUTER ?? DEFAULT_ARGS[chainId].UNIV3_ROUTER]; 19 | 20 | // Get the deployer 21 | const [deployer] = await hre.ethers.getSigners(); 22 | if (!deployer) { 23 | throw new Error(`Cannot find signer to deploy with`); 24 | } 25 | console.log("\n============================= Deploying UniV3Swapper ==============================="); 26 | console.log("deployer: ", deployer.address); 27 | console.log("constructorArgs:", args); 28 | 29 | // Deploy contract 30 | if (chainId === 56) { 31 | // deploy pancake v3 swapper 32 | const adapter = await hre.deployments.deploy("PancakeV3Swapper", { 33 | from: deployer.address, 34 | args: args, 35 | skipIfAlreadyDeployed: true, 36 | log: true, 37 | // deterministicDeployment: true, 38 | }); 39 | console.log(`PancakeV3Swapper deployed to ${adapter.address}`); 40 | } else { 41 | const adapter = await hre.deployments.deploy("UniV3Swapper", { 42 | from: deployer.address, 43 | args: args, 44 | skipIfAlreadyDeployed: true, 45 | log: true, 46 | // deterministicDeployment: true, 47 | }); 48 | console.log(`UniV3Swapper deployed to ${adapter.address}`); 49 | } 50 | }; 51 | export default func; 52 | func.tags = ["univ3swapper", "test", "prod"]; 53 | -------------------------------------------------------------------------------- /contracts/destination/xreceivers/Swap/SwapForwarderXReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {IUniswapV2Router02} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; 5 | import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 6 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 7 | 8 | import {ForwarderXReceiver} from "../ForwarderXReceiver.sol"; 9 | import {SwapAdapter} from "../../../shared/Swap/SwapAdapter.sol"; 10 | 11 | /** 12 | * @title SwapForwarderXReceiver 13 | * @author Connext 14 | * @notice Abstract contract to allow for swapping tokens before forwarding a call. 15 | */ 16 | abstract contract SwapForwarderXReceiver is ForwarderXReceiver, SwapAdapter { 17 | using Address for address; 18 | 19 | /// @dev The address of the Connext contract on this domain. 20 | constructor(address _connext) ForwarderXReceiver(_connext) {} 21 | 22 | /// INTERNAL 23 | /** 24 | * @notice Prepare the data by calling to the swap adapter. Return the data to be swapped. 25 | * @dev This is called by the xReceive function so the input data is provided by the Connext bridge. 26 | * @param _transferId The transferId of the transfer. 27 | * @param _data The data to be swapped. 28 | * @param _amount The amount to be swapped. 29 | * @param _asset The incoming asset to be swapped. 30 | */ 31 | function _prepare( 32 | bytes32 _transferId, 33 | bytes memory _data, 34 | uint256 _amount, 35 | address _asset 36 | ) internal override returns (bytes memory) { 37 | (address _swapper, address _toAsset, bytes memory _swapData, bytes memory _forwardCallData) = abi.decode( 38 | _data, 39 | (address, address, bytes, bytes) 40 | ); 41 | 42 | uint256 _amountOut = this.exactSwap(_swapper, _amount, _asset, _toAsset, _swapData); 43 | 44 | return abi.encode(_forwardCallData, _amountOut, _asset, _toAsset, _transferId); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /utils/interval-utils.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export class SwapInterval { 4 | static readonly ONE_MINUTE = new SwapInterval(60, '0x01'); 5 | static readonly FIVE_MINUTES = new SwapInterval(SwapInterval.ONE_MINUTE.seconds * 5, '0x02'); 6 | static readonly FIFTEEN_MINUTES = new SwapInterval(SwapInterval.FIVE_MINUTES.seconds * 3, '0x04'); 7 | static readonly THIRTY_MINUTES = new SwapInterval(SwapInterval.FIFTEEN_MINUTES.seconds * 2, '0x08'); 8 | static readonly ONE_HOUR = new SwapInterval(SwapInterval.THIRTY_MINUTES.seconds * 2, '0x10'); 9 | static readonly FOUR_HOURS = new SwapInterval(SwapInterval.ONE_HOUR.seconds * 4, '0x20'); 10 | static readonly ONE_DAY = new SwapInterval(SwapInterval.FOUR_HOURS.seconds * 6, '0x40'); 11 | static readonly ONE_WEEK = new SwapInterval(SwapInterval.ONE_DAY.seconds * 7, '0x80'); 12 | 13 | static readonly INTERVALS = [ 14 | SwapInterval.ONE_MINUTE, 15 | SwapInterval.FIVE_MINUTES, 16 | SwapInterval.FIFTEEN_MINUTES, 17 | SwapInterval.THIRTY_MINUTES, 18 | SwapInterval.ONE_HOUR, 19 | SwapInterval.FOUR_HOURS, 20 | SwapInterval.ONE_DAY, 21 | SwapInterval.ONE_WEEK, 22 | ]; 23 | 24 | private constructor(readonly seconds: number, readonly mask: string) {} 25 | 26 | public isInByteSet(byte: string): boolean { 27 | return (parseInt(byte) & parseInt(this.mask)) != 0; 28 | } 29 | 30 | static intervalsToByte(...intervals: SwapInterval[]): string { 31 | const finalMask = intervals.map((intervals) => parseInt(intervals.mask)).reduce((a, b) => a | b, 0); 32 | return '0x' + finalMask.toString(16).padStart(2, '0'); 33 | } 34 | 35 | static intervalsfromByte(byte: string): SwapInterval[] { 36 | let num = parseInt(byte); 37 | let index = 0; 38 | const result = []; 39 | while (index <= 8 && 1 << index <= num) { 40 | if ((num & (1 << index)) != 0) { 41 | result.push(SwapInterval.INTERVALS[index]); 42 | } 43 | index++; 44 | } 45 | return result; 46 | } 47 | } -------------------------------------------------------------------------------- /contracts/test/unit/shared/Swap/OneInch/OneInchUniswapV3.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.19; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {IDCAHubPositionHandler, IDCAPermissionManager} from "@mean-finance/dca-v2-core/contracts/interfaces/IDCAHub.sol"; 6 | 7 | import {TestHelper} from "../../../../utils/TestHelper.sol"; 8 | import {OneInchUniswapV3, IUniswapV3Router} from "../../../../../shared/Swap/OneInch/OneInchUniswapV3.sol"; 9 | 10 | // data from 1inch API: 0xe449022e000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000120fd1200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000e0554a476a092703abdb3ef35c80e0d76d32939fcfee7c08 11 | 12 | contract OneInchUniswapV3Test is TestHelper { 13 | OneInchUniswapV3 public swapper; 14 | 15 | function setUp() public override { 16 | super.setUp(); 17 | swapper = new OneInchUniswapV3(address(1)); 18 | 19 | vm.label(address(this), "TestContract"); 20 | } 21 | 22 | function test_OneInchUniswapV3Test__works() public { 23 | uint256 _amountIn = 1; 24 | address _tokenIn = address(2); 25 | address _toAsset = address(3); 26 | 27 | bytes 28 | memory _swapData = hex"e449022e000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000120fd1200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000e0554a476a092703abdb3ef35c80e0d76d32939fcfee7c08"; 29 | 30 | vm.mockCall(_tokenIn, abi.encodeWithSelector(IERC20.transferFrom.selector), abi.encode(true)); 31 | vm.mockCall(_tokenIn, abi.encodeWithSelector(IERC20.allowance.selector), abi.encode(0)); 32 | vm.mockCall(_tokenIn, abi.encodeWithSelector(IERC20.approve.selector), abi.encode(true)); 33 | vm.mockCall(address(1), abi.encodeWithSelector(IUniswapV3Router.uniswapV3Swap.selector), abi.encode(10)); 34 | vm.mockCall(_toAsset, abi.encodeWithSelector(IERC20.transfer.selector), abi.encode(true)); 35 | uint256 amountOut = swapper.swap(_amountIn, _tokenIn, _toAsset, _swapData); 36 | assertEq(amountOut, 10); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connext-integration", 3 | "version": "1.0.0", 4 | "description": "Connext Integration Source and Target Contracts", 5 | "main": "index.js", 6 | "repository": "https://github.com/connext/connext-integration.git", 7 | "author": "Connext", 8 | "license": "MIT", 9 | "scripts": { 10 | "compile": "hardhat compile", 11 | "test": "yarn forge:test", 12 | "hardhat:test": "hardhat test", 13 | "forge": "forge", 14 | "forge:install": "forge install", 15 | "forge:build": "forge build --via-ir", 16 | "forge:test": "forge test --via-ir", 17 | "lint": "yarn lint:sol && yarn prettier:check", 18 | "lint:sol": "forge fmt --check && yarn solhint \"{script,src,test}/**/*.sol\"", 19 | "prettier:check": "prettier --check './contracts/**/*.sol' && prettier --check './contracts_forge/**/*.sol'", 20 | "prettier:write": "prettier --write './contracts/**/*.sol' && prettier --write './contracts_forge/**/*.sol'", 21 | "hardhat": "hardhat", 22 | "export": "hardhat export --export-all ./deployments.json", 23 | "clean": "rimraf cache out", 24 | "cli:run": "ts-node test/cli.ts" 25 | }, 26 | "devDependencies": { 27 | "@0xgafu/common-abi": "^1.0.3", 28 | "@connext/utils": "^7.3.17", 29 | "@ethersproject/abi": "^5.7.0", 30 | "@ethersproject/providers": "^5.7.2", 31 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", 32 | "@nomicfoundation/hardhat-foundry": "^1.0.0", 33 | "@nomicfoundation/hardhat-network-helpers": "^1.0.8", 34 | "@nomicfoundation/hardhat-toolbox": "^2.0.1", 35 | "@nomiclabs/hardhat-ethers": "^2.2.2", 36 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 37 | "@nomiclabs/hardhat-waffle": "^2.0.1", 38 | "@typechain/ethers-v5": "^10.2.0", 39 | "@typechain/hardhat": "^6.1.5", 40 | "@types/chai": "^4.2.0", 41 | "@types/mocha": ">=9.1.0", 42 | "@types/node": ">=12.0.0", 43 | "chai": "^4.3.7", 44 | "dotenv": "^16.0.3", 45 | "ethereum-waffle": "^3.4.0", 46 | "ethers": "^5.5.2", 47 | "ethers-eip712": "^0.2.0", 48 | "hardhat": "^2.12.6", 49 | "hardhat-deploy": "^0.11.23", 50 | "hardhat-deploy-ethers": "^0.3.0-beta.11", 51 | "hardhat-gas-reporter": "^1.0.8", 52 | "rimraf": "^4.4.0", 53 | "solidity-coverage": "^0.8.2", 54 | "ts-node": ">=8.0.0", 55 | "typechain": "^8.1.1", 56 | "typescript": ">=4.5.0" 57 | }, 58 | "dependencies": { 59 | "@connext/interfaces": "^2.0.3", 60 | "@mean-finance/dca-v2-core": "^3.2.0", 61 | "@openzeppelin/contracts": "^4.8.2", 62 | "@uniswap/v2-core": "^1.0.1", 63 | "@uniswap/v2-periphery": "^1.1.0-beta.0", 64 | "prettier": "^2.8.7", 65 | "prettier-plugin-solidity": "^1.1.3" 66 | }, 67 | "packageManager": "yarn@3.4.1" 68 | } 69 | -------------------------------------------------------------------------------- /contracts/origin/Swap/ExampleSource.sol: -------------------------------------------------------------------------------- 1 | // // SPDX-License-Identifier: UNLICENSED 2 | // pragma solidity ^0.8.13; 3 | 4 | // import {IConnext, TokenId} from "@connext/interfaces/core/IConnext.sol"; 5 | // import {IWeth} from "@connext/interfaces/core/IWeth.sol"; 6 | // import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | // import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 8 | 9 | // import {SwapAdapter} from "../../shared/Swap/SwapAdapter.sol"; 10 | 11 | // contract ExampleSource is SwapAdapter { 12 | // // The Connext contract on this domain 13 | // IConnext public immutable connext; 14 | // /// @notice WETH address to handle native assets before swapping / sending. 15 | // IWeth public immutable weth; 16 | 17 | // receive() external payable virtual override(SwapAdapter) {} 18 | 19 | // constructor(address _connext, address _weth) SwapAdapter() { 20 | // connext = IConnext(_connext); 21 | // weth = IWeth(_weth); 22 | // } 23 | 24 | // function xDeposit( 25 | // address target, 26 | // uint32 destinationDomain, 27 | // address inputAsset, 28 | // address connextAsset, 29 | // uint256 amountIn, 30 | // uint256 connextSlippage, 31 | // uint24 sourcePoolFee, 32 | // uint256 sourceAmountOutMin, 33 | // bytes memory _callData 34 | // ) external payable returns (bytes32 transferId) { 35 | // // Sanity check: amounts above mins 36 | // require(amountIn > 0, "!amount"); 37 | 38 | // uint256 amountOut = amountIn; 39 | 40 | // // wrap origin asset if needed 41 | // if (inputAsset == address(0)) { 42 | // weth.deposit{value: amountIn}(); 43 | // inputAsset = address(weth); 44 | // } else { 45 | // TransferHelper.safeTransferFrom(inputAsset, msg.sender, address(this), amountIn); 46 | // } 47 | 48 | // // swap to donation asset if needed 49 | // if (inputAsset != connextAsset) { 50 | // require(connextApprovedAssets(connextAsset), "!connextAsset"); 51 | // // amountOut = swap(inputAsset, connextAsset, sourcePoolFee, amountIn, sourceAmountOutMin); 52 | // } 53 | 54 | // TransferHelper.safeApprove(connextAsset, address(connext), amountOut); 55 | // // xcall 56 | // // Perform connext transfer 57 | // transferId = connext.xcall{value: msg.value}( 58 | // destinationDomain, // 59 | // target, // 60 | // connextAsset, // 61 | // msg.sender, // 62 | // amountOut, // 63 | // connextSlippage, // 64 | // _callData // 65 | // ); 66 | // } 67 | 68 | // /// INTERNAL 69 | // function connextApprovedAssets(address adopted) internal view returns (bool approved) { 70 | // TokenId memory canonical = connext.adoptedToCanonical(adopted); 71 | // approved = connext.approvedAssets(canonical); 72 | // } 73 | // } 74 | -------------------------------------------------------------------------------- /contracts/shared/IXERC20/IXERC20Lockbox.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.4 <0.9.0; 3 | 4 | interface IXERC20Lockbox { 5 | /** 6 | * @notice Emitted when tokens are deposited into the lockbox 7 | */ 8 | 9 | event Deposit(address _sender, uint256 _amount); 10 | 11 | /** 12 | * @notice Emitted when tokens are withdrawn from the lockbox 13 | */ 14 | 15 | event Withdraw(address _sender, uint256 _amount); 16 | 17 | /** 18 | * @notice Reverts when a user tries to deposit native tokens on a non-native lockbox 19 | */ 20 | 21 | error IXERC20Lockbox_NotNative(); 22 | 23 | /** 24 | * @notice Reverts when a user tries to deposit non-native tokens on a native lockbox 25 | */ 26 | 27 | error IXERC20Lockbox_Native(); 28 | 29 | /** 30 | * @notice Reverts when a user tries to withdraw and the call fails 31 | */ 32 | 33 | error IXERC20Lockbox_WithdrawFailed(); 34 | 35 | /** 36 | * @notice The XERC20 token of this contract 37 | */ 38 | 39 | function XERC20() external view returns (address); 40 | 41 | /** 42 | * @notice The ERC20 token of this contract 43 | */ 44 | 45 | function ERC20() external view returns (address); 46 | 47 | /** 48 | * @notice Whether the ERC20 token is the native gas token of this chain 49 | */ 50 | 51 | function IS_NATIVE() external view returns (bool); 52 | 53 | /** 54 | * @notice Deposit ERC20 tokens into the lockbox 55 | * 56 | * @param _amount The amount of tokens to deposit 57 | */ 58 | 59 | function deposit(uint256 _amount) external; 60 | 61 | /** 62 | * @notice Deposit native tokens into the lockbox 63 | */ 64 | 65 | function depositNative() external payable; 66 | 67 | /** 68 | * @notice Deposit ERC20 tokens into the lockbox, and send the XERC20 to a user 69 | * 70 | * @param _user The user to send the XERC20 to 71 | * @param _amount The amount of tokens to deposit 72 | */ 73 | 74 | function depositTo(address _user, uint256 _amount) external; 75 | 76 | /** 77 | * @notice Deposit the native asset into the lockbox, and send the XERC20 to a user 78 | * 79 | * @param _user The user to send the XERC20 to 80 | */ 81 | 82 | function depositNativeTo(address _user) external payable; 83 | 84 | /** 85 | * @notice Withdraw ERC20 tokens from the lockbox 86 | * 87 | * @param _amount The amount of tokens to withdraw 88 | */ 89 | 90 | function withdraw(uint256 _amount) external; 91 | 92 | /** 93 | * @notice Withdraw ERC20 tokens from the lockbox 94 | * 95 | * @param _user The user to withdraw to 96 | * @param _amount The amount of tokens to withdraw 97 | */ 98 | 99 | function withdrawTo(address _user, uint256 _amount) external; 100 | } 101 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "problemMatcher": [], 6 | "type": "shell", 7 | "options": { "cwd": "${workspaceFolder}" }, 8 | "inputs": [ 9 | { 10 | "id": "createPackageName", 11 | "type": "promptString", 12 | "description": "What is the name of the package to create?" 13 | }, 14 | { 15 | "id": "createTemplate", 16 | "type": "pickString", 17 | "description": "Which template should the package be created with?", 18 | "options": ["node-lib", "node-cli", "node-server", "browser-lib"], 19 | "default": "node-lib" 20 | }, 21 | { 22 | "id": "addPackageName", 23 | "type": "promptString", 24 | "description": "What is the name of the dependency you are adding?" 25 | }, 26 | { 27 | "id": "addTo", 28 | "type": "promptString", 29 | "description": "To which package are you adding the dependency?" 30 | }, 31 | { 32 | "id": "removePackageName", 33 | "type": "promptString", 34 | "description": "What is the name of the dependency you are removing?" 35 | }, 36 | { 37 | "id": "removeFrom", 38 | "type": "promptString", 39 | "description": "From which package are you removing the dependency?" 40 | } 41 | ], 42 | "tasks": [ 43 | // Sample task to run a server in dev mode. For a package named "my-server", 44 | // created from the node-server template: 45 | // { 46 | // "label": "Run my-server for dev", 47 | // "command": "yarn workspace my-server dev" 48 | // }, 49 | { 50 | "label": "Create a new package", 51 | "command": "yarn tsp:dev create ${input:createPackageName} --template ${input:createTemplate}" 52 | }, 53 | { 54 | "label": "Add a dependency from one package to another", 55 | "command": "yarn tsp:dev add ${input:addPackageName} --to ${input:addTo}" 56 | }, 57 | { 58 | "label": "Remove a reference from one package to another", 59 | "command": "yarn tsp:dev remove ${input:removePackageName} --from ${input:removeFrom}" 60 | }, 61 | { "label": "List all packages", "command": "yarn tsp list" }, 62 | { 63 | "group": { "kind": "build", "isDefault": true }, 64 | "label": "Verify all packages", 65 | "command": "yarn verify:all" 66 | }, 67 | { 68 | "group": { "kind": "test", "isDefault": true }, 69 | "label": "Test all packages", 70 | "command": "yarn test:all" 71 | }, 72 | { "label": "Build all packages", "command": "yarn build:all" }, 73 | { "label": "Lint all packages", "command": "yarn lint:all" }, 74 | { "label": "Clean all packages", "command": "yarn clean:all" }, 75 | { "label": "Purge all packages", "command": "yarn purge:all" } 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /test/cli.ts: -------------------------------------------------------------------------------- 1 | import { ethers, Wallet } from "ethers"; 2 | import { 3 | defaultAbiCoder, 4 | joinSignature, 5 | keccak256, 6 | solidityKeccak256, 7 | _TypedDataEncoder, 8 | toUtf8Bytes, 9 | hexlify, 10 | hexZeroPad, 11 | } from "ethers/lib/utils"; 12 | 13 | const CASTDATA_TYPEHASH = keccak256(toUtf8Bytes("CastData(string[] targetNames,bytes[] datas,address origin)")); 14 | const SIG_TYPEHASH = keccak256( 15 | toUtf8Bytes( 16 | "Sig(CastData cast,bytes32 salt,uint256 deadline)CastData(string[] targetNames,bytes[] datas,address origin)", 17 | ), 18 | ); 19 | 20 | type EIP712Domain = { 21 | name: string; 22 | version: string; 23 | chainId: number; 24 | verifyingContract: string; 25 | }; 26 | 27 | type CastData = { 28 | targetNames: string[]; 29 | datas: Uint8Array[]; 30 | origin: string; 31 | }; 32 | 33 | export const generateEIP712Signature = (domain: EIP712Domain, structHash: string, signer: Wallet): string => { 34 | const domainSeparator = _TypedDataEncoder.hashDomain(domain); 35 | 36 | const digest = solidityKeccak256( 37 | ["string", "bytes32", "bytes32"], 38 | ["\x19\x01", domainSeparator, keccak256(structHash)], 39 | ); 40 | const signingKey = signer._signingKey(); 41 | const signature = signingKey.signDigest(digest); 42 | const joinedSignature = joinSignature(signature); 43 | return joinedSignature; 44 | }; 45 | 46 | const generateSignature = async ( 47 | sender: Wallet, 48 | domain: EIP712Domain, 49 | castData: CastData, 50 | salt: string, 51 | deadline: string, 52 | ): Promise => { 53 | const encodedData = defaultAbiCoder.encode( 54 | ["bytes32", "string[]", "bytes[]", "address"], 55 | [CASTDATA_TYPEHASH, castData.targetNames, castData.datas, castData.origin], 56 | ); 57 | const structHash = defaultAbiCoder.encode( 58 | ["bytes32", "bytes32", "bytes32", "uint256"], 59 | [SIG_TYPEHASH, keccak256(encodedData), salt, deadline], 60 | ); 61 | 62 | const signature = generateEIP712Signature(domain, structHash, sender); 63 | return signature; 64 | }; 65 | 66 | export const main = async () => { 67 | const TEST_PRIIVATE_KEY = "913b591c8abc30e7d3d2a4ebd560e1f02197cb701f077c9fcdc2ba8b0a7d9abe"; // address = 0xc1aAED5D41a3c3c33B1978EA55916f9A3EE1B397 68 | const sender = new ethers.Wallet(TEST_PRIIVATE_KEY); 69 | const authContractAddress = "0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"; 70 | 71 | const domain = { 72 | name: "InstaTargetAuth", 73 | version: "1", 74 | chainId: 31337, 75 | verifyingContract: authContractAddress, 76 | }; 77 | 78 | const castData = { 79 | targetNames: ["target111", "target222", "target333"], 80 | datas: [toUtf8Bytes("0x111"), toUtf8Bytes("0x222"), toUtf8Bytes("0x333")], 81 | origin: sender.address.toLowerCase(), 82 | }; 83 | 84 | const salt = hexZeroPad(hexlify(1), 32); 85 | const signature = await generateSignature(sender, domain, castData, salt, "100"); 86 | console.log({ signature }); 87 | }; 88 | 89 | main(); 90 | -------------------------------------------------------------------------------- /deploy/index.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_ARGS: Record = { 2 | // default to mimic optimism 3 | 31337: { 4 | CONNEXT: "0x8f7492DE823025b4CfaAB1D34c58963F2af5DEDA", 5 | WETH: "0x4200000000000000000000000000000000000006", // Weth on Optimism 6 | USDC: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", // USDC on Optimism 7 | DOMAIN: "6648936", // Ethereum domain ID 8 | ONEINCH_ROUTER: "", 9 | UNIV3_ROUTER: "0xE592427A0AEce92De3Edee1F18E0157C05861564", 10 | }, 11 | // Optimism 12 | 10: { 13 | CONNEXT: "0x8f7492DE823025b4CfaAB1D34c58963F2af5DEDA", 14 | WETH: "0x4200000000000000000000000000000000000006", // Weth on Optimism 15 | USDC: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", // USDC on Optimism 16 | DOMAIN: "1869640809", // Optimism domain ID 17 | ONEINCH_ROUTER: "0x1111111254EEB25477B68fb85Ed929f73A960582", 18 | UNIV3_ROUTER: "0xE592427A0AEce92De3Edee1F18E0157C05861564", 19 | }, 20 | // Arbitrum 21 | 42161: { 22 | CONNEXT: "0xEE9deC2712cCE65174B561151701Bf54b99C24C8", // Connext on Arbitrum 23 | WETH: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // Weth on Arbitrum 24 | USDC: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", // USDC on Arbitrum (donation asset) 25 | DOMAIN: "1634886255", // Arbitrum domain ID 26 | ONEINCH_ROUTER: "0x1111111254EEB25477B68fb85Ed929f73A960582", 27 | UNIV3_ROUTER: "0xE592427A0AEce92De3Edee1F18E0157C05861564", 28 | }, 29 | // Polygon 30 | 137: { 31 | CONNEXT: "0x11984dc4465481512eb5b777E44061C158CF2259", // Connext 32 | WETH: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", // Weth 33 | USDC: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", // USDC 34 | DOMAIN: "1886350457", // Polygon domain ID 35 | ONEINCH_ROUTER: "0x1111111254EEB25477B68fb85Ed929f73A960582", 36 | UNIV3_ROUTER: "0xE592427A0AEce92De3Edee1F18E0157C05861564", 37 | }, 38 | // Bnb 39 | 56: { 40 | CONNEXT: "0xCd401c10afa37d641d2F594852DA94C700e4F2CE", // Connext 41 | USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", // USDC 42 | DOMAIN: "6450786", // Polygon domain ID 43 | ONEINCH_ROUTER: "0x1111111254EEB25477B68fb85Ed929f73A960582", 44 | UNIV3_ROUTER: "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4", // PancakeSwap Smart Router 45 | }, 46 | // Gnosis 47 | 100: { 48 | CONNEXT: "0x5bB83e95f63217CDa6aE3D181BA580Ef377D2109", // Connext 49 | USDC: "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83", // USDC 50 | DOMAIN: "6778479", // Gnosis domain ID 51 | ONEINCH_ROUTER: "0x1111111254EEB25477B68fb85Ed929f73A960582", 52 | UNIV2_ROUTER: "0x1C232F01118CB8B424793ae03F870aa7D0ac7f77", // HoneysSwap UniV2 Router 53 | }, 54 | }; 55 | 56 | export const MIDAS_CONFIG: Record = { 57 | 137: { 58 | COMPTROLLER: "0xDb984f8cbc1cF893a18c2DA50282a1492234602c", 59 | }, 60 | 56: { 61 | COMPTROLLER: "0x31d76A64Bc8BbEffb601fac5884372DEF910F044", 62 | }, 63 | 42161: { 64 | COMPTROLLER: "0x185Fa7d0e7d8A4FE7E09eB9df68B549c660e1116", 65 | }, 66 | }; 67 | 68 | export const MEAN_CONFIG: Record = { 69 | 137: { 70 | HUB: "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox"; 2 | import "hardhat-deploy"; 3 | import "@nomiclabs/hardhat-ethers"; 4 | import "@nomicfoundation/hardhat-foundry"; 5 | import { config as dotenvConfig } from "dotenv"; 6 | import { HardhatUserConfig } from "hardhat/config"; 7 | 8 | import "./tasks/addSwapper"; 9 | import "./tasks/removeSwapper"; 10 | 11 | dotenvConfig(); 12 | const alchemyApiKey = process.env.ALCHEMY_API_KEY; 13 | const PRIVATE_KEY = process.env.PRIVATE_KEY; 14 | const MNEMONIC = process.env.MNEMONIC ?? "test test test test test test test test test test test junk"; 15 | 16 | function createConfig(network: string) { 17 | return { 18 | url: getNetworkUrl(network)!, 19 | accounts: !!PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : { mnemonic: MNEMONIC }, 20 | // gasPrice: BigNumber.from(networkGasPriceConfig[network]) 21 | // .mul(1e9).toString(), // Update the mapping above 22 | verify: { 23 | etherscan: { 24 | apiKey: process.env.ETHERSCAN_API_KEY!, 25 | }, 26 | }, 27 | }; 28 | } 29 | 30 | function getNetworkUrl(networkType: string) { 31 | if (networkType === "polygon") 32 | return alchemyApiKey ? `https://polygon-mainnet.g.alchemy.com/v2/${alchemyApiKey}` : "https://polygon.llamarpc.com"; 33 | else if (networkType === "arbitrum") 34 | return alchemyApiKey ? `https://arb-mainnet.g.alchemy.com/v2/${alchemyApiKey}` : "https://arb1.arbitrum.io/rpc"; 35 | else if (networkType === "optimism") 36 | return alchemyApiKey ? `https://opt-mainnet.g.alchemy.com/v2/${alchemyApiKey}` : "https://mainnet.optimism.io"; 37 | else if (networkType === "fantom") return `https://rpc.ftm.tools/`; 38 | else if (networkType === "bnb") return `https://bsc-dataseed.binance.org`; 39 | else if (networkType === "xdai") return `https://rpc.ankr.com/gnosis`; 40 | else return alchemyApiKey ? `https://eth-mainnet.alchemyapi.io/v2/${alchemyApiKey}` : "https://cloudflare-eth.com"; 41 | } 42 | 43 | const config: HardhatUserConfig = { 44 | solidity: { 45 | compilers: [ 46 | { 47 | version: "0.8.19", 48 | settings: { 49 | viaIR: true, 50 | optimizer: { 51 | enabled: true, 52 | runs: 200, 53 | }, 54 | }, 55 | }, 56 | { 57 | version: "0.7.6", // for @uniswap/v3-periphery/contracts/libraries/Path.sol 58 | settings: {}, 59 | }, 60 | ], 61 | }, 62 | defaultNetwork: "hardhat", 63 | namedAccounts: { 64 | deployer: { default: 0 }, 65 | alice: { default: 1 }, 66 | bob: { default: 2 }, 67 | rando: { default: 3 }, 68 | }, 69 | networks: { 70 | hardhat: { 71 | chainId: 31337, 72 | forking: { 73 | url: "https://mainnet.optimism.io", 74 | blockNumber: 73304256, // mined 09/02/2023 75 | }, 76 | }, 77 | mainnet: createConfig("mainnet"), 78 | polygon: createConfig("polygon"), 79 | arbitrum: createConfig("arbitrum"), 80 | optimism: createConfig("optimism"), 81 | bnb: createConfig("bnb"), 82 | fantom: createConfig("fantom"), 83 | xdai: createConfig("xdai"), 84 | }, 85 | etherscan: { 86 | apiKey: { 87 | // mainnets 88 | polygon: String(process.env.POLYGONSCAN_API_KEY), 89 | optimism: String(process.env.OPTIMISM_API_KEY), 90 | arbitrum: String(process.env.ARBISCAN_API_KEY), 91 | }, 92 | }, 93 | }; 94 | 95 | export default config; 96 | -------------------------------------------------------------------------------- /contracts/integration/LockboxAdapterBlast.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 5 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import { IXERC20 } from "../shared/IXERC20/IXERC20.sol"; 7 | import { IXERC20Lockbox } from "../shared/IXERC20/IXERC20Lockbox.sol"; 8 | 9 | interface IXERC20Registry { 10 | function getXERC20(address erc20) external view returns (address xerc20); 11 | 12 | function getERC20(address xerc20) external view returns (address erc20); 13 | 14 | function getLockbox(address erc20) external view returns (address xerc20); 15 | } 16 | 17 | interface L1StandardBridge { 18 | function bridgeERC20To( 19 | address _localToken, 20 | address _remoteToken, 21 | address _to, 22 | uint256 _amount, 23 | uint32 _minGasLimit, 24 | bytes calldata _extraData 25 | ) external; 26 | } 27 | 28 | /// @notice This adapter is only used for sending assets from Ethereum mainnet to Blast. 29 | /// @dev Combines Lockbox deposit and Blast bridge's BridgeERC20 call to minimize user transactions. 30 | contract LockboxAdapterBlast { 31 | address immutable blastStandardBridge; 32 | address immutable registry; 33 | 34 | // ERRORS 35 | error InvalidRemoteToken(address _remoteToken); 36 | error AmountLessThanZero(); 37 | error InvalidAddress(); 38 | 39 | constructor(address _blastStandardBridge, address _registry) { 40 | // Sanity check 41 | if (_blastStandardBridge == address(0) || _registry == address(0)) { 42 | revert InvalidAddress(); 43 | } 44 | 45 | blastStandardBridge = _blastStandardBridge; 46 | registry = _registry; 47 | } 48 | 49 | /// @dev Combines Lockbox deposit and Blast bridge's BridgeERC20To call. 50 | /// @param _to The recipient or contract address on destination. 51 | /// @param _erc20 The address of the adopted ERC20 on the origin chain. 52 | /// @param _remoteToken The address of the asset to be received on the destination chain. 53 | /// @param _amount The amount of asset to bridge. 54 | /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. 55 | /// @param _extraData Extra data to be sent with the transaction. 56 | function bridgeTo( 57 | address _to, 58 | address _erc20, 59 | address _remoteToken, 60 | uint256 _amount, 61 | uint32 _minGasLimit, 62 | bytes calldata _extraData 63 | ) external { 64 | // Sanity check 65 | if (_amount <= 0) { 66 | revert AmountLessThanZero(); 67 | } 68 | 69 | address xerc20 = IXERC20Registry(registry).getXERC20(_erc20); 70 | address lockbox = IXERC20Registry(registry).getLockbox(xerc20); 71 | 72 | // Sanity check 73 | if (xerc20 == address(0) || lockbox == address(0)) { 74 | revert InvalidAddress(); 75 | } 76 | 77 | // If using xERC20, the assumption is that the contract should be deployed at same address 78 | // on both networks. 79 | if (xerc20 != _remoteToken) { 80 | revert InvalidRemoteToken(_remoteToken); 81 | } 82 | 83 | SafeERC20.safeTransferFrom(IERC20(_erc20), msg.sender, address(this), _amount); 84 | SafeERC20.safeApprove(IERC20(_erc20), lockbox, _amount); 85 | IXERC20Lockbox(lockbox).deposit(_amount); 86 | SafeERC20.safeApprove(IERC20(xerc20), blastStandardBridge, _amount); 87 | L1StandardBridge(blastStandardBridge).bridgeERC20To( 88 | xerc20, 89 | _remoteToken, 90 | _to, 91 | _amount, 92 | _minGasLimit, 93 | _extraData 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/meanFinance/MeanFinanceAdapter.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { ethers } from 'hardhat' 3 | import { BigNumber, constants, Contract, utils, Wallet } from 'ethers' 4 | import { DEFAULT_ARGS } from '../../deploy' 5 | import { SwapInterval } from '../../utils/interval-utils' 6 | import { deploy, fund, ERC20_ABI } from '../helpers' 7 | 8 | describe('MeanFinanceAdapter', function () { 9 | // Set up constants (will mirror what deploy fixture uses) 10 | const { WETH, USDC } = DEFAULT_ARGS[31337] 11 | const MEAN_FINANCE_IDCAHUB = '0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345' 12 | const WHALE = '0x385BAe68690c1b86e2f1Ad75253d080C14fA6e16' // this is the address that should have weth, adapter, and random addr 13 | const NOT_ZERO_ADDRESS = '0x7088C5611dAE159A640d940cde0a3221a4af8896' 14 | const RANDOM_TOKEN = '0x4200000000000000000000000000000000000042' // this is OP 15 | const ASSET_DECIMALS = 6 // USDC decimals on op 16 | 17 | // Set up variables 18 | let adapter: Contract 19 | let wallet: Wallet 20 | let whale: Wallet 21 | let tokenA: Contract 22 | let weth: Contract 23 | let randomToken: Contract 24 | 25 | enum Permission { 26 | INCREASE, 27 | REDUCE, 28 | WITHDRAW, 29 | TERMINATE, 30 | } 31 | 32 | before(async () => { 33 | // get wallet 34 | ;[wallet] = ((await ethers.getSigners()) as unknown) as Wallet[] 35 | // get whale 36 | whale = ((await ethers.getImpersonatedSigner(WHALE)) as unknown) as Wallet 37 | 38 | const { instance } = await deploy('MeanFinanceAdapter') 39 | adapter = instance 40 | 41 | // setup tokens 42 | tokenA = new ethers.Contract(USDC, ERC20_ABI, ethers.provider) 43 | weth = new ethers.Contract(WETH, ERC20_ABI, ethers.provider) 44 | randomToken = new ethers.Contract(RANDOM_TOKEN, ERC20_ABI, ethers.provider) 45 | }) 46 | 47 | describe('constructor', () => { 48 | it('should deploy correctly', async () => { 49 | // Ensure all properties set correctly 50 | expect(await adapter.hub()).to.be.eq(MEAN_FINANCE_IDCAHUB) 51 | // Ensure whale is okay 52 | expect(whale.address).to.be.eq(WHALE) 53 | expect(tokenA.address).to.be.eq(USDC) 54 | }) 55 | }) 56 | 57 | describe('deposit', () => { 58 | before(async () => { 59 | // fund the adapter contract with eth, random token, and adapter asset 60 | await fund( 61 | constants.AddressZero, 62 | utils.parseEther('1'), 63 | wallet, 64 | adapter.address, 65 | ) 66 | 67 | await fund( 68 | USDC, 69 | utils.parseUnits('1', ASSET_DECIMALS), 70 | whale, 71 | adapter.address, 72 | ) 73 | 74 | await fund( 75 | randomToken.address, 76 | utils.parseUnits('1', await randomToken.decimals()), 77 | whale, 78 | adapter.address, 79 | ) 80 | }) 81 | 82 | it('should work', async () => { 83 | // get reasonable amount out 84 | const adapterBalance = await randomToken.balanceOf(adapter.address) 85 | 86 | const permissions = [ 87 | { operator: NOT_ZERO_ADDRESS, permissions: [Permission.INCREASE] }, 88 | ] 89 | 90 | const swaps = 10 91 | const interval = SwapInterval.ONE_DAY.seconds 92 | 93 | // send sweep tx 94 | const tx = await adapter 95 | .connect(wallet) 96 | .deposit( 97 | randomToken.address, 98 | tokenA.address, 99 | BigNumber.from(adapterBalance), 100 | swaps, 101 | interval, 102 | wallet.address, 103 | permissions, 104 | ) 105 | const receipt = await tx.wait() 106 | // Ensure tokens got sent to connext 107 | expect( 108 | (await randomToken.balanceOf(adapter.address)).toString(), 109 | ).to.be.eq('0') 110 | }) 111 | }) 112 | }) 113 | -------------------------------------------------------------------------------- /contracts/shared/Swap/libraries/BytesLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * @title Solidity Bytes Arrays Utils 4 | * @author Gonçalo Sá 5 | * 6 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. 7 | * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. 8 | */ 9 | pragma solidity >=0.5.0; 10 | 11 | library BytesLib { 12 | function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { 13 | require(_length + 31 >= _length, "slice_overflow"); 14 | require(_start + _length >= _start, "slice_overflow"); 15 | require(_bytes.length >= _start + _length, "slice_outOfBounds"); 16 | 17 | bytes memory tempBytes; 18 | 19 | assembly { 20 | switch iszero(_length) 21 | case 0 { 22 | // Get a location of some free memory and store it in tempBytes as 23 | // Solidity does for memory variables. 24 | tempBytes := mload(0x40) 25 | 26 | // The first word of the slice result is potentially a partial 27 | // word read from the original array. To read it, we calculate 28 | // the length of that partial word and start copying that many 29 | // bytes into the array. The first word we copy will start with 30 | // data we don't care about, but the last `lengthmod` bytes will 31 | // land at the beginning of the contents of the new array. When 32 | // we're done copying, we overwrite the full first word with 33 | // the actual length of the slice. 34 | let lengthmod := and(_length, 31) 35 | 36 | // The multiplication in the next line is necessary 37 | // because when slicing multiples of 32 bytes (lengthmod == 0) 38 | // the following copy loop was copying the origin's length 39 | // and then ending prematurely not copying everything it should. 40 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 41 | let end := add(mc, _length) 42 | 43 | for { 44 | // The multiplication in the next line has the same exact purpose 45 | // as the one above. 46 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) 47 | } lt(mc, end) { 48 | mc := add(mc, 0x20) 49 | cc := add(cc, 0x20) 50 | } { 51 | mstore(mc, mload(cc)) 52 | } 53 | 54 | mstore(tempBytes, _length) 55 | 56 | //update free-memory pointer 57 | //allocating the array padded to 32 bytes like the compiler does now 58 | mstore(0x40, and(add(mc, 31), not(31))) 59 | } 60 | //if we want a zero-length slice let's just return a zero-length array 61 | default { 62 | tempBytes := mload(0x40) 63 | //zero out the 32 bytes slice we are about to return 64 | //we need to do it because Solidity does not garbage collect 65 | mstore(tempBytes, 0) 66 | 67 | mstore(0x40, add(tempBytes, 0x20)) 68 | } 69 | } 70 | 71 | return tempBytes; 72 | } 73 | 74 | function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { 75 | require(_start + 20 >= _start, "toAddress_overflow"); 76 | require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); 77 | address tempAddress; 78 | 79 | assembly { 80 | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) 81 | } 82 | 83 | return tempAddress; 84 | } 85 | 86 | function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { 87 | require(_start + 3 >= _start, "toUint24_overflow"); 88 | require(_bytes.length >= _start + 3, "toUint24_outOfBounds"); 89 | uint24 tempUint; 90 | 91 | assembly { 92 | tempUint := mload(add(add(_bytes, 0x3), _start)) 93 | } 94 | 95 | return tempUint; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /contracts/shared/IXERC20/IXERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.4 <0.9.0; 3 | 4 | interface IXERC20 { 5 | /** 6 | * @notice Emits when a lockbox is set 7 | * 8 | * @param _lockbox The address of the lockbox 9 | */ 10 | 11 | event LockboxSet(address _lockbox); 12 | 13 | /** 14 | * @notice Emits when a limit is set 15 | * 16 | * @param _mintingLimit The updated minting limit we are setting to the bridge 17 | * @param _burningLimit The updated burning limit we are setting to the bridge 18 | * @param _bridge The address of the bridge we are setting the limit too 19 | */ 20 | event BridgeLimitsSet(uint256 _mintingLimit, uint256 _burningLimit, address indexed _bridge); 21 | 22 | /** 23 | * @notice Reverts when a user with too low of a limit tries to call mint/burn 24 | */ 25 | 26 | error IXERC20_NotHighEnoughLimits(); 27 | 28 | /** 29 | * @notice Reverts when caller is not the factory 30 | */ 31 | 32 | error IXERC20_NotFactory(); 33 | 34 | struct Bridge { 35 | BridgeParameters minterParams; 36 | BridgeParameters burnerParams; 37 | } 38 | 39 | struct BridgeParameters { 40 | uint256 timestamp; 41 | uint256 ratePerSecond; 42 | uint256 maxLimit; 43 | uint256 currentLimit; 44 | } 45 | 46 | /** 47 | * @notice Sets the lockbox address 48 | * 49 | * @param _lockbox The address of the lockbox 50 | */ 51 | 52 | function setLockbox(address _lockbox) external; 53 | 54 | /** 55 | * @notice Updates the limits of any bridge 56 | * @dev Can only be called by the owner 57 | * @param _mintingLimit The updated minting limit we are setting to the bridge 58 | * @param _burningLimit The updated burning limit we are setting to the bridge 59 | * @param _bridge The address of the bridge we are setting the limits too 60 | */ 61 | function setLimits(address _bridge, uint256 _mintingLimit, uint256 _burningLimit) external; 62 | 63 | /** 64 | * @notice The address of the lockbox contract 65 | */ 66 | function lockbox() external view returns (address); 67 | 68 | /** 69 | * @notice Returns the max limit of a minter 70 | * 71 | * @param _minter The minter we are viewing the limits of 72 | * @return _limit The limit the minter has 73 | */ 74 | function mintingMaxLimitOf(address _minter) external view returns (uint256 _limit); 75 | 76 | /** 77 | * @notice Returns the max limit of a bridge 78 | * 79 | * @param _bridge the bridge we are viewing the limits of 80 | * @return _limit The limit the bridge has 81 | */ 82 | 83 | function burningMaxLimitOf(address _bridge) external view returns (uint256 _limit); 84 | 85 | /** 86 | * @notice Returns the current limit of a minter 87 | * 88 | * @param _minter The minter we are viewing the limits of 89 | * @return _limit The limit the minter has 90 | */ 91 | 92 | function mintingCurrentLimitOf(address _minter) external view returns (uint256 _limit); 93 | 94 | /** 95 | * @notice Returns the current limit of a bridge 96 | * 97 | * @param _bridge the bridge we are viewing the limits of 98 | * @return _limit The limit the bridge has 99 | */ 100 | 101 | function burningCurrentLimitOf(address _bridge) external view returns (uint256 _limit); 102 | 103 | /** 104 | * @notice Mints tokens for a user 105 | * @dev Can only be called by a minter 106 | * @param _user The address of the user who needs tokens minted 107 | * @param _amount The amount of tokens being minted 108 | */ 109 | 110 | function mint(address _user, uint256 _amount) external; 111 | 112 | /** 113 | * @notice Burns tokens for a user 114 | * @dev Can only be called by a minter 115 | * @param _user The address of the user who needs tokens burned 116 | * @param _amount The amount of tokens being burned 117 | */ 118 | 119 | function burn(address _user, uint256 _amount) external; 120 | } 121 | -------------------------------------------------------------------------------- /test/swapAdapter/SwapAdapter.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers } from "hardhat"; 3 | import { BigNumber, constants, Contract, utils, Wallet } from "ethers"; 4 | import { DEFAULT_ARGS } from "../../deploy"; 5 | import { fund, deploy, ERC20_ABI } from "../helpers"; 6 | import ISwapAdapter from "../../artifacts/contracts/xreceivers/Swap/SwapAdapter.sol/SwapAdapter.json"; 7 | 8 | describe("SwapAdapter", function () { 9 | // Set up constants (will mirror what deploy fixture uses) 10 | const { WETH, USDC } = DEFAULT_ARGS[31337]; 11 | const UNISWAP_SWAP_ROUTER = "0xE592427A0AEce92De3Edee1F18E0157C05861564"; 12 | const WHALE = "0x385BAe68690c1b86e2f1Ad75253d080C14fA6e16"; // this is the address that should have weth, adapter, and random addr 13 | const UNPERMISSIONED = "0x7088C5611dAE159A640d940cde0a3221a4af8896"; 14 | const RANDOM_TOKEN = "0x4200000000000000000000000000000000000042"; // this is OP 15 | const ASSET_DECIMALS = 6; // USDC decimals on op 16 | 17 | // Set up variables 18 | let adapter: Contract; 19 | let wallet: Wallet; 20 | let whale: Wallet; 21 | let unpermissioned: Wallet; 22 | let tokenA: Contract; 23 | let weth: Contract; 24 | let randomToken: Contract; 25 | 26 | before(async () => { 27 | // get wallet 28 | [wallet] = (await ethers.getSigners()) as unknown as Wallet[]; 29 | // get whale 30 | whale = (await ethers.getImpersonatedSigner(WHALE)) as unknown as Wallet; 31 | // get unpermissioned 32 | unpermissioned = (await ethers.getImpersonatedSigner(UNPERMISSIONED)) as unknown as Wallet; 33 | // deploy contract 34 | const { instance } = await deploy("SwapAdapter"); 35 | adapter = instance; 36 | // setup tokens 37 | tokenA = new ethers.Contract(USDC, ERC20_ABI, ethers.provider); 38 | weth = new ethers.Contract(WETH, ERC20_ABI, ethers.provider); 39 | randomToken = new ethers.Contract(RANDOM_TOKEN, ERC20_ABI, ethers.provider); 40 | }); 41 | 42 | describe("constructor", () => { 43 | it("should deploy correctly", async () => { 44 | // Ensure all properties set correctly 45 | // Ensure whale is okay 46 | expect(whale.address).to.be.eq(WHALE); 47 | expect(tokenA.address).to.be.eq(USDC); 48 | }); 49 | }); 50 | 51 | describe("_exactSwap", () => { 52 | before(async () => { 53 | // // fund the adapter contract with eth, random token, and adapter asset 54 | await fund(constants.AddressZero, utils.parseEther("1"), wallet, adapter.address); 55 | await fund(USDC, utils.parseUnits("1", ASSET_DECIMALS), whale, adapter.address); 56 | await fund(randomToken.address, utils.parseUnits("1", await randomToken.decimals()), whale, adapter.address); 57 | }); 58 | 59 | it("should work", async () => { 60 | // get initial connext balances 61 | // send sweep tx 62 | 63 | const functionName = "uniswapV3ExactInputSingle"; 64 | const iface = new utils.Interface(ISwapAdapter.abi); 65 | const fragment = iface.getFunction(functionName); 66 | const selector = iface.getSighash(fragment); 67 | 68 | const adapterBalance = await randomToken.balanceOf(adapter.address); 69 | const decimals = await randomToken.decimals(); 70 | const normalized = 71 | decimals > ASSET_DECIMALS 72 | ? adapterBalance.div(BigNumber.from(10).pow(decimals - ASSET_DECIMALS)) 73 | : adapterBalance.mul(BigNumber.from(10).pow(ASSET_DECIMALS - decimals)); 74 | // use 0.1% slippage (OP is > $2, adapter = usdc) 75 | const lowerBound = normalized.mul(10).div(10_000); 76 | 77 | const abi = utils.defaultAbiCoder; 78 | const swapData = abi.encode( 79 | ["address", "address", "uint24", "uint256", "uint256", "address"], 80 | [randomToken.address, tokenA.address, 3000, adapterBalance, lowerBound, adapter.address], 81 | ); 82 | 83 | const tx = await adapter.connect(wallet)._exactSwap(UNISWAP_SWAP_ROUTER, selector, swapData); 84 | const receipt = await tx.wait(); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /contracts/test/unit/shared/Swap/SwapAdapter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.19; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {SwapAdapter} from "../../../../shared/Swap/SwapAdapter.sol"; 6 | import {ISwapper} from "../../../../shared/Swap/interfaces/ISwapper.sol"; 7 | import {TestHelper} from "../../../utils/TestHelper.sol"; 8 | 9 | contract SwapAdapterTest is TestHelper { 10 | // ============ Storage ============ 11 | SwapAdapter swapAdapter; 12 | address owner = address(0x1); 13 | 14 | // ============ Test set up ============ 15 | function setUp() public override { 16 | super.setUp(); 17 | 18 | vm.prank(owner); 19 | swapAdapter = new SwapAdapter(); 20 | } 21 | 22 | // ============ SwapAdapter.addSwapper ============ 23 | function test_SwapAdapter__addSwapper_revertIfNotOwner() public { 24 | address swapper = address(0x111); 25 | vm.prank(address(0x2)); 26 | vm.expectRevert(bytes("Ownable: caller is not the owner")); 27 | swapAdapter.addSwapper(swapper); 28 | assertEq(swapAdapter.allowedSwappers(swapper), false); 29 | } 30 | 31 | function test_SwapAdapter__addSwapper_shouldWork() public { 32 | address swapper = address(0x111); 33 | vm.prank(owner); 34 | swapAdapter.addSwapper(swapper); 35 | assertEq(swapAdapter.allowedSwappers(swapper), true); 36 | } 37 | 38 | // ============ SwapAdapter.removeSwapper ============ 39 | function test_SwapAdapter__removeSwapper_revertIfNotOwner() public { 40 | address swapper = address(0x111); 41 | vm.prank(owner); 42 | swapAdapter.addSwapper(swapper); 43 | assertEq(swapAdapter.allowedSwappers(swapper), true); 44 | 45 | vm.prank(address(0x2)); 46 | vm.expectRevert(bytes("Ownable: caller is not the owner")); 47 | swapAdapter.removeSwapper(swapper); 48 | assertEq(swapAdapter.allowedSwappers(swapper), true); 49 | } 50 | 51 | function test_SwapAdapter__removeSwapper_shouldWork() public { 52 | address swapper = address(0x111); 53 | vm.prank(owner); 54 | swapAdapter.addSwapper(swapper); 55 | assertEq(swapAdapter.allowedSwappers(swapper), true); 56 | 57 | vm.prank(owner); 58 | swapAdapter.removeSwapper(swapper); 59 | assertEq(swapAdapter.allowedSwappers(swapper), false); 60 | } 61 | 62 | // ============ SwapAdapter.exactSwap ============ 63 | function test_SwapAdapter__exactSwap_revertIfNotAllowedSwap() public { 64 | address swapper = address(0x111); 65 | vm.mockCall(swapper, abi.encodeWithSelector(ISwapper.swap.selector), abi.encode(100)); 66 | 67 | uint256 amountIn = 100; 68 | address fromAsset = address(0x12345); 69 | address toAsset = address(0x54321); 70 | bytes memory swapData = bytes("0x"); 71 | vm.expectRevert(bytes("!allowedSwapper")); 72 | swapAdapter.exactSwap(swapper, amountIn, fromAsset, toAsset, swapData); 73 | } 74 | 75 | function test_SwapAdapter__exactSwap_shouldWork() public { 76 | address swapper = address(0x111); 77 | vm.prank(owner); 78 | swapAdapter.addSwapper(swapper); 79 | 80 | uint256 amountIn = 100; 81 | address fromAsset = address(0x12345); 82 | address toAsset = address(0x54321); 83 | bytes memory swapData = bytes("0x"); 84 | 85 | vm.mockCall(fromAsset, abi.encodeWithSelector(IERC20.allowance.selector), abi.encode(0)); 86 | vm.mockCall(fromAsset, abi.encodeWithSelector(IERC20.approve.selector), abi.encode(true)); 87 | vm.mockCall(swapper, abi.encodeWithSelector(ISwapper.swap.selector), abi.encode(100)); 88 | 89 | swapAdapter.exactSwap(swapper, amountIn, fromAsset, toAsset, swapData); 90 | } 91 | 92 | // ============ SwapAdapter.directSwapperCall ============ 93 | function testFail_SwapAdapter__directSwapperCall_shouldWork() public { 94 | address swapper = address(0x111); 95 | vm.prank(owner); 96 | swapAdapter.addSwapper(swapper); 97 | bytes memory swapData = bytes("0x"); 98 | swapAdapter.directSwapperCall(swapper, swapData); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/shared/Swap/SwapAdapter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7 <0.9.0; 3 | 4 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 5 | import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; 6 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 8 | 9 | import {ISwapper} from "./interfaces/ISwapper.sol"; 10 | 11 | /** 12 | * @title SwapAdapter 13 | * @author Connext 14 | * @notice This contract is used to provide a generic interface to swap tokens through 15 | * a variety of different swap routers. It is used to swap tokens 16 | * before proceeding with other actions. Swap router implementations can be added by owner. 17 | * This is designed to be owned by the Connext DAO and swappers can be added by the DAO. 18 | */ 19 | contract SwapAdapter is Ownable2Step { 20 | using Address for address; 21 | using Address for address payable; 22 | 23 | mapping(address => bool) public allowedSwappers; 24 | 25 | address public immutable uniswapSwapRouter = address(0xE592427A0AEce92De3Edee1F18E0157C05861564); 26 | 27 | constructor() { 28 | allowedSwappers[address(this)] = true; 29 | allowedSwappers[uniswapSwapRouter] = true; 30 | } 31 | 32 | /// Payable 33 | // @dev On the origin side, we can accept native assets for a swap. 34 | receive() external payable virtual {} 35 | 36 | /// ADMIN 37 | /** 38 | * @notice Add a swapper to the list of allowed swappers. 39 | * @param _swapper Address of the swapper to add. 40 | */ 41 | function addSwapper(address _swapper) external onlyOwner { 42 | allowedSwappers[_swapper] = true; 43 | } 44 | 45 | /** 46 | * @notice Remove a swapper from the list of allowed swappers. 47 | * @param _swapper Address of the swapper to remove. 48 | */ 49 | function removeSwapper(address _swapper) external onlyOwner { 50 | allowedSwappers[_swapper] = false; 51 | } 52 | 53 | /// EXTERNAL 54 | /** 55 | * @notice Swap an exact amount of tokens for another token. 56 | * @param _swapper Address of the swapper to use. 57 | * @param _amountIn Amount of tokens to swap. 58 | * @param _fromAsset Address of the token to swap from. 59 | * @param _toAsset Address of the token to swap to. 60 | * @param _swapData Data to pass to the swapper. This data is encoded for a particular swap router, usually given 61 | * by an API. The swapper will decode the data and re-encode it with the new amountIn. 62 | */ 63 | function exactSwap( 64 | address _swapper, 65 | uint256 _amountIn, 66 | address _fromAsset, 67 | address _toAsset, 68 | bytes calldata _swapData // comes directly from API with swap data encoded 69 | ) external payable returns (uint256 amountOut) { 70 | require(allowedSwappers[_swapper], "!allowedSwapper"); 71 | 72 | // If from == to, no need to swap 73 | if (_fromAsset == _toAsset) { 74 | return _amountIn; 75 | } 76 | 77 | if (_fromAsset == address(0)) { 78 | amountOut = ISwapper(_swapper).swapETH(_amountIn, _toAsset, _swapData); 79 | } else { 80 | if (IERC20(_fromAsset).allowance(address(this), _swapper) < _amountIn) { 81 | TransferHelper.safeApprove(_fromAsset, _swapper, type(uint256).max); 82 | } 83 | amountOut = ISwapper(_swapper).swap(_amountIn, _fromAsset, _toAsset, _swapData); 84 | } 85 | } 86 | 87 | /** 88 | * @notice Swap an exact amount of tokens for another token. Uses a direct call to the swapper to allow 89 | * easy swaps on the source side where the amount does not need to be changed. 90 | * @param _swapper Address of the swapper to use. 91 | * @param swapData Data to pass to the swapper. This data is encoded for a particular swap router. 92 | */ 93 | function directSwapperCall(address _swapper, bytes calldata swapData) external payable returns (uint256 amountOut) { 94 | bytes memory ret = _swapper.functionCallWithValue(swapData, msg.value, "!directSwapperCallFailed"); 95 | amountOut = abi.decode(ret, (uint256)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /contracts/integration/Instadapp/InstadappTarget.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {IConnext} from "@connext/interfaces/core/IConnext.sol"; 5 | import {IXReceiver} from "@connext/interfaces/core/IXReceiver.sol"; 6 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8 | import {InstadappAdapter} from "./InstadappAdapter.sol"; 9 | 10 | /// @title InstadappTarget 11 | /// @author Connext 12 | /// @notice You can use this contract for cross-chain casting via dsa address 13 | /// @dev This contract is used to receive funds from Connext 14 | /// and forward them to Instadapp DSA via authCast function, In case of failure, 15 | /// funds are forwarded to fallback address defined by the user under callData. 16 | /// @custom:experimental This is an experimental contract. 17 | contract InstadappTarget is IXReceiver, InstadappAdapter { 18 | using SafeERC20 for IERC20; 19 | /// Storage 20 | /// @dev This is the address of the Connext contract. 21 | IConnext public immutable connext; 22 | 23 | /// Events 24 | /// @dev This event is emitted when the authCast function is called. 25 | event AuthCast( 26 | bytes32 indexed transferId, 27 | address indexed dsaAddress, 28 | bool indexed success, 29 | address auth, 30 | bytes returnedData 31 | ); 32 | 33 | /// Modifiers 34 | /// @dev This modifier is used to ensure that only the Connext contract can call the function. 35 | modifier onlyConnext() { 36 | require(msg.sender == address(connext), "Caller must be Connext"); 37 | _; 38 | } 39 | 40 | /// Constructor 41 | /// @param _connext The address of the Connext contract. 42 | constructor(address _connext) { 43 | connext = IConnext(_connext); 44 | } 45 | 46 | /// Public functions 47 | /// @dev This function is used to receive funds from Connext and forward them to DSA. 48 | /// Then it forwards the call to authCast function. 49 | /// @param _amount The amount of funds that will be received. 50 | /// @param _asset The address of the asset that will be received. 51 | /// @param _transferId The id of the transfer. 52 | /// @param _callData The data that will be sent to the targets. 53 | function xReceive( 54 | bytes32 _transferId, 55 | uint256 _amount, 56 | address _asset, 57 | address, 58 | uint32, 59 | bytes memory _callData 60 | ) external onlyConnext returns (bytes memory) { 61 | // Decode signed calldata 62 | // dsaAddress: address of DSA contract 63 | // auth: address of Authority, which whitelisted at dsaContract. 64 | // signature: signature is signed by the auth includes the castData with salt. 65 | // castData: CastData required for execution at destination 66 | // salt: salt for Signature Replay Protection, which is unique to each signature signed by auth. 67 | // deadline: deadline for the cast to be valid 68 | ( 69 | address dsaAddress, 70 | address auth, 71 | bytes memory _signature, 72 | CastData memory _castData, 73 | bytes32 salt, 74 | uint256 deadline 75 | ) = abi.decode(_callData, (address, address, bytes, CastData, bytes32, uint256)); 76 | 77 | // verify the dsaAddress 78 | require(dsaAddress != address(0), "!invalidFallback"); 79 | 80 | // transfer funds to this dsaAddress 81 | SafeERC20.safeTransfer(IERC20(_asset), dsaAddress, _amount); 82 | 83 | // forward call to AuthCast 84 | // calling via encodeWithSignature as alternative to try/catch 85 | (bool success, bytes memory returnedData) = address(this).call( 86 | abi.encodeWithSignature( 87 | "authCast(address,address,bytes,CastData,bytes32,uint256)", 88 | dsaAddress, 89 | auth, 90 | _signature, 91 | _castData, 92 | salt, 93 | deadline 94 | ) 95 | ); 96 | 97 | emit AuthCast(_transferId, dsaAddress, success, auth, returnedData); 98 | 99 | return returnedData; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/uniswap/UniswapAdapter.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers } from "hardhat"; 3 | import { BigNumber, constants, Contract, utils, Wallet } from "ethers"; 4 | import { DEFAULT_ARGS } from "../../deploy"; 5 | import { fund, deploy, ERC20_ABI } from "../helpers"; 6 | 7 | describe("UniswapAdapter", function () { 8 | // Set up constants (will mirror what deploy fixture uses) 9 | const { WETH, USDC } = DEFAULT_ARGS[31337]; 10 | const UNISWAP_SWAP_ROUTER = "0xE592427A0AEce92De3Edee1F18E0157C05861564"; 11 | const WHALE = "0x385BAe68690c1b86e2f1Ad75253d080C14fA6e16"; // this is the address that should have weth, adapter, and random addr 12 | const UNPERMISSIONED = "0x7088C5611dAE159A640d940cde0a3221a4af8896"; 13 | const RANDOM_TOKEN = "0x4200000000000000000000000000000000000042"; // this is OP 14 | const ASSET_DECIMALS = 6; // USDC decimals on op 15 | 16 | // Set up variables 17 | let adapter: Contract; 18 | let wallet: Wallet; 19 | let whale: Wallet; 20 | let unpermissioned: Wallet; 21 | let tokenA: Contract; 22 | let weth: Contract; 23 | let randomToken: Contract; 24 | 25 | before(async () => { 26 | // get wallet 27 | [wallet] = (await ethers.getSigners()) as unknown as Wallet[]; 28 | // get whale 29 | whale = (await ethers.getImpersonatedSigner(WHALE)) as unknown as Wallet; 30 | // get unpermissioned 31 | unpermissioned = (await ethers.getImpersonatedSigner( 32 | UNPERMISSIONED 33 | )) as unknown as Wallet; 34 | // deploy contract 35 | const { instance } = await deploy("UniswapAdapter"); 36 | adapter = instance; 37 | // setup tokens 38 | tokenA = new ethers.Contract(USDC, ERC20_ABI, ethers.provider); 39 | weth = new ethers.Contract(WETH, ERC20_ABI, ethers.provider); 40 | randomToken = new ethers.Contract(RANDOM_TOKEN, ERC20_ABI, ethers.provider); 41 | }); 42 | 43 | describe("constructor", () => { 44 | it("should deploy correctly", async () => { 45 | // Ensure all properties set correctly 46 | expect(await adapter.swapRouter()).to.be.eq(UNISWAP_SWAP_ROUTER); 47 | // Ensure whale is okay 48 | expect(whale.address).to.be.eq(WHALE); 49 | expect(tokenA.address).to.be.eq(USDC); 50 | }); 51 | }); 52 | 53 | describe("swap", () => { 54 | before(async () => { 55 | // fund the adapter contract with eth, random token, and adapter asset 56 | await fund( 57 | constants.AddressZero, 58 | utils.parseEther("1"), 59 | wallet, 60 | adapter.address 61 | ); 62 | 63 | await fund( 64 | USDC, 65 | utils.parseUnits("1", ASSET_DECIMALS), 66 | whale, 67 | adapter.address 68 | ); 69 | 70 | await fund( 71 | randomToken.address, 72 | utils.parseUnits("1", await randomToken.decimals()), 73 | whale, 74 | adapter.address 75 | ); 76 | }); 77 | 78 | it("should work for adapter asset", async () => { 79 | // get initial connext balances 80 | const initBalance = await tokenA.balanceOf(adapter.address); 81 | 82 | // get reasonable amount out 83 | const adapterBalance = await randomToken.balanceOf(adapter.address); 84 | const randomDecimals = await randomToken.decimals(); 85 | const normalized = 86 | randomDecimals > ASSET_DECIMALS 87 | ? adapterBalance.div( 88 | BigNumber.from(10).pow(randomDecimals - ASSET_DECIMALS) 89 | ) 90 | : adapterBalance.mul( 91 | BigNumber.from(10).pow(ASSET_DECIMALS - randomDecimals) 92 | ); 93 | // use 0.1% slippage (OP is > $2, adapter = usdc) 94 | const lowerBound = normalized.mul(10).div(10_000); 95 | 96 | // send sweep tx 97 | const tx = await adapter 98 | .connect(wallet) 99 | .swap( 100 | randomToken.address, 101 | tokenA.address, 102 | 3000, 103 | adapterBalance, 104 | lowerBound 105 | ); 106 | const receipt = await tx.wait(); 107 | // Ensure tokens got sent to connext 108 | expect( 109 | (await randomToken.balanceOf(adapter.address)).toString() 110 | ).to.be.eq("0"); 111 | // Only asserting balance increased 112 | expect((await tokenA.balanceOf(adapter.address)).gt(initBalance)).to.be 113 | .true; 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /contracts/shared/Swap/Uniswap/UniV2Swapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {IUniswapV2Router02} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; 6 | import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 7 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 8 | 9 | import {ISwapper} from "../interfaces/ISwapper.sol"; 10 | 11 | /** 12 | * @title UniV2Swapper 13 | * @notice Swapper contract for UniswapV2 swaps. 14 | */ 15 | contract UniV2Swapper is ISwapper { 16 | using Address for address; 17 | 18 | IUniswapV2Router02 public immutable uniswapV2Router; 19 | 20 | constructor(address _uniV2Router) { 21 | uniswapV2Router = IUniswapV2Router02(_uniV2Router); 22 | } 23 | 24 | /** 25 | * @notice Swap the given amount of tokens using UniV2. 26 | * @dev Decode the passed in data and re-encode it with the correct amountIn. This is because the amountIn is not known 27 | * until the funds are transferred to this contract. 28 | * @param _amountIn Amount of tokens to swap. 29 | * @param _fromAsset Address of the token to swap from. 30 | * @param _toAsset Address of the token to swap to. 31 | * @param _swapData Data to pass to the UniV2 router. 32 | */ 33 | function swap( 34 | uint256 _amountIn, 35 | address _fromAsset, 36 | address _toAsset, 37 | bytes calldata _swapData 38 | ) external override returns (uint256 amountOut) { 39 | // transfer the funds to be swapped from the sender into this contract 40 | TransferHelper.safeTransferFrom(_fromAsset, msg.sender, address(this), _amountIn); 41 | 42 | if (_fromAsset != _toAsset) { 43 | (uint256 amountOutMin, address[] memory path) = abi.decode(_swapData, (uint256, address[])); 44 | 45 | bool toNative = _toAsset == address(0); 46 | address toAsset = toNative ? uniswapV2Router.WETH() : _toAsset; 47 | 48 | uint256 length = path.length; 49 | if (length > 1) { 50 | require(path[0] == _fromAsset && path[length - 1] == toAsset, "UniV2Swapper: invalid path"); 51 | } else { 52 | path = new address[](2); 53 | path[0] = _fromAsset; 54 | path[1] = toAsset; 55 | } 56 | 57 | TransferHelper.safeApprove(_fromAsset, address(uniswapV2Router), _amountIn); 58 | 59 | uint[] memory amounts; 60 | if (!toNative) { 61 | amounts = uniswapV2Router.swapExactTokensForTokens(_amountIn, amountOutMin, path, msg.sender, block.timestamp); 62 | } else { 63 | amounts = uniswapV2Router.swapExactTokensForETH(_amountIn, amountOutMin, path, msg.sender, block.timestamp); 64 | } 65 | amountOut = amounts[amounts.length - 1]; 66 | } 67 | } 68 | 69 | /** 70 | * @notice Swap the given amount of ETH using UniV2. 71 | * @dev Decode the passed in data and re-encode it with the correct amountIn. This is because the amountIn is not known 72 | * until the funds are transferred to this contract. 73 | * @param _amountIn Amount of tokens to swap. 74 | * @param _toAsset Address of the token to swap to. 75 | * @param _swapData Data to pass to the UniV2 router. 76 | */ 77 | function swapETH( 78 | uint256 _amountIn, 79 | address _toAsset, 80 | bytes calldata _swapData 81 | ) public payable override returns (uint256 amountOut) { 82 | // check if msg.value is same as amountIn 83 | require(msg.value == _amountIn, "UniV2Swapper: msg.value != _amountIn"); 84 | 85 | if (_toAsset != address(0)) { 86 | (uint256 amountOutMin, address[] memory path) = abi.decode(_swapData, (uint256, address[])); 87 | 88 | address weth = uniswapV2Router.WETH(); 89 | 90 | uint256 length = path.length; 91 | if (length > 1) { 92 | require(path[length - 1] == _toAsset, "UniV2Swapper: invalid path"); 93 | } else { 94 | path = new address[](2); 95 | path[0] = weth; 96 | path[1] = _toAsset; 97 | } 98 | 99 | uint[] memory amounts = uniswapV2Router.swapExactETHForTokens{value: _amountIn}( 100 | amountOutMin, 101 | path, 102 | msg.sender, 103 | block.timestamp 104 | ); 105 | amountOut = amounts[amounts.length - 1]; 106 | } else { 107 | amountOut = _amountIn; 108 | TransferHelper.safeTransferETH(msg.sender, amountOut); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /contracts/xtoken/XERC20Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {ERC20PermitUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; 5 | import {ProposedOwnableUpgradeable} from "../shared/ownership/ProposedOwnableUpgradeable.sol"; 6 | 7 | /** 8 | * @title XERC20Upgradeable 9 | * @author Connext Labs 10 | * @notice This is a simple implementation of an xToken to use within Connext. An xToken is a minimal extension to the 11 | * ERC-20 standard that enables bridging tokens across domains without creating multiple infungible representations of 12 | * the same underlying asset. 13 | * 14 | * To learn more, please see: 15 | * - EIP: 16 | * https://github.com/connext/EIPs/blob/master/EIPS/eip-draft_bridged_tokens.md 17 | * - Guide to whitelist an xtoken on Connext: 18 | * https://connext.notion.site/Public-xTokens-Setup-Guide-be4e136a6db14191b8d61bd60563ebd0?pvs=4 19 | * 20 | * @dev This contract is designed to be upgradeable so as the EIP is finalized, the implementation 21 | * can be updated to reflect the standard. The current implementation is the minimal interface 22 | * required to create an xtoken supported by Connext. 23 | */ 24 | contract XERC20Upgradeable is ERC20PermitUpgradeable, ProposedOwnableUpgradeable { 25 | // ======== Events ========= 26 | /** 27 | * Emitted when bridge is whitelisted 28 | * @param bridge Address of the bridge being added 29 | */ 30 | event BridgeAdded(address indexed bridge); 31 | 32 | /** 33 | * Emitted when bridge is dropped from whitelist 34 | * @param bridge Address of the bridge being added 35 | */ 36 | event BridgeRemoved(address indexed bridge); 37 | 38 | // ======== Constants ========= 39 | 40 | // ======== Storage ========= 41 | /** 42 | * @notice The set of whitelisted bridges 43 | */ 44 | mapping(address => bool) internal _whitelistedBridges; 45 | 46 | // ======== Constructor ========= 47 | constructor() {} 48 | 49 | // ======== Initializer ========= 50 | 51 | function initialize(address _owner, string memory _name, string memory _symbol) public initializer { 52 | __XERC20_init(); 53 | __ERC20_init(_name, _symbol); 54 | __ERC20Permit_init(_name); 55 | __ProposedOwnable_init(); 56 | 57 | // Set specified owner 58 | _setOwner(_owner); 59 | } 60 | 61 | /** 62 | * @dev Initializes XERC20 instance 63 | */ 64 | function __XERC20_init() internal onlyInitializing { 65 | __XERC20_init_unchained(); 66 | } 67 | 68 | function __XERC20_init_unchained() internal onlyInitializing {} 69 | 70 | // ======== Errors ========= 71 | error XERC20__onlyBridge_notBridge(); 72 | error XERC20__addBridge_alreadyAdded(); 73 | error XERC20__removeBridge_alreadyRemoved(); 74 | 75 | // ============ Modifiers ============== 76 | modifier onlyBridge() { 77 | if (!_whitelistedBridges[msg.sender]) { 78 | revert XERC20__onlyBridge_notBridge(); 79 | } 80 | _; 81 | } 82 | 83 | // ========= Admin Functions ========= 84 | /** 85 | * @notice Adds a bridge to the whitelist 86 | * @param _bridge Address of the bridge to add 87 | */ 88 | function addBridge(address _bridge) external onlyOwner { 89 | if (_whitelistedBridges[_bridge]) { 90 | revert XERC20__addBridge_alreadyAdded(); 91 | } 92 | emit BridgeAdded(_bridge); 93 | _whitelistedBridges[_bridge] = true; 94 | } 95 | 96 | /** 97 | * @notice Removes a bridge from the whitelist 98 | * @param _bridge Address of the bridge to remove 99 | */ 100 | function removeBridge(address _bridge) external onlyOwner { 101 | if (!_whitelistedBridges[_bridge]) { 102 | revert XERC20__removeBridge_alreadyRemoved(); 103 | } 104 | emit BridgeRemoved(_bridge); 105 | _whitelistedBridges[_bridge] = false; 106 | } 107 | 108 | // ========= Public Functions ========= 109 | 110 | /** 111 | * @notice Mints tokens for a given address 112 | * @param _to Address to mint to 113 | * @param _amount Amount to mint 114 | */ 115 | function mint(address _to, uint256 _amount) public onlyBridge { 116 | _mint(_to, _amount); 117 | } 118 | 119 | /** 120 | * @notice Mints tokens for a given address 121 | * @param _from Address to burn from 122 | * @param _amount Amount to mint 123 | */ 124 | function burn(address _from, uint256 _amount) public onlyBridge { 125 | _burn(_from, _amount); 126 | } 127 | 128 | // ============ Upgrade Gap ============ 129 | uint256[49] private __GAP; // gap for upgrade safety 130 | } 131 | -------------------------------------------------------------------------------- /contracts/test/utils/TestHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import "forge-std/StdCheats.sol"; 6 | 7 | contract TestHelper is StdCheats, Test { 8 | /// Testnet Domain IDs 9 | uint32 public GOERLI_DOMAIN_ID = 1735353714; 10 | uint32 public OPTIMISM_GOERLI_DOMAIN_ID = 1735356532; 11 | uint32 public ARBITRUM_GOERLI_DOMAIN_ID = 1734439522; 12 | uint32 public POLYGON_MUMBAI_DOMAIN_ID = 9991; 13 | 14 | /// Testnet Chain IDs 15 | uint32 public GOERLI_CHAIN_ID = 5; 16 | uint32 public OPTIMISM_GOERLI_CHAIN_ID = 420; 17 | uint32 public ARBITRUM_GOERLI_CHAIN_ID = 421613; 18 | uint32 public POLYGON_MUMBAI_CHAIN_ID = 80001; 19 | 20 | /// Mainnet Domain IDs 21 | uint32 public ETHEREUM_DOMAIN_ID = 6648936; 22 | uint32 public ARBITRUM_DOMAIN_ID = 1634886255; 23 | uint32 public OPTIMISM_DOMAIN_ID = 1869640809; 24 | uint32 public BNB_DOMAIN_ID = 6450786; 25 | uint32 public POLYGON_DOMAIN_ID = 1886350457; 26 | 27 | /// Mainnet Chain IDs 28 | uint32 public ARBITRUM_CHAIN_ID = 42161; 29 | uint32 public OPTIMISM_CHAIN_ID = 10; 30 | 31 | // Live Addresses 32 | address public CONNEXT_ETHEREUM = 0x8898B472C54c31894e3B9bb83cEA802a5d0e63C6; 33 | address public CONNEXT_ARBITRUM = 0xEE9deC2712cCE65174B561151701Bf54b99C24C8; 34 | address public CONNEXT_OPTIMISM = 0x8f7492DE823025b4CfaAB1D34c58963F2af5DEDA; 35 | address public CONNEXT_BNB = 0xCd401c10afa37d641d2F594852DA94C700e4F2CE; 36 | address public CONNEXT_POLYGON = 0x11984dc4465481512eb5b777E44061C158CF2259; 37 | 38 | // Forks 39 | uint256 public ethereumForkId; 40 | uint256 public arbitrumForkId; 41 | uint256 public optimismForkId; 42 | uint256 public bnbForkId; 43 | uint256 public polygonForkId; 44 | 45 | /// Mock Addresses 46 | address public USER_CHAIN_A = address(bytes20(keccak256("USER_CHAIN_A"))); 47 | address public USER_CHAIN_B = address(bytes20(keccak256("USER_CHAIN_B"))); 48 | address public MOCK_CONNEXT = address(bytes20(keccak256("MOCK_CONNEXT"))); 49 | address public TokenA_ERC20 = address(bytes20(keccak256("TokenA_ERC20"))); 50 | address public TokenB_ERC20 = address(bytes20(keccak256("TokenB_ERC20"))); 51 | 52 | // OneInch Aggregator constants 53 | uint256 public constant ONE_FOR_ZERO_MASK = 1 << 255; 54 | 55 | function setUp() public virtual { 56 | vm.label(MOCK_CONNEXT, "Mock Connext"); 57 | vm.label(TokenA_ERC20, "TokenA_ERC20"); 58 | vm.label(TokenB_ERC20, "TokenB_ERC20"); 59 | vm.label(USER_CHAIN_A, "User Chain A"); 60 | vm.label(USER_CHAIN_B, "User Chain B"); 61 | } 62 | 63 | function setUpEthereum(uint256 blockNumber) public { 64 | ethereumForkId = vm.createSelectFork(getRpc(1), blockNumber); 65 | vm.label(CONNEXT_ETHEREUM, "Connext Ethereum"); 66 | } 67 | 68 | function setUpArbitrum(uint256 blockNumber) public { 69 | arbitrumForkId = vm.createSelectFork(getRpc(42161), blockNumber); 70 | vm.label(CONNEXT_ARBITRUM, "Connext Arbitrum"); 71 | } 72 | 73 | function setUpOptimism(uint256 blockNumber) public { 74 | optimismForkId = vm.createSelectFork(getRpc(10), blockNumber); 75 | vm.label(CONNEXT_OPTIMISM, "Connext Optimism"); 76 | } 77 | 78 | function setUpBNB(uint256 blockNumber) public { 79 | bnbForkId = vm.createSelectFork(getRpc(56), blockNumber); 80 | vm.label(CONNEXT_BNB, "Connext BNB"); 81 | } 82 | 83 | function setUpPolygon(uint256 blockNumber) public { 84 | polygonForkId = vm.createSelectFork(getRpc(137), blockNumber); 85 | vm.label(CONNEXT_POLYGON, "Connext Polygon"); 86 | } 87 | 88 | function getRpc(uint256 chainId) internal view returns (string memory) { 89 | string memory keyName; 90 | string memory defaultRpc; 91 | 92 | if (chainId == 1) { 93 | keyName = "MAINNET_RPC_URL"; 94 | defaultRpc = "https://eth-mainnet.g.alchemy.com/v2/rN1fkDW9_vMmLhRj5dyVXV26k6lXZoGr"; 95 | } else if (chainId == 10) { 96 | keyName = "OPTIMISM_RPC_URL"; 97 | defaultRpc = "https://opt-mainnet.g.alchemy.com/v2/Kpix_PNmfxTUGWJ3xTpvGieSm3VN_5za"; 98 | } else if (chainId == 42161) { 99 | keyName = "ARBITRUM_RPC_URL"; 100 | defaultRpc = "https://arb-mainnet.g.alchemy.com/v2/E2B_nYYEybuSsvjrBwKKXGPzAt8NxjE0"; 101 | } else if (chainId == 56) { 102 | keyName = "BNB_RPC_URL"; 103 | defaultRpc = "https://bsc-dataseed.binance.org"; 104 | } else if (chainId == 137) { 105 | keyName = "POLYGON_RPC_URL"; 106 | defaultRpc = "https://polygon-mainnet.g.alchemy.com/v2/0xcCQA06LTzwAiRM5B9qHGs6X958x0oF"; 107 | } 108 | 109 | try vm.envString(keyName) { 110 | return vm.envString(keyName); 111 | } catch { 112 | return defaultRpc; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /contracts/shared/Swap/OneInch/OneInchUniswapV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 6 | import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; 7 | import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 8 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 9 | 10 | import {ISwapper} from "../interfaces/ISwapper.sol"; 11 | 12 | interface IUniswapV3Router { 13 | function uniswapV3Swap( 14 | uint256 amount, 15 | uint256 minReturn, 16 | uint256[] calldata pools 17 | ) external payable returns (uint256 returnAmount); 18 | } 19 | 20 | /** 21 | * @title OneInchUniswapV3 22 | * @notice Swapper contract for 1inch UniswapV3 swaps. 23 | */ 24 | contract OneInchUniswapV3 is ISwapper { 25 | using Address for address; 26 | 27 | IUniswapV3Router public immutable oneInchUniRouter; 28 | 29 | constructor(address _oneInchUniRouter) { 30 | oneInchUniRouter = IUniswapV3Router(_oneInchUniRouter); 31 | } 32 | 33 | /** 34 | * @notice Swap the given amount of tokens using 1inch. 35 | * @dev Decode the passed in data and re-encode it with the correct amountIn. This is because the amountIn is not known 36 | * until the funds are transferred to this contract. 37 | * @param _amountIn Amount of tokens to swap. 38 | * @param _fromAsset Address of the token to swap from. 39 | * @param _toAsset Address of the token to swap to. 40 | * @param _swapData Data to pass to the 1inch aggregator router. 41 | */ 42 | function swap( 43 | uint256 _amountIn, 44 | address _fromAsset, 45 | address _toAsset, 46 | bytes calldata _swapData // from 1inch API 47 | ) external override returns (uint256 amountOut) { 48 | // transfer the funds to be swapped from the sender into this contract 49 | TransferHelper.safeTransferFrom(_fromAsset, msg.sender, address(this), _amountIn); 50 | 51 | if (_fromAsset != _toAsset) { 52 | // decode the swap data 53 | // the data included with the swap encodes with the selector so we need to remove it 54 | // https://docs.1inch.io/docs/aggregation-protocol/smart-contract/UnoswapV3Router#uniswapv3swap 55 | (, uint256 _minReturn, uint256[] memory _pools) = abi.decode(_swapData[4:], (uint256, uint256, uint256[])); 56 | 57 | // Set up swap params 58 | // Approve the swapper if needed 59 | if (IERC20(_fromAsset).allowance(address(this), address(oneInchUniRouter)) < _amountIn) { 60 | TransferHelper.safeApprove(_fromAsset, address(oneInchUniRouter), type(uint256).max); 61 | } 62 | 63 | // The call to `uniswapV3Swap` executes the swap. 64 | // use actual amountIn that was sent to the xReceiver 65 | amountOut = oneInchUniRouter.uniswapV3Swap(_amountIn, _minReturn, _pools); 66 | } else { 67 | amountOut = _amountIn; 68 | } 69 | 70 | // transfer the swapped funds back to the sender 71 | if (_toAsset == address(0)) { 72 | TransferHelper.safeTransferETH(msg.sender, amountOut); 73 | } else { 74 | TransferHelper.safeTransfer(_toAsset, msg.sender, amountOut); 75 | } 76 | } 77 | 78 | /** 79 | * @notice Swap the given amount of ETH using 1inch. 80 | * @dev Decode the passed in data and re-encode it with the correct amountIn. This is because the amountIn is not known 81 | * until the funds are transferred to this contract. 82 | * @param _amountIn Amount of tokens to swap. 83 | * @param _toAsset Address of the token to swap to. 84 | * @param _swapData Data to pass to the 1inch aggregator router. 85 | */ 86 | function swapETH( 87 | uint256 _amountIn, 88 | address _toAsset, 89 | bytes calldata _swapData // from 1inch API 90 | ) external payable override returns (uint256 amountOut) { 91 | // check if msg.value is same as amountIn 92 | require(msg.value >= _amountIn, "OneInchUniswapV3: msg.value != _amountIn"); 93 | 94 | if (_toAsset != address(0)) { 95 | // decode the swap data 96 | // the data included with the swap encodes with the selector so we need to remove it 97 | // https://docs.1inch.io/docs/aggregation-protocol/smart-contract/UnoswapV3Router#uniswapv3swap 98 | (, uint256 _minReturn, uint256[] memory _pools) = abi.decode(_swapData[4:], (uint256, uint256, uint256[])); 99 | 100 | // The call to `uniswapV3Swap` executes the swap. 101 | // use actual amountIn that was sent to the xReceiver 102 | amountOut = oneInchUniRouter.uniswapV3Swap(_amountIn, _minReturn, _pools); 103 | TransferHelper.safeTransfer(_toAsset, msg.sender, amountOut); 104 | } else { 105 | amountOut = _amountIn; 106 | TransferHelper.safeTransferETH(msg.sender, amountOut); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /contracts/shared/Swap/Pancakeswap/PancakeV3Swapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 6 | 7 | import {BytesLib} from "../libraries/BytesLib.sol"; 8 | import {IPancakeSmartRouter, IV3SwapRouter} from "../interfaces/IPancakeSmartRouter.sol"; 9 | import {ISwapper} from "../interfaces/ISwapper.sol"; 10 | import {IWETH9} from "../interfaces/IWETH9.sol"; 11 | 12 | /** 13 | * @title PancakeV3Swapper 14 | * @notice Swapper contract for PancakeV3 swaps. 15 | */ 16 | contract PancakeV3Swapper is ISwapper { 17 | using BytesLib for bytes; 18 | 19 | IPancakeSmartRouter public immutable uniswapV3Router; 20 | 21 | constructor(address _uniV3Router) { 22 | uniswapV3Router = IPancakeSmartRouter(_uniV3Router); 23 | } 24 | 25 | /** 26 | * @notice Swap the given amount of tokens using PancakeSwapV3. 27 | * @dev Decode the passed in data and re-encode it with the correct amountIn. This is because the amountIn is not known 28 | * until the funds are transferred to this contract. 29 | * @param _amountIn Amount of tokens to swap. 30 | * @param _fromAsset Address of the token to swap from. 31 | * @param _toAsset Address of the token to swap to. 32 | * @param _swapData Data to pass to the PancakeV3 router. 33 | */ 34 | function swap( 35 | uint256 _amountIn, 36 | address _fromAsset, 37 | address _toAsset, 38 | bytes calldata _swapData 39 | ) external override returns (uint256 amountOut) { 40 | // transfer the funds to be swapped from the sender into this contract 41 | TransferHelper.safeTransferFrom(_fromAsset, msg.sender, address(this), _amountIn); 42 | 43 | if (_fromAsset != _toAsset) { 44 | (uint256 amountOutMin, bytes memory path) = abi.decode(_swapData, (uint256, bytes)); 45 | 46 | bool toNative = _toAsset == address(0); 47 | IWETH9 weth9 = IWETH9(uniswapV3Router.WETH9()); 48 | address toAsset = toNative ? address(weth9) : _toAsset; 49 | 50 | _checkPath(_fromAsset, toAsset, path); 51 | 52 | TransferHelper.safeApprove(_fromAsset, address(uniswapV3Router), _amountIn); 53 | 54 | // Set up uniswap swap params. 55 | IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter.ExactInputParams({ 56 | path: path, 57 | recipient: address(this), 58 | amountIn: _amountIn, 59 | amountOutMinimum: amountOutMin 60 | }); 61 | 62 | // The call to `exactInputSingle` executes the swap. 63 | amountOut = uniswapV3Router.exactInput(params); 64 | 65 | if (toNative) { 66 | weth9.withdraw(amountOut); 67 | TransferHelper.safeTransferETH(msg.sender, amountOut); 68 | } else { 69 | TransferHelper.safeTransfer(_toAsset, msg.sender, amountOut); 70 | } 71 | } else { 72 | amountOut = _amountIn; 73 | TransferHelper.safeTransfer(_toAsset, msg.sender, amountOut); 74 | } 75 | } 76 | 77 | /** 78 | * @notice Swap the given amount of ETH using PancakeSwapV3. 79 | * @dev Decode the passed in data and re-encode it with the correct amountIn. This is because the amountIn is not known 80 | * until the funds are transferred to this contract. 81 | * @param _amountIn Amount of tokens to swap. 82 | * @param _toAsset Address of the token to swap to. 83 | * @param _swapData Data to pass to the PancakeV3 router. 84 | */ 85 | function swapETH( 86 | uint256 _amountIn, 87 | address _toAsset, 88 | bytes calldata _swapData 89 | ) external payable override returns (uint256 amountOut) { 90 | // check if msg.value is same as amountIn 91 | require(msg.value == _amountIn, "PancakeV3Swapper: msg.value != _amountIn"); 92 | 93 | if (_toAsset != address(0)) { 94 | (uint256 amountOutMin, bytes memory path) = abi.decode(_swapData, (uint256, bytes)); 95 | 96 | IWETH9 weth9 = IWETH9(uniswapV3Router.WETH9()); 97 | 98 | _checkPath(address(weth9), _toAsset, path); 99 | 100 | weth9.deposit{value: _amountIn}(); 101 | TransferHelper.safeApprove(address(weth9), address(uniswapV3Router), _amountIn); 102 | 103 | // Set up uniswap swap params. 104 | IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter.ExactInputParams({ 105 | path: path, 106 | recipient: msg.sender, 107 | amountIn: _amountIn, 108 | amountOutMinimum: amountOutMin 109 | }); 110 | 111 | // The call to `exactInput` executes the swap. 112 | amountOut = uniswapV3Router.exactInput(params); 113 | } else { 114 | amountOut = _amountIn; 115 | TransferHelper.safeTransferETH(msg.sender, amountOut); 116 | } 117 | } 118 | 119 | function _checkPath(address from, address to, bytes memory path) internal pure { 120 | require(from == path.toAddress(0), "from token invalid"); 121 | require(to == path.toAddress(path.length - 20), "to token invalid"); 122 | } 123 | 124 | receive() external payable {} 125 | } 126 | -------------------------------------------------------------------------------- /contracts/shared/Swap/Uniswap/UniV3Swapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 6 | 7 | import {BytesLib} from "../libraries/BytesLib.sol"; 8 | import {ISmartRouter, ISwapRouter} from "../interfaces/ISmartRouter.sol"; 9 | import {ISwapper} from "../interfaces/ISwapper.sol"; 10 | import {IWETH9} from "../interfaces/IWETH9.sol"; 11 | 12 | /** 13 | * @title UniV3Swapper 14 | * @notice Swapper contract for UniV3 swaps. 15 | */ 16 | contract UniV3Swapper is ISwapper { 17 | using BytesLib for bytes; 18 | 19 | ISmartRouter public immutable uniswapV3Router; 20 | 21 | constructor(address _uniV3Router) { 22 | uniswapV3Router = ISmartRouter(_uniV3Router); 23 | } 24 | 25 | /** 26 | * @notice Swap the given amount of tokens using UniV3. 27 | * @dev Decode the passed in data and re-encode it with the correct amountIn. This is because the amountIn is not known 28 | * until the funds are transferred to this contract. 29 | * @param _amountIn Amount of tokens to swap. 30 | * @param _fromAsset Address of the token to swap from. 31 | * @param _toAsset Address of the token to swap to. 32 | * @param _swapData Data to pass to the UniV3 router. 33 | */ 34 | function swap( 35 | uint256 _amountIn, 36 | address _fromAsset, 37 | address _toAsset, 38 | bytes calldata _swapData 39 | ) external override returns (uint256 amountOut) { 40 | // transfer the funds to be swapped from the sender into this contract 41 | TransferHelper.safeTransferFrom(_fromAsset, msg.sender, address(this), _amountIn); 42 | 43 | if (_fromAsset != _toAsset) { 44 | (uint256 amountOutMin, bytes memory path) = abi.decode(_swapData, (uint256, bytes)); 45 | 46 | bool toNative = _toAsset == address(0); 47 | IWETH9 weth9 = IWETH9(uniswapV3Router.WETH9()); 48 | address toAsset = toNative ? address(weth9) : _toAsset; 49 | 50 | _checkPath(_fromAsset, toAsset, path); 51 | 52 | TransferHelper.safeApprove(_fromAsset, address(uniswapV3Router), _amountIn); 53 | 54 | // Set up uniswap swap params. 55 | ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ 56 | path: path, 57 | recipient: address(this), 58 | deadline: block.timestamp, 59 | amountIn: _amountIn, 60 | amountOutMinimum: amountOutMin 61 | }); 62 | 63 | // The call to `exactInputSingle` executes the swap. 64 | amountOut = uniswapV3Router.exactInput(params); 65 | 66 | if (toNative) { 67 | weth9.withdraw(amountOut); 68 | TransferHelper.safeTransferETH(msg.sender, amountOut); 69 | } else { 70 | TransferHelper.safeTransfer(_toAsset, msg.sender, amountOut); 71 | } 72 | } else { 73 | amountOut = _amountIn; 74 | TransferHelper.safeTransfer(_toAsset, msg.sender, amountOut); 75 | } 76 | } 77 | 78 | /** 79 | * @notice Swap the given amount of ETH using UniV3Swapper. 80 | * @dev Decode the passed in data and re-encode it with the correct amountIn. This is because the amountIn is not known 81 | * until the funds are transferred to this contract. 82 | * @param _amountIn Amount of tokens to swap. 83 | * @param _toAsset Address of the token to swap to. 84 | * @param _swapData Data to pass to the UniV3 router. 85 | */ 86 | function swapETH( 87 | uint256 _amountIn, 88 | address _toAsset, 89 | bytes calldata _swapData 90 | ) external payable override returns (uint256 amountOut) { 91 | // check if msg.value is same as amountIn 92 | require(msg.value == _amountIn, "UniV3Swapper: msg.value != _amountIn"); 93 | 94 | if (_toAsset != address(0)) { 95 | (uint256 amountOutMin, bytes memory path) = abi.decode(_swapData, (uint256, bytes)); 96 | 97 | IWETH9 weth9 = IWETH9(uniswapV3Router.WETH9()); 98 | 99 | _checkPath(address(weth9), _toAsset, path); 100 | 101 | weth9.deposit{value: _amountIn}(); 102 | TransferHelper.safeApprove(address(weth9), address(uniswapV3Router), _amountIn); 103 | 104 | // Set up uniswap swap params. 105 | ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ 106 | path: path, 107 | recipient: msg.sender, 108 | deadline: block.timestamp, 109 | amountIn: _amountIn, 110 | amountOutMinimum: amountOutMin 111 | }); 112 | 113 | // The call to `exactInput` executes the swap. 114 | amountOut = uniswapV3Router.exactInput(params); 115 | } else { 116 | amountOut = _amountIn; 117 | TransferHelper.safeTransferETH(msg.sender, amountOut); 118 | } 119 | } 120 | 121 | function _checkPath(address from, address to, bytes memory path) internal pure { 122 | require(from == path.toAddress(0), "from token invalid"); 123 | require(to == path.toAddress(path.length - 20), "to token invalid"); 124 | } 125 | 126 | receive() external payable {} 127 | } 128 | -------------------------------------------------------------------------------- /contracts/shared/ownership/ProposedOwnable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | * @title ProposedOwnable 6 | * @notice Contract module which provides a basic access control mechanism, 7 | * where there is an account (an owner) that can be granted exclusive access to 8 | * specific functions. 9 | * 10 | * By default, the owner account will be the one that deploys the contract. This 11 | * can later be changed via a two step process: 12 | * 1. Call `proposeOwner` 13 | * 2. Wait out the delay period 14 | * 3. Call `acceptOwner` 15 | * 16 | * @dev This module is used through inheritance. It will make available the 17 | * modifier `onlyOwner`, which can be applied to your functions to restrict 18 | * their use to the owner. 19 | * 20 | * @dev The majority of this code was taken from the openzeppelin Ownable 21 | * contract 22 | * 23 | */ 24 | abstract contract ProposedOwnable { 25 | address private _owner; 26 | 27 | address private _proposed; 28 | uint256 private _proposedOwnershipTimestamp; 29 | 30 | uint256 private constant _delay = 7 days; 31 | 32 | event OwnershipProposed(address indexed proposedOwner); 33 | 34 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 35 | 36 | /** 37 | * @notice Throws if called by any account other than the owner. 38 | */ 39 | modifier onlyOwner() { 40 | require(_owner == msg.sender, "!owner"); 41 | _; 42 | } 43 | 44 | /** 45 | * @notice Throws if called by any account other than the proposed owner. 46 | */ 47 | modifier onlyProposed() { 48 | require(_proposed == msg.sender, "!proposed"); 49 | _; 50 | } 51 | 52 | /** 53 | * @notice Returns the address of the current owner. 54 | */ 55 | function owner() public view virtual returns (address) { 56 | return _owner; 57 | } 58 | 59 | /** 60 | * @notice Returns the address of the proposed owner. 61 | */ 62 | function proposed() public view virtual returns (address) { 63 | return _proposed; 64 | } 65 | 66 | /** 67 | * @notice Returns the address of the proposed owner. 68 | */ 69 | function proposedTimestamp() public view virtual returns (uint256) { 70 | return _proposedOwnershipTimestamp; 71 | } 72 | 73 | /** 74 | * @notice Returns the delay period before a new owner can be accepted. 75 | */ 76 | function delay() public view virtual returns (uint256) { 77 | return _delay; 78 | } 79 | 80 | /** 81 | * @notice Indicates if the ownership has been renounced() by 82 | * checking if current owner is address(0) 83 | */ 84 | function renounced() public view returns (bool) { 85 | return _owner == address(0); 86 | } 87 | 88 | /** 89 | * @notice Sets the timestamp for an owner to be proposed, and sets the 90 | * newly proposed owner as step 1 in a 2-step process 91 | */ 92 | function proposeNewOwner(address newlyProposed) public virtual onlyOwner { 93 | // Contract as source of truth 94 | require(_proposed != newlyProposed || newlyProposed == address(0), "!proposed"); 95 | 96 | // Sanity check: reasonable proposal 97 | require(_owner != newlyProposed, "!change"); 98 | 99 | _setProposed(newlyProposed); 100 | } 101 | 102 | /** 103 | * @notice Renounces ownership of the contract after a delay 104 | */ 105 | function renounceOwnership() public virtual onlyOwner { 106 | // Ensure there has been a proposal cycle started 107 | require(_proposedOwnershipTimestamp > 0, "!started"); 108 | 109 | // Ensure delay has elapsed 110 | require((block.timestamp - _proposedOwnershipTimestamp) > _delay, "!elapsed"); 111 | 112 | // Require proposed is set to 0 113 | require(_proposed == address(0), "!proposed"); 114 | 115 | // Emit event, set new owner, reset timestamp 116 | _setOwner(_proposed); 117 | } 118 | 119 | /** 120 | * @notice Transfers ownership of the contract to a new account (`newOwner`). 121 | * Can only be called by the current owner. 122 | */ 123 | function acceptProposedOwner() public virtual onlyProposed { 124 | // Contract as source of truth 125 | require(_owner != _proposed, "!change"); 126 | 127 | // NOTE: no need to check if _proposedOwnershipTimestamp > 0 because 128 | // the only time this would happen is if the _proposed was never 129 | // set (will fail from modifier) or if the owner == _proposed (checked 130 | // above) 131 | 132 | // Ensure delay has elapsed 133 | require((block.timestamp - _proposedOwnershipTimestamp) > _delay, "!elapsed"); 134 | 135 | // Emit event, set new owner, reset timestamp 136 | _setOwner(_proposed); 137 | } 138 | 139 | ////// INTERNAL ////// 140 | 141 | function _setOwner(address newOwner) internal { 142 | address oldOwner = _owner; 143 | _owner = newOwner; 144 | _proposedOwnershipTimestamp = 0; 145 | emit OwnershipTransferred(oldOwner, newOwner); 146 | } 147 | 148 | function _setProposed(address newlyProposed) private { 149 | _proposedOwnershipTimestamp = block.timestamp; 150 | _proposed = newlyProposed; 151 | emit OwnershipProposed(_proposed); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /test/instadapp/InstaTargetAuth.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers } from "hardhat"; 3 | import instaIndexABI from "../helpers/abis/instaindex.json"; 4 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 5 | import { Contract, Wallet } from "ethers"; 6 | import { 7 | defaultAbiCoder, 8 | joinSignature, 9 | keccak256, 10 | solidityKeccak256, 11 | toUtf8Bytes, 12 | _TypedDataEncoder, 13 | } from "ethers/lib/utils"; 14 | import { sign } from "crypto"; 15 | 16 | // Hardcoded addresses on optimism 17 | const instaIndexAddr = "0x6CE3e607C808b4f4C26B7F6aDAeB619e49CAbb25"; 18 | 19 | // DO NOT USE THIS KEY ON MAINNET 20 | const TEST_PRIIVATE_KEY = "913b591c8abc30e7d3d2a4ebd560e1f02197cb701f077c9fcdc2ba8b0a7d9abe"; // address = 0xc1aAED5D41a3c3c33B1978EA55916f9A3EE1B397 21 | const deployDSAv2 = async (owner: any) => { 22 | const instaIndex = await ethers.getContractAt(instaIndexABI, instaIndexAddr); 23 | const tx = await instaIndex.build(owner, 2, owner); 24 | const receipt = await tx.wait(); 25 | 26 | const event = receipt.events.find((a: { event: string }) => a.event === "LogAccountCreated"); 27 | return await ethers.getContractAt(instaIndexABI, event.args.account); 28 | }; 29 | 30 | const deployInstaTargetAuth = async (dsaAddress: string) => { 31 | const instaTagetAuthFactory = await ethers.getContractFactory("InstaTargetAuth"); 32 | const contractInstance = await instaTagetAuthFactory.deploy(dsaAddress); 33 | await contractInstance.deployed(); 34 | return contractInstance; 35 | }; 36 | 37 | const generateSignature = async (sender: Wallet, domain: any, castData: any, typeHash: string): Promise => { 38 | const encodedData = defaultAbiCoder.encode( 39 | ["bytes32", "string[]", "bytes[]", "address"], 40 | [typeHash, castData._targetNames, castData._datas, castData._origin], 41 | ); 42 | const domainSeparator = _TypedDataEncoder.hashDomain(domain); 43 | const digest = solidityKeccak256( 44 | ["string", "bytes32", "bytes32"], 45 | ["\x19\x01", domainSeparator, keccak256(encodedData)], 46 | ); 47 | const signingKey = sender._signingKey(); 48 | const signature = signingKey.signDigest(digest); 49 | const joinedSig = joinSignature(signature); 50 | return joinedSig; 51 | }; 52 | describe("InstaTargetAuth", () => { 53 | let deployer: SignerWithAddress, sender: Wallet; 54 | let dsaContract: Contract; 55 | let instaTargetAuthContract: any; 56 | 57 | before(async () => { 58 | [deployer] = await ethers.getSigners(); 59 | 60 | sender = new ethers.Wallet(TEST_PRIIVATE_KEY, ethers.provider); 61 | dsaContract = await deployDSAv2(deployer.address); 62 | // instaTargetAuthContract = await deployInstaTargetAuth(dsaContract.address); 63 | }); 64 | describe("#verify", () => { 65 | it("happy: should verify successfully", async () => { 66 | // const authContractAddress = instaTargetAuthContract.address.toLowerCase(); 67 | const authContractAddress = "0x6CE3e607C808b4f4C26B7F6aDAeB619e49CAbb25"; 68 | const domain = { 69 | name: "InstaTargetAuth", 70 | version: "1", 71 | chainId: 31337, 72 | verifyingContract: authContractAddress, 73 | }; 74 | 75 | const castData = { 76 | _targetNames: ["target111", "target222", "target333"], 77 | _datas: [toUtf8Bytes("0x111"), toUtf8Bytes("0x222"), toUtf8Bytes("0x333")], 78 | _origin: sender.address.toLowerCase(), 79 | }; 80 | 81 | const CASTDATA_TYPEHASH = keccak256( 82 | toUtf8Bytes("CastData(string[] _targetNames,bytes[] _datas,address _origin)"), 83 | ); 84 | const signature = await generateSignature(sender, domain, castData, CASTDATA_TYPEHASH); 85 | console.log("> signature: ", signature); 86 | const verified = await instaTargetAuthContract.connect(deployer).verify(signature, sender.address, castData); 87 | expect(verified).to.be.true; 88 | }); 89 | 90 | it("should return false if signature is invalid", async () => { 91 | const authContractAddress = instaTargetAuthContract.address.toLowerCase(); 92 | const domain = { 93 | name: "InstaTargetAuth", 94 | version: "1", 95 | chainId: 31337, 96 | verifyingContract: authContractAddress, 97 | }; 98 | 99 | const castData = { 100 | _targetNames: ["target111", "target222", "target333"], 101 | _datas: [toUtf8Bytes("0x111"), toUtf8Bytes("0x222"), toUtf8Bytes("0x333")], 102 | _origin: sender.address.toLowerCase(), 103 | }; 104 | 105 | const CASTDATA_TYPEHASH = keccak256( 106 | toUtf8Bytes("CastData(string[] _targetNames,bytes[] _datas,address _origin)"), 107 | ); 108 | const signature = await generateSignature(sender, domain, castData, CASTDATA_TYPEHASH); 109 | const verified = await instaTargetAuthContract.connect(deployer).verify(signature, deployer.address, castData); 110 | expect(verified).to.be.false; 111 | }); 112 | }); 113 | 114 | describe("#authCast", () => { 115 | it("should revert if verification fails", async () => {}); 116 | it("happy: should work", async () => {}); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /contracts/integration/Instadapp/InstadappAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 5 | import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; 6 | 7 | import {IDSA} from "./interfaces/IDSA.sol"; 8 | import "forge-std/console.sol"; 9 | 10 | /// @title InstadappAdapter 11 | /// @author Connext 12 | /// @notice This contract is inherited by InstadappTarget, it includes the logic to verify signatures 13 | /// and execute the calls. 14 | /// @dev This contract is not meant to be used directly, it is meant to be inherited by other contracts. 15 | /// @custom:experimental This is an experimental contract. 16 | contract InstadappAdapter is EIP712 { 17 | /// Structs 18 | /// @dev This struct is used to encode the data for InstadappTarget.cast function. 19 | /// @param targetNames The names of the targets that will be called. 20 | /// @param datas The data that will be sent to the targets. 21 | /// @param origin The address that will be used as the origin of the call. 22 | struct CastData { 23 | string[] targetNames; 24 | bytes[] datas; 25 | address origin; 26 | } 27 | 28 | /// @dev This struct is used to encode the data that is signed by the auth address. 29 | /// The signature is then verified by the verify function. 30 | struct Sig { 31 | CastData castData; 32 | bytes32 salt; 33 | uint256 deadline; 34 | } 35 | 36 | /// Storage 37 | /// @dev This mapping is used to prevent replay attacks. 38 | mapping(bytes32 => bool) private sigReplayProtection; 39 | 40 | /// Constants 41 | /// @dev This is the typehash for the CastData struct. 42 | bytes32 public constant CASTDATA_TYPEHASH = keccak256("CastData(string[] targetNames,bytes[] datas,address origin)"); 43 | 44 | /// @dev This is the typehash for the Sig struct. 45 | bytes32 public constant SIG_TYPEHASH = 46 | keccak256( 47 | "Sig(CastData cast,bytes32 salt,uint256 deadline)CastData(string[] targetNames,bytes[] datas,address origin)" 48 | ); 49 | 50 | /// Constructor 51 | constructor() EIP712("InstaTargetAuth", "1") {} 52 | 53 | /// Internal functions 54 | /// @dev This function is used to forward the call to dsa.cast function. 55 | /// Cast the call is forwarded, the signature is verified and the salt is stored in the sigReplayProtection mapping. 56 | /// @param dsaAddress The address of the DSA. 57 | /// @param auth The address of the auth. 58 | /// @param signature The signature by the auth. This signature is used to verify the SIG data. 59 | /// @param castData The data that will be sent to the targets. 60 | /// @param salt The salt that will be used to prevent replay attacks. 61 | /// @param deadline The deadline that will be used to prevent replay attacks. 62 | function authCast( 63 | address dsaAddress, 64 | address auth, 65 | bytes memory signature, 66 | CastData memory castData, 67 | bytes32 salt, 68 | uint256 deadline 69 | ) internal { 70 | IDSA dsa = IDSA(dsaAddress); 71 | // check if Auth is valid, and included in the DSA 72 | require(auth != address(0) && dsa.isAuth(auth), "Invalid Auth"); 73 | 74 | // check if signature is not replayed 75 | require(!sigReplayProtection[salt], "Replay Attack"); 76 | 77 | // check if signature is not expired 78 | require(block.timestamp < deadline, "Signature Expired"); 79 | 80 | // check if signature is valid, and not replayed 81 | require(verify(auth, signature, castData, salt, deadline), "Invalid signature"); 82 | 83 | // Signature Replay Protection 84 | sigReplayProtection[salt] = true; 85 | 86 | // Cast the call 87 | dsa.cast(castData.targetNames, castData.datas, castData.origin); 88 | } 89 | 90 | /// @dev This function is used to verify the signature. 91 | /// @param auth The address of the auth. 92 | /// @param signature The signature of the auth. 93 | /// @param castData The data that will be sent to the targets. 94 | /// @param salt The salt that will be used to prevent replay attacks. 95 | /// @param deadline The deadline that will be used to prevent replay attacks. 96 | /// @return boolean that indicates if the signature is valid. 97 | function verify( 98 | address auth, 99 | bytes memory signature, 100 | CastData memory castData, 101 | bytes32 salt, 102 | uint256 deadline 103 | ) internal view returns (bool) { 104 | bytes32 digest = getDigest(castData, salt, deadline); 105 | address signer = ECDSA.recover(digest, signature); 106 | return signer == auth; 107 | } 108 | 109 | function getDigest(CastData memory castData, bytes32 salt, uint256 deadline) internal view returns (bytes32) { 110 | return _hashTypedDataV4(keccak256(abi.encode(SIG_TYPEHASH, getHash(castData), salt, deadline))); 111 | } 112 | 113 | /// @dev This function is used to hash the CastData struct. 114 | /// @param castData The data that will be sent to the targets. 115 | /// @return bytes32 that is the hash of the CastData struct. 116 | function getHash(CastData memory castData) internal pure returns (bytes32) { 117 | return keccak256(abi.encode(CASTDATA_TYPEHASH, castData.targetNames, castData.datas, castData.origin)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /contracts/test/unit/integration/Instadapp/InstadappTarget.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 5 | import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; 6 | 7 | import {TestHelper} from "../../../utils/TestHelper.sol"; 8 | import {InstadappAdapter} from "../../../../integration/Instadapp/InstadappAdapter.sol"; 9 | import {InstadappTarget} from "../../../../integration/Instadapp/InstadappTarget.sol"; 10 | import {TestERC20} from "../../../TestERC20.sol"; 11 | 12 | contract MockInstadappReceiver is InstadappAdapter { 13 | constructor() {} 14 | 15 | function tryGetDigest(CastData memory castData, bytes32 salt, uint256 deadline) external returns (bytes32) { 16 | return getDigest(castData, salt, deadline); 17 | } 18 | } 19 | 20 | contract InstadappTargetTest is TestHelper, EIP712 { 21 | // ============ Storage ============ 22 | InstadappTarget instadappTarget; 23 | address instadappReceiver = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f; 24 | 25 | // ============ Events ============ 26 | event AuthCast( 27 | bytes32 indexed transferId, 28 | address indexed dsaAddress, 29 | bool indexed success, 30 | address auth, 31 | bytes returnedData 32 | ); 33 | 34 | // ============ Test set up ============ 35 | function setUp() public override { 36 | super.setUp(); 37 | MockInstadappReceiver _instadappReceiver = new MockInstadappReceiver(); 38 | vm.etch(instadappReceiver, address(_instadappReceiver).code); 39 | 40 | instadappTarget = new InstadappTarget(MOCK_CONNEXT); 41 | } 42 | 43 | constructor() EIP712("InstaTargetAuth", "1") {} 44 | 45 | function test_InstadappTarget__xReceive_shouldRevertIfCallerNotConnext() public { 46 | bytes32 transferId = keccak256(abi.encode(0x123)); 47 | uint256 amount = 1 ether; 48 | address asset = address(0x123123123); 49 | bytes memory callData = bytes("123"); 50 | 51 | vm.prank(address(0x456)); 52 | vm.expectRevert(bytes("Caller must be Connext")); 53 | instadappTarget.xReceive(transferId, amount, asset, address(0), 0, callData); 54 | } 55 | 56 | function test_InstadappTarget__xReceive_shouldRevertIfFallbackAddressInvalid() public { 57 | // Mock xReceive data 58 | bytes32 transferId = keccak256(abi.encode(0x123)); 59 | uint256 amount = 1 ether; 60 | address asset = address(0x123123123); 61 | 62 | // Mock callData of `xReceive` 63 | address originSender = address(0xc1aAED5D41a3c3c33B1978EA55916f9A3EE1B397); 64 | string[] memory _targetNames = new string[](3); 65 | _targetNames[0] = "target111"; 66 | _targetNames[1] = "target222"; 67 | _targetNames[2] = "target333"; 68 | bytes[] memory _datas = new bytes[](3); 69 | _datas[0] = bytes("0x111"); 70 | _datas[1] = bytes("0x222"); 71 | _datas[2] = bytes("0x333"); 72 | address _origin = originSender; 73 | 74 | InstadappAdapter.CastData memory castData = InstadappAdapter.CastData(_targetNames, _datas, _origin); 75 | bytes 76 | memory signature = hex"d75642b5e0cfceac682011943f3586fefc3709594a89bf8087acc58d2009d85412aca8b1f9b63989de45da85f5ffcea52cc5077a61a2128fa7322a97523afe0e1b"; 77 | address auth = originSender; 78 | bytes memory callData = abi.encode(address(0), auth, signature, castData); 79 | 80 | vm.prank(MOCK_CONNEXT); 81 | vm.expectRevert(bytes("!invalidFallback")); 82 | instadappTarget.xReceive(transferId, amount, asset, address(0), 0, callData); 83 | } 84 | 85 | function test_InstadappTarget__xReceive_shouldWork() public { 86 | // Mock xReceive data 87 | bytes32 transferId = keccak256(abi.encode(0x123)); 88 | uint256 amount = 1 ether; 89 | TestERC20 asset = new TestERC20("Test", "TST"); 90 | 91 | // Mock callData of `xReceive` 92 | address originSender = vm.addr(1); 93 | string[] memory _targetNames = new string[](3); 94 | _targetNames[0] = "target111"; 95 | _targetNames[1] = "target222"; 96 | _targetNames[2] = "target333"; 97 | bytes[] memory _datas = new bytes[](3); 98 | _datas[0] = bytes("0x111"); 99 | _datas[1] = bytes("0x222"); 100 | _datas[2] = bytes("0x333"); 101 | address _origin = originSender; 102 | address dsa = address(0x111222333); 103 | bytes32 salt = bytes32(0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef); 104 | uint256 deadline = 10000; 105 | 106 | InstadappAdapter.CastData memory castData = InstadappAdapter.CastData(_targetNames, _datas, _origin); 107 | bytes32 digest = MockInstadappReceiver(instadappReceiver).tryGetDigest(castData, salt, deadline); 108 | 109 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, digest); 110 | bytes memory signature = abi.encodePacked(r, s, v); 111 | address auth = originSender; 112 | bytes memory callData = abi.encode(dsa, auth, signature, castData); 113 | 114 | bytes memory returnedData = hex""; 115 | vm.expectEmit(true, false, false, true); 116 | emit AuthCast(transferId, dsa, false, auth, returnedData); 117 | deal(address(asset), address(instadappTarget), amount); 118 | vm.prank(MOCK_CONNEXT); 119 | instadappTarget.xReceive(transferId, amount, address(asset), address(0), 0, callData); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /contracts/integration/LockboxAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import {IXERC20} from "../shared/IXERC20/IXERC20.sol"; 7 | import {IXERC20Lockbox} from "../shared/IXERC20/IXERC20Lockbox.sol"; 8 | import {IConnext} from "@connext/interfaces/core/IConnext.sol"; 9 | import {IXReceiver} from "@connext/interfaces/core/IXReceiver.sol"; 10 | 11 | interface IXERC20Registry { 12 | function getXERC20(address erc20) external view returns (address xerc20); 13 | 14 | function getERC20(address xerc20) external view returns (address erc20); 15 | 16 | function getLockbox(address erc20) external view returns (address xerc20); 17 | } 18 | 19 | contract LockboxAdapter is IXReceiver { 20 | address immutable connext; 21 | address immutable registry; 22 | 23 | // EVENTS 24 | event LockBoxWithdrawFailed(bytes _lowLevelData); 25 | 26 | // ERRORS 27 | error Forwarder__is__not__Adapter(address sender); 28 | error IXERC20Adapter_WithdrawFailed(); 29 | error NotConnext(address sender); 30 | error AmountLessThanZero(); 31 | error ValueLessThanAmount(uint256 value, uint256 amount); 32 | 33 | modifier onlyConnext() { 34 | if (msg.sender != connext) { 35 | revert NotConnext(msg.sender); 36 | } 37 | _; 38 | } 39 | 40 | constructor(address _connext, address _registry) { 41 | connext = _connext; 42 | registry = _registry; 43 | } 44 | 45 | /// @dev Combines Lockbox deposit and xcall using native asset as relayer fee. 46 | /// @param _destination The destination domain ID. 47 | /// @param _to The recipient or contract address on destination. 48 | /// @param _asset The address of the asset to be sent (ERC20 or native). 49 | /// @param _delegate The address on destination allowed to update slippage. 50 | /// @param _amount The amount of asset to bridge. 51 | /// @param _slippage The maximum slippage a user is willing to take, in BPS. 52 | /// @param _callData The data that will be sent to the target contract. 53 | function xcall( 54 | uint32 _destination, 55 | address _to, 56 | address _asset, 57 | address _delegate, 58 | uint256 _amount, 59 | uint256 _slippage, 60 | bytes calldata _callData 61 | ) external payable returns (bytes32) { 62 | if (_amount <= 0) { 63 | revert AmountLessThanZero(); 64 | } 65 | 66 | address xerc20 = IXERC20Registry(registry).getXERC20(_asset); 67 | address lockbox = IXERC20Registry(registry).getLockbox(xerc20); 68 | bool isNative = IXERC20Lockbox(lockbox).IS_NATIVE(); 69 | 70 | uint256 _relayerFee; 71 | if (isNative) { 72 | if (msg.value < _amount) { 73 | revert ValueLessThanAmount(msg.value, _amount); 74 | } 75 | 76 | // Assume the rest of msg.value is the relayer fee 77 | _relayerFee = msg.value - _amount; 78 | IXERC20Lockbox(lockbox).depositNative{value: _amount}(); 79 | } else { 80 | // IERC20(_asset).transferFrom(msg.sender, address(this), _amount); 81 | SafeERC20.safeTransferFrom(IERC20(_asset), msg.sender, address(this), _amount); 82 | IERC20(_asset).approve(lockbox, _amount); 83 | 84 | // The entirety of msg.value is the relayer fee 85 | _relayerFee = msg.value; 86 | IXERC20Lockbox(lockbox).deposit(_amount); 87 | } 88 | 89 | IERC20(xerc20).approve(connext, _amount); 90 | return 91 | IConnext(connext).xcall{value: _relayerFee}(_destination, _to, xerc20, _delegate, _amount, _slippage, _callData); 92 | } 93 | 94 | /// @dev Receives xERC20s from Connext and withdraws ERC20 from Lockbox. 95 | /// @param _amount The amount of funds that will be received. 96 | /// @param _asset The address of the XERC20 that will be received. 97 | /// @param _callData The data which should contain the recipient's address. 98 | function xReceive( 99 | bytes32 /* _transferId */, 100 | uint256 _amount, 101 | address _asset, 102 | address /* _originSender */, 103 | uint32 /* _origin */, 104 | bytes memory _callData 105 | ) external onlyConnext returns (bytes memory) { 106 | address recipient = abi.decode(_callData, (address)); 107 | 108 | try this.handlexReceive(_amount, _asset, recipient) {} catch (bytes memory _lowLevelData) { 109 | // This is executed in case revert() was used. 110 | IERC20(_asset).transfer(recipient, _amount); 111 | emit LockBoxWithdrawFailed(_lowLevelData); 112 | } 113 | 114 | return ""; 115 | } 116 | 117 | function handlexReceive(uint256 _amount, address _asset, address _recipient) public { 118 | if (msg.sender != address(this)) { 119 | revert Forwarder__is__not__Adapter(msg.sender); 120 | } 121 | address lockbox = IXERC20Registry(registry).getLockbox(_asset); 122 | address erc20 = IXERC20Registry(registry).getERC20(_asset); 123 | bool isNative = IXERC20Lockbox(lockbox).IS_NATIVE(); 124 | IERC20(_asset).approve(lockbox, _amount); 125 | IXERC20Lockbox(lockbox).withdraw(_amount); 126 | 127 | if (isNative) { 128 | (bool _success, ) = payable(_recipient).call{value: _amount}(""); 129 | if (!_success) revert IXERC20Adapter_WithdrawFailed(); 130 | } else { 131 | SafeERC20.safeTransfer(IERC20(erc20), _recipient, _amount); 132 | } 133 | } 134 | 135 | receive() external payable {} 136 | } -------------------------------------------------------------------------------- /test/meanFinance/MeanFinanceTarget.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { deployments, ethers } from "hardhat"; 3 | import { 4 | BigNumber, 5 | BigNumberish, 6 | constants, 7 | Contract, 8 | providers, 9 | utils, 10 | Wallet, 11 | } from "ethers"; 12 | import { DEFAULT_ARGS } from "../../deploy"; 13 | import { ERC20_ABI } from "@0xgafu/common-abi"; 14 | import { getRandomBytes32 } from "@connext/utils"; 15 | import { SwapInterval } from "../../utils/interval-utils"; 16 | import ConnextInterface from "../../artifacts/@connext/interfaces/core/IConnext.sol/IConnext.json"; 17 | 18 | enum Permission { 19 | INCREASE, 20 | REDUCE, 21 | WITHDRAW, 22 | TERMINATE, 23 | } 24 | 25 | const fund = async ( 26 | asset: string, 27 | wei: BigNumberish, 28 | from: Wallet, 29 | to: string 30 | ): Promise => { 31 | if (asset === constants.AddressZero) { 32 | const tx = await from.sendTransaction({ to, value: wei }); 33 | // send eth 34 | return await tx.wait(); 35 | } 36 | 37 | // send tokens 38 | const token = new Contract(asset, ERC20_ABI, from); 39 | const tx = await token.transfer(to, wei); 40 | return await tx.wait(); 41 | }; 42 | 43 | describe("MeanFinanceTarget", function () { 44 | // Set up constants (will mirror what deploy fixture uses) 45 | const { WETH, USDC, CONNEXT, DOMAIN } = DEFAULT_ARGS[31337]; 46 | const MEAN_FINANCE_IDCAHUB = "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345"; 47 | const WHALE = "0x385BAe68690c1b86e2f1Ad75253d080C14fA6e16"; // this is the address that should have weth, target, and random addr 48 | const NOT_ZERO_ADDRESS = "0x7088C5611dAE159A640d940cde0a3221a4af8896"; 49 | const RANDOM_TOKEN = "0x4200000000000000000000000000000000000042"; // this is OP 50 | const ASSET_DECIMALS = 6; // USDC decimals on op 51 | 52 | const permissions = [ 53 | { operator: NOT_ZERO_ADDRESS, permissions: [Permission.INCREASE] }, 54 | ]; 55 | const swaps = 10; 56 | const interval = SwapInterval.ONE_DAY.seconds; 57 | 58 | // Set up variables 59 | let connext: Contract; 60 | let target: Contract; 61 | let wallet: Wallet; 62 | let whale: Wallet; 63 | let tokenUSDC: Contract; 64 | let weth: Contract; 65 | let randomToken: Contract; 66 | 67 | before(async () => { 68 | // get wallet 69 | [wallet] = (await ethers.getSigners()) as unknown as Wallet[]; 70 | // get whale 71 | whale = (await ethers.getImpersonatedSigner(WHALE)) as unknown as Wallet; 72 | // deploy contract 73 | const { MeanFinanceTarget } = await deployments.fixture([ 74 | "meanfinancetarget", 75 | ]); 76 | target = new Contract( 77 | MeanFinanceTarget.address, 78 | MeanFinanceTarget.abi, 79 | ethers.provider 80 | ); 81 | 82 | connext = new Contract(CONNEXT, ConnextInterface.abi, ethers.provider); 83 | // setup tokens 84 | tokenUSDC = new ethers.Contract(USDC, ERC20_ABI, ethers.provider); 85 | weth = new ethers.Contract(WETH, ERC20_ABI, ethers.provider); 86 | randomToken = new ethers.Contract(RANDOM_TOKEN, ERC20_ABI, ethers.provider); 87 | }); 88 | 89 | describe("constructor", () => { 90 | it("should deploy correctly", async () => { 91 | // Ensure all properties set correctly 92 | expect(await target.hub()).to.be.eq(MEAN_FINANCE_IDCAHUB); 93 | // Ensure whale is okay 94 | expect(whale.address).to.be.eq(WHALE); 95 | expect(tokenUSDC.address).to.be.eq(USDC); 96 | }); 97 | }); 98 | 99 | describe("xReceive", () => { 100 | before(async () => { 101 | // fund the target contract with eth, random token, and target asset 102 | await fund( 103 | constants.AddressZero, 104 | utils.parseEther("1"), 105 | wallet, 106 | target.address 107 | ); 108 | 109 | await fund( 110 | USDC, 111 | utils.parseUnits("1", ASSET_DECIMALS), 112 | whale, 113 | target.address 114 | ); 115 | 116 | await fund( 117 | randomToken.address, 118 | utils.parseUnits("1", await randomToken.decimals()), 119 | whale, 120 | target.address 121 | ); 122 | }); 123 | 124 | it("should work when from is ERC20", async () => { 125 | const fromAsset = randomToken; 126 | const toAsset = tokenUSDC; 127 | // get reasonable amount out 128 | 129 | const adapterBalance = await fromAsset.balanceOf(target.address); 130 | const randomDecimals = await fromAsset.decimals(); 131 | const normalized = 132 | randomDecimals > ASSET_DECIMALS 133 | ? adapterBalance.div( 134 | BigNumber.from(10).pow(randomDecimals - ASSET_DECIMALS) 135 | ) 136 | : adapterBalance.mul( 137 | BigNumber.from(10).pow(ASSET_DECIMALS - randomDecimals) 138 | ); 139 | // use 0.1% slippage (OP is > $2, adapter = usdc) 140 | const lowerBound = normalized.mul(10).div(10_000); 141 | const calldata = await target.connect(wallet).encode( 142 | 3000, //0.3% 143 | lowerBound, 144 | fromAsset.address, 145 | toAsset.address, 146 | swaps, 147 | interval, 148 | wallet.address, 149 | permissions 150 | ); 151 | 152 | const transferId = getRandomBytes32(); 153 | 154 | // send tx 155 | const tx = await target 156 | .connect(wallet) 157 | .xReceive( 158 | transferId, 159 | BigNumber.from(adapterBalance), 160 | fromAsset.address, 161 | wallet.address, 162 | DOMAIN, 163 | calldata 164 | ); 165 | 166 | const receipt = await tx.wait(); 167 | // Ensure tokens got sent to connext 168 | expect((await fromAsset.balanceOf(target.address)).toString()).to.be.eq( 169 | "0" 170 | ); 171 | }); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /test/meanFinance/MeanFinanceSource.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { deployments, ethers } from "hardhat"; 3 | import { 4 | BigNumber, 5 | BigNumberish, 6 | constants, 7 | Contract, 8 | providers, 9 | utils, 10 | Wallet, 11 | } from "ethers"; 12 | import { DEFAULT_ARGS } from "../../deploy"; 13 | import { ERC20_ABI } from "@0xgafu/common-abi"; 14 | import ConnextInterface from "../../artifacts/@connext/interfaces/core/IConnext.sol/IConnext.json"; 15 | 16 | const fund = async ( 17 | asset: string, 18 | wei: BigNumberish, 19 | from: Wallet, 20 | to: string 21 | ): Promise => { 22 | if (asset === constants.AddressZero) { 23 | const tx = await from.sendTransaction({ to, value: wei }); 24 | // send eth 25 | return await tx.wait(); 26 | } 27 | 28 | // send tokens 29 | const token = new Contract(asset, ERC20_ABI, from); 30 | const tx = await token.transfer(to, wei); 31 | return await tx.wait(); 32 | }; 33 | 34 | describe("MeanFinanceSource", function () { 35 | // Set up constants (will mirror what deploy fixture uses) 36 | const { WETH, USDC, CONNEXT, DOMAIN } = DEFAULT_ARGS[31337]; 37 | const UNISWAP_SWAP_ROUTER = "0xE592427A0AEce92De3Edee1F18E0157C05861564"; 38 | const WHALE = "0x385BAe68690c1b86e2f1Ad75253d080C14fA6e16"; // this is the address that should have weth, source, and random addr 39 | const NOT_ZERO_ADDRESS = "0x7088C5611dAE159A640d940cde0a3221a4af8896"; 40 | const RANDOM_TOKEN = "0x4200000000000000000000000000000000000042"; // this is OP 41 | const ASSET_DECIMALS = 6; // USDC decimals on op 42 | 43 | // Set up variables 44 | let connext: Contract; 45 | let source: Contract; 46 | let wallet: Wallet; 47 | let whale: Wallet; 48 | let tokenUSDC: Contract; 49 | let weth: Contract; 50 | let randomToken: Contract; 51 | 52 | before(async () => { 53 | // get wallet 54 | [wallet] = (await ethers.getSigners()) as unknown as Wallet[]; 55 | // get whale 56 | whale = (await ethers.getImpersonatedSigner(WHALE)) as unknown as Wallet; 57 | // deploy contract 58 | const { MeanFinanceSource } = await deployments.fixture([ 59 | "meanfinancesource", 60 | ]); 61 | source = new Contract( 62 | MeanFinanceSource.address, 63 | MeanFinanceSource.abi, 64 | ethers.provider 65 | ); 66 | 67 | connext = new Contract(CONNEXT, ConnextInterface.abi, ethers.provider); 68 | // setup tokens 69 | tokenUSDC = new ethers.Contract(USDC, ERC20_ABI, ethers.provider); 70 | weth = new ethers.Contract(WETH, ERC20_ABI, ethers.provider); 71 | randomToken = new ethers.Contract(RANDOM_TOKEN, ERC20_ABI, ethers.provider); 72 | }); 73 | 74 | describe("constructor", () => { 75 | it("should deploy correctly", async () => { 76 | // Ensure all properties set correctly 77 | expect(await source.swapRouter()).to.be.eq(UNISWAP_SWAP_ROUTER); 78 | // Ensure whale is okay 79 | expect(whale.address).to.be.eq(WHALE); 80 | expect(tokenUSDC.address).to.be.eq(USDC); 81 | }); 82 | }); 83 | 84 | describe("xDeposit", () => { 85 | before(async () => { 86 | // fund wallet with eth, random token, and source asset 87 | await fund( 88 | USDC, 89 | utils.parseUnits("1", ASSET_DECIMALS), 90 | whale, 91 | wallet.address 92 | ); 93 | 94 | await fund( 95 | randomToken.address, 96 | utils.parseUnits("1", await randomToken.decimals()), 97 | whale, 98 | wallet.address 99 | ); 100 | }); 101 | 102 | it("should work input non-connext ERC20", async () => { 103 | const target = NOT_ZERO_ADDRESS; 104 | const destinationDomain = "6648936"; 105 | const inputAsset = randomToken; 106 | const connextAsset = tokenUSDC; 107 | 108 | const inputBalance = await inputAsset.balanceOf(wallet.address); 109 | const inputDecimals = await inputAsset.decimals(); 110 | const outputDecimals = await connextAsset.decimals(); 111 | const normalized = 112 | inputDecimals > outputDecimals 113 | ? inputBalance.div( 114 | BigNumber.from(10).pow(inputDecimals - outputDecimals) 115 | ) 116 | : inputBalance.mul( 117 | BigNumber.from(10).pow(outputDecimals - inputDecimals) 118 | ); 119 | // use 0.1% slippage (OP is > $2, adapter = usdc) 120 | const lowerBound = normalized.mul(10).div(10_000); 121 | const calldata = utils.defaultAbiCoder.encode(["string"], ["hello"]); 122 | 123 | const approveTx = await inputAsset 124 | .connect(wallet) 125 | .approve(source.address, inputBalance); 126 | 127 | const approveReceipt = await approveTx.wait(); 128 | 129 | // send tx 130 | const tx = await source 131 | .connect(wallet) 132 | .xDeposit( 133 | target, 134 | destinationDomain, 135 | inputAsset.address, 136 | connextAsset.address, 137 | inputBalance, 138 | 3000, 139 | 3000, 140 | lowerBound, 141 | calldata, 142 | { value: 0 } 143 | ); 144 | 145 | const receipt = await tx.wait(); 146 | // Ensure tokens got sent to connext 147 | expect((await inputAsset.balanceOf(source.address)).toString()).to.be.eq( 148 | "0" 149 | ); 150 | }); 151 | 152 | it.skip("should work input AddressZero", async () => { 153 | const target = NOT_ZERO_ADDRESS; 154 | const destinationDomain = "6648936"; 155 | const inputAsset = constants.AddressZero; 156 | const connextAsset = weth; 157 | 158 | const inputBalance = utils.parseEther("0.5"); 159 | const calldata = utils.defaultAbiCoder.encode(["string"], ["hello"]); 160 | 161 | // send tx 162 | const tx = await source 163 | .connect(wallet) 164 | .xDeposit( 165 | target, 166 | destinationDomain, 167 | inputAsset, 168 | connextAsset.address, 169 | inputBalance, 170 | 3000, 171 | 0, 172 | 0, 173 | calldata, 174 | { value: inputBalance } 175 | ); 176 | 177 | const receipt = await tx.wait(); 178 | // Ensure tokens got sent to connext 179 | 180 | expect((await wallet.getBalance(source.address)).toString()).to.be.eq( 181 | "0" 182 | ); 183 | }); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /contracts/origin/Swap/SwapAndXCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.17; 3 | 4 | import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | import {IConnext} from "connext-interfaces/core/IConnext.sol"; 8 | import {SwapAdapter} from "../../shared/Swap/SwapAdapter.sol"; 9 | 10 | contract SwapAndXCall is SwapAdapter { 11 | // Connext address on this domain 12 | IConnext connext; 13 | 14 | constructor(address _connext) SwapAdapter() { 15 | connext = IConnext(_connext); 16 | } 17 | 18 | // EXTERNAL FUNCTIONS 19 | /** 20 | * @notice Calls a swapper contract and then calls xcall on connext 21 | * @dev Data for the swap is generated offchain to call to the appropriate swapper contract 22 | * Function is payable since it uses the relayer fee in native asset 23 | * @param _fromAsset Address of the asset to swap from 24 | * @param _toAsset Address of the asset to swap to 25 | * @param _amountIn Amount of the asset to swap from 26 | * @param _swapper Address of the swapper contract 27 | * @param _swapData Data to call the swapper contract with 28 | * @param _destination Destination of the xcall 29 | * @param _to Address to send the asset and call with the calldata on the destination 30 | * @param _delegate Delegate address 31 | * @param _slippage Total slippage amount accepted 32 | * @param _callData Calldata to call the destination with 33 | */ 34 | function swapAndXCall( 35 | address _fromAsset, 36 | address _toAsset, 37 | uint256 _amountIn, 38 | address _swapper, 39 | bytes calldata _swapData, 40 | uint32 _destination, 41 | address _to, 42 | address _delegate, 43 | uint256 _slippage, 44 | bytes calldata _callData 45 | ) external payable { 46 | uint256 amountOut = _fromAsset == address(0) 47 | ? _setupAndSwapETH(_toAsset, _amountIn, _swapper, _swapData) 48 | : _setupAndSwap(_fromAsset, _toAsset, _amountIn, _swapper, _swapData); 49 | 50 | connext.xcall{value: _fromAsset == address(0) ? msg.value - _amountIn : msg.value}( 51 | _destination, 52 | _to, 53 | _toAsset, 54 | _delegate, 55 | amountOut, 56 | _slippage, 57 | _callData 58 | ); 59 | } 60 | 61 | /** 62 | * @notice Calls a swapper contract and then calls xcall on connext 63 | * @dev Data for the swap is generated offchain to call to the appropriate swapper contract 64 | * Pays relayer fee from the input asset 65 | * @param _fromAsset Address of the asset to swap from 66 | * @param _toAsset Address of the asset to swap to 67 | * @param _amountIn Amount of the asset to swap from 68 | * @param _swapper Address of the swapper contract 69 | * @param _swapData Data to call the swapper contract with 70 | * @param _destination Destination of the xcall 71 | * @param _to Address to send the asset and call with the calldata on the destination 72 | * @param _delegate Delegate address 73 | * @param _slippage Total slippage amount accepted 74 | * @param _callData Calldata to call the destination with 75 | * @param _relayerFee Relayer fee to pay in the input asset 76 | */ 77 | function swapAndXCall( 78 | address _fromAsset, 79 | address _toAsset, 80 | uint256 _amountIn, 81 | address _swapper, 82 | bytes calldata _swapData, 83 | uint32 _destination, 84 | address _to, 85 | address _delegate, 86 | uint256 _slippage, 87 | bytes calldata _callData, 88 | uint256 _relayerFee 89 | ) external payable { 90 | uint256 amountOut = _fromAsset == address(0) 91 | ? _setupAndSwapETH(_toAsset, _amountIn, _swapper, _swapData) 92 | : _setupAndSwap(_fromAsset, _toAsset, _amountIn, _swapper, _swapData); 93 | 94 | connext.xcall(_destination, _to, _toAsset, _delegate, amountOut - _relayerFee, _slippage, _callData, _relayerFee); 95 | } 96 | 97 | // INTERNAL FUNCTIONS 98 | 99 | /** 100 | * @notice Sets up the swap and returns the amount out 101 | * @dev Handles approvals to the connext contract and the swapper contract 102 | * @param _fromAsset Address of the asset to swap from 103 | * @param _toAsset Address of the asset to swap to 104 | * @param _amountIn Amount of the asset to swap from 105 | * @param _swapper Address of the swapper contract 106 | * @param _swapData Data to call the swapper contract with 107 | * @return amountOut Amount of the asset after swap 108 | */ 109 | function _setupAndSwap( 110 | address _fromAsset, 111 | address _toAsset, 112 | uint256 _amountIn, 113 | address _swapper, 114 | bytes calldata _swapData 115 | ) internal returns (uint256 amountOut) { 116 | TransferHelper.safeTransferFrom(_fromAsset, msg.sender, address(this), _amountIn); 117 | 118 | if (_fromAsset != _toAsset) { 119 | require(_swapper != address(0), "SwapAndXCall: zero swapper!"); 120 | 121 | // If fromAsset is not native and allowance is less than amountIn 122 | if (IERC20(_fromAsset).allowance(address(this), _swapper) < _amountIn) { 123 | TransferHelper.safeApprove(_fromAsset, _swapper, type(uint256).max); 124 | } 125 | 126 | amountOut = this.directSwapperCall(_swapper, _swapData); 127 | } else { 128 | amountOut = _amountIn; 129 | } 130 | 131 | if (_toAsset != address(0)) { 132 | if (IERC20(_toAsset).allowance(address(this), address(connext)) < amountOut) { 133 | TransferHelper.safeApprove(_toAsset, address(connext), type(uint256).max); 134 | } 135 | } 136 | } 137 | 138 | /** 139 | * @notice Sets up the swap and returns the amount out 140 | * @dev Handles approvals to the connext contract and the swapper contract 141 | * @param _toAsset Address of the asset to swap to 142 | * @param _amountIn Amount of the asset to swap from 143 | * @param _swapper Address of the swapper contract 144 | * @param _swapData Data to call the swapper contract with 145 | * @return amountOut Amount of the asset after swap 146 | */ 147 | function _setupAndSwapETH( 148 | address _toAsset, 149 | uint256 _amountIn, 150 | address _swapper, 151 | bytes calldata _swapData 152 | ) internal returns (uint256 amountOut) { 153 | require(msg.value >= _amountIn, "SwapAndXCall: msg.value != _amountIn"); 154 | 155 | if (_toAsset != address(0)) { 156 | require(_swapper != address(0), "SwapAndXCall: zero swapper!"); 157 | amountOut = this.directSwapperCall{value: _amountIn}(_swapper, _swapData); 158 | 159 | if (IERC20(_toAsset).allowance(address(this), address(connext)) < amountOut) { 160 | TransferHelper.safeApprove(_toAsset, address(connext), type(uint256).max); 161 | } 162 | } else { 163 | amountOut = _amountIn; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /broadcast/DeployLockboxAdapterBlast.s.sol/1/run-latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactions": [ 3 | { 4 | "hash": "0xb2647531460473ecf20dd845c9e8256d1fefbec11c9b5947c5cfaf76b907364a", 5 | "transactionType": "CREATE", 6 | "contractName": "LockboxAdapterBlast", 7 | "contractAddress": "0xBE1a8Fe5CB3B7E50825Cef73aFDf29E3fc34DE33", 8 | "function": null, 9 | "arguments": [ 10 | "0x697402166Fbf2F22E970df8a6486Ef171dbfc524", 11 | "0xBf29A2D67eFb6766E44c163B19C6F4118b164702" 12 | ], 13 | "transaction": { 14 | "type": "0x02", 15 | "from": "0xade09131c6f43fe22c2cbabb759636c43cfc181e", 16 | "gas": "0xa6de5", 17 | "value": "0x0", 18 | "data": "0x60c0346100b757601f61096238819003918201601f19168301916001600160401b038311848410176100bc5780849260409485528339810103126100b757610052602061004b836100d2565b92016100d2565b906001600160a01b03808216159081156100ac575b5061009a5760805260a05260405161087b90816100e7823960805181610273015260a051818181610102015261015c0152f35b60405163e6c4247b60e01b8152600490fd5b905082161538610067565b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036100b75756fe608080604052600436101561001357600080fd5b6000803560e01c63636626f11461002957600080fd5b346103705760c0366003190112610370576004356001600160a01b038116810361046f57602435926001600160a01b038416840361046b57604435916001600160a01b0383168303610467576084359163ffffffff831683036104635767ffffffffffffffff60a435116104635736602360a4350112156104635767ffffffffffffffff60a43560040135116104635736602460a4356004013560a4350101116104635760643515610454575060405163476a536360e11b81526001600160a01b038681166004830152909190602090839060249082907f0000000000000000000000000000000000000000000000000000000000000000165afa918215610396578592610433575b5060405163eec9567960e01b81526001600160a01b038381166004830152602090829060249082907f0000000000000000000000000000000000000000000000000000000000000000165afa9081156104285786916103f9575b506001600160a01b0383161580156103e8575b6103d6576001600160a01b03858116908416036103b5576040516323b872dd60e01b60208201523360248201523060448201526064803581830152815260a0810167ffffffffffffffff8111828210176103a157604052959687966102269190610212906001600160a01b03831661062c565b6064359083906001600160a01b03166104e3565b6001600160a01b0381163b1561037e5760405163b6b55f2560e01b8152606435600482015296869188916024918391906001600160a01b03165af1801561039657610382575b93945084937f00000000000000000000000000000000000000000000000000000000000000006102a8606435826001600160a01b0386166104e3565b6001600160a01b031690813b1561037e57859363ffffffff9160405196879563540abf7360e01b875260018060a01b0316600487015260018060a01b0316602486015260018060a01b03166044850152606435606485015216608483015260c060a483015260a4356004013560c483015260a43560040135602460a4350160e48401378260e460a4356004013584010152818360e482601f19601f60a43560040135011681010301925af18015610373576103605750f35b61036990610473565b6103705780f35b80fd5b6040513d84823e3d90fd5b8580fd5b93909461038e90610473565b92849061026c565b6040513d87823e3d90fd5b634e487b7160e01b88526041600452602488fd5b60405163359ff56f60e01b81526001600160a01b0386166004820152602490fd5b60405163e6c4247b60e01b8152600490fd5b506001600160a01b0381161561019f565b61041b915060203d602011610421575b610413818361049d565b8101906104bf565b3861018c565b503d610409565b6040513d88823e3d90fd5b61044d91925060203d60201161042157610413818361049d565b9038610132565b63820bf1e560e01b8152600490fd5b8480fd5b8380fd5b8280fd5b5080fd5b67ffffffffffffffff811161048757604052565b634e487b7160e01b600052604160045260246000fd5b90601f8019910116810190811067ffffffffffffffff82111761048757604052565b908160209103126104de57516001600160a01b03811681036104de5790565b600080fd5b91801580156105a7575b156105435760405163095ea7b360e01b60208201526001600160a01b0390921660248301526044808301919091528152608081019167ffffffffffffffff831182841017610487576105419260405261062c565b565b60405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b6064820152608490fd5b50604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526020908290604490829088165afa908115610620576000916105ef575b50156104ed565b906020823d8211610618575b816106086020938361049d565b81010312610370575051386105e8565b3d91506105fb565b6040513d6000823e3d90fd5b60018060a01b0316906040516040810167ffffffffffffffff9082811082821117610487576040526020938483527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564858401526000808587829751910182855af1903d15610766573d92831161075257906106c7939291604051926106ba88601f19601f840116018561049d565b83523d868885013e610771565b8051806106d5575b50505050565b8184918101031261046f578201519081159182150361037057506106fb578080806106cf565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b634e487b7160e01b85526041600452602485fd5b906106c79392506060915b919290156107d35750815115610785575090565b3b1561078e5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b8251909150156107e65750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b82851061082c575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061080956fea2646970667358221220ccb5894e7ee5d863bbdcb36e2ce6551aebeed85599ea3c6c477c272f4a0713ec64736f6c63430008140033000000000000000000000000697402166fbf2f22e970df8a6486ef171dbfc524000000000000000000000000bf29a2d67efb6766e44c163b19c6f4118b164702", 19 | "nonce": "0x7e9", 20 | "accessList": [] 21 | }, 22 | "additionalContracts": [], 23 | "isFixedGasLimit": false 24 | } 25 | ], 26 | "receipts": [ 27 | { 28 | "transactionHash": "0xb2647531460473ecf20dd845c9e8256d1fefbec11c9b5947c5cfaf76b907364a", 29 | "transactionIndex": "0x6", 30 | "blockHash": "0xb11507aa58fed7d0cf00e557a5602647ea73532ab2a78f5eaa5937409ddf455c", 31 | "blockNumber": "0x129f710", 32 | "from": "0xade09131C6f43fe22C2CbABb759636C43cFc181e", 33 | "to": null, 34 | "cumulativeGasUsed": "0x1896e9", 35 | "gasUsed": "0x80660", 36 | "contractAddress": "0xBE1a8Fe5CB3B7E50825Cef73aFDf29E3fc34DE33", 37 | "logs": [], 38 | "status": "0x1", 39 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 40 | "type": "0x2", 41 | "effectiveGasPrice": "0xe4d8b75bf" 42 | } 43 | ], 44 | "libraries": [], 45 | "pending": [], 46 | "returns": {}, 47 | "timestamp": 1711564889, 48 | "chain": 1, 49 | "commit": "c53bb2b" 50 | } -------------------------------------------------------------------------------- /contracts/test/unit/integration/Instadapp/InstadappAdapter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {IDSA} from "../../../../integration/Instadapp/interfaces/IDSA.sol"; 5 | import {TestHelper} from "../../../utils/TestHelper.sol"; 6 | import {InstadappAdapter} from "../../../../integration/Instadapp/InstadappAdapter.sol"; 7 | import "forge-std/console.sol"; 8 | 9 | contract MockInstadappReceiver is InstadappAdapter { 10 | constructor() {} 11 | 12 | function tryAuthCast( 13 | address dsaAddress, 14 | address auth, 15 | bytes memory signature, 16 | CastData memory castData, 17 | bytes32 salt, 18 | uint256 deadline 19 | ) external payable { 20 | authCast(dsaAddress, auth, signature, castData, salt, deadline); 21 | } 22 | 23 | function tryVerify( 24 | address auth, 25 | bytes memory signature, 26 | CastData memory castData, 27 | bytes32 salt, 28 | uint256 deadline 29 | ) external returns (bool) { 30 | return verify(auth, signature, castData, salt, deadline); 31 | } 32 | } 33 | 34 | contract InstadappAdapterTest is TestHelper { 35 | // ============ Storage ============ 36 | address dsa = address(1); 37 | address instadappReceiver = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f; 38 | 39 | uint256 deadline = 100; 40 | uint256 timestamp = 90; 41 | 42 | // ============ Test set up ============ 43 | function setUp() public override { 44 | super.setUp(); 45 | MockInstadappReceiver _instadappReceiver = new MockInstadappReceiver(); 46 | vm.etch(instadappReceiver, address(_instadappReceiver).code); 47 | } 48 | 49 | // ============ Utils ============ 50 | function utils_dsaMocks(bool isAuth) public { 51 | vm.mockCall(dsa, abi.encodeWithSelector(IDSA.isAuth.selector), abi.encode(isAuth)); 52 | vm.mockCall(dsa, abi.encodeWithSelector(IDSA.cast.selector), abi.encode(bytes32(abi.encode(1)))); 53 | } 54 | 55 | // ============ InstadappAdapter.authCast ============ 56 | function test_InstadappAdapter__authCast_shouldRevertIfInvalidAuth() public { 57 | utils_dsaMocks(false); 58 | 59 | address originSender = address(0x123); 60 | 61 | string[] memory _targetNames = new string[](2); 62 | _targetNames[0] = "target1"; 63 | _targetNames[1] = "target2"; 64 | bytes[] memory _datas = new bytes[](2); 65 | _datas[0] = bytes("data1"); 66 | _datas[1] = bytes("data2"); 67 | address _origin = originSender; 68 | 69 | InstadappAdapter.CastData memory castData = InstadappAdapter.CastData(_targetNames, _datas, _origin); 70 | 71 | bytes memory signature = bytes("0x111"); 72 | address auth = originSender; 73 | bytes32 salt = bytes32(abi.encode(1)); 74 | 75 | vm.expectRevert(bytes("Invalid Auth")); 76 | MockInstadappReceiver(instadappReceiver).tryAuthCast(dsa, auth, signature, castData, salt, deadline); 77 | } 78 | 79 | function test_InstadappAdapter__authCast_shouldRevertIfInvalidSignature() public { 80 | utils_dsaMocks(true); 81 | 82 | address originSender = address(0x123); 83 | 84 | string[] memory _targetNames = new string[](2); 85 | _targetNames[0] = "target1"; 86 | _targetNames[1] = "target2"; 87 | bytes[] memory _datas = new bytes[](2); 88 | _datas[0] = bytes("data1"); 89 | _datas[1] = bytes("data2"); 90 | address _origin = originSender; 91 | 92 | InstadappAdapter.CastData memory castData = InstadappAdapter.CastData(_targetNames, _datas, _origin); 93 | 94 | bytes 95 | memory signature = hex"e91f49cb8bf236eafb590ba328a6ca75f4d189fa51bfce2ac774541801c17d3f2d3df798f18c0520db5a98d33362d507f890d5904c2aea1dd059a9b0f05fb3ad1c"; 96 | 97 | address auth = originSender; 98 | bytes32 salt = bytes32(abi.encode(1)); 99 | vm.expectRevert(bytes("Invalid signature")); 100 | MockInstadappReceiver(instadappReceiver).tryAuthCast(dsa, auth, signature, castData, salt, deadline); 101 | } 102 | 103 | function test_InstadappAdapter__authCast_shouldWork() public { 104 | utils_dsaMocks(true); 105 | 106 | address originSender = address(0xc1aAED5D41a3c3c33B1978EA55916f9A3EE1B397); 107 | 108 | string[] memory _targetNames = new string[](3); 109 | _targetNames[0] = "target111"; 110 | _targetNames[1] = "target222"; 111 | _targetNames[2] = "target333"; 112 | bytes[] memory _datas = new bytes[](3); 113 | _datas[0] = bytes("0x111"); 114 | _datas[1] = bytes("0x222"); 115 | _datas[2] = bytes("0x333"); 116 | address _origin = originSender; 117 | bytes32 salt = bytes32(abi.encode(1)); 118 | 119 | InstadappAdapter.CastData memory castData = InstadappAdapter.CastData(_targetNames, _datas, _origin); 120 | 121 | bytes 122 | memory signature = hex"ec163cca0d31ea58537dfff8377fdbd957fb0ba58088f74436af944bf1c3248148910f14a13b7d0b6d707fb7478cfd7f9ae3830b02d0a5e5584ef7648460a8d71c"; 123 | 124 | address auth = originSender; 125 | vm.warp(timestamp); 126 | MockInstadappReceiver(instadappReceiver).tryAuthCast{value: 1}(dsa, auth, signature, castData, salt, deadline); 127 | } 128 | 129 | // ============ InstadappAdapter.verify ============ 130 | function test_InstadappAdapter__verify_shouldReturnTrue() public { 131 | utils_dsaMocks(true); 132 | 133 | address originSender = address(0xc1aAED5D41a3c3c33B1978EA55916f9A3EE1B397); 134 | 135 | string[] memory _targetNames = new string[](3); 136 | _targetNames[0] = "target111"; 137 | _targetNames[1] = "target222"; 138 | _targetNames[2] = "target333"; 139 | bytes[] memory _datas = new bytes[](3); 140 | _datas[0] = bytes("0x111"); 141 | _datas[1] = bytes("0x222"); 142 | _datas[2] = bytes("0x333"); 143 | address _origin = originSender; 144 | bytes32 salt = bytes32(abi.encode(1)); 145 | 146 | InstadappAdapter.CastData memory castData = InstadappAdapter.CastData(_targetNames, _datas, _origin); 147 | 148 | bytes 149 | memory signature = hex"ec163cca0d31ea58537dfff8377fdbd957fb0ba58088f74436af944bf1c3248148910f14a13b7d0b6d707fb7478cfd7f9ae3830b02d0a5e5584ef7648460a8d71c"; 150 | 151 | address auth = originSender; 152 | vm.warp(timestamp); 153 | assertEq(MockInstadappReceiver(instadappReceiver).tryVerify(auth, signature, castData, salt, deadline), true); 154 | } 155 | 156 | function test_InstadappAdapter__verify_shouldReturnFalse() public { 157 | utils_dsaMocks(true); 158 | 159 | address originSender = address(0x123); 160 | 161 | string[] memory _targetNames = new string[](2); 162 | _targetNames[0] = "target1"; 163 | _targetNames[1] = "target2"; 164 | bytes[] memory _datas = new bytes[](2); 165 | _datas[0] = bytes("data1"); 166 | _datas[1] = bytes("data2"); 167 | address _origin = originSender; 168 | 169 | InstadappAdapter.CastData memory castData = InstadappAdapter.CastData(_targetNames, _datas, _origin); 170 | 171 | bytes 172 | memory signature = hex"e91f49cb8bf236eafb590ba328a6ca75f4d189fa51bfce2ac774541801c17d3f2d3df798f18c0520db5a98d33362d507f890d5904c2aea1dd059a9b0f05fb3ad1c"; 173 | 174 | address auth = originSender; 175 | bytes32 salt = bytes32(abi.encode(1)); 176 | assertEq(MockInstadappReceiver(instadappReceiver).tryVerify(auth, signature, castData, salt, deadline), false); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /contracts/shared/Swap/interfaces/IPancakeSmartRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.17; 3 | pragma abicoder v2; 4 | 5 | /// @title Callback for IPancakeV3PoolActions#swap 6 | /// @notice Any contract that calls IPancakeV3PoolActions#swap must implement this interface 7 | interface IPancakeV3SwapCallback { 8 | /// @notice Called to `msg.sender` after executing a swap via IPancakeV3Pool#swap. 9 | /// @dev In the implementation you must pay the pool tokens owed for the swap. 10 | /// The caller of this method must be checked to be a PancakeV3Pool deployed by the canonical PancakeV3Factory. 11 | /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. 12 | /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by 13 | /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. 14 | /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by 15 | /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. 16 | /// @param data Any data passed through by the caller via the IPancakeV3PoolActions#swap call 17 | function pancakeV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external; 18 | } 19 | 20 | /// @title Router token swapping functionality 21 | /// @notice Functions for swapping tokens via PancakeSwap V3 22 | interface IV3SwapRouter is IPancakeV3SwapCallback { 23 | struct ExactInputSingleParams { 24 | address tokenIn; 25 | address tokenOut; 26 | uint24 fee; 27 | address recipient; 28 | uint256 amountIn; 29 | uint256 amountOutMinimum; 30 | uint160 sqrtPriceLimitX96; 31 | } 32 | 33 | /// @notice Swaps `amountIn` of one token for as much as possible of another token 34 | /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, 35 | /// and swap the entire amount, enabling contracts to send tokens before calling this function. 36 | /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata 37 | /// @return amountOut The amount of the received token 38 | function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); 39 | 40 | struct ExactInputParams { 41 | bytes path; 42 | address recipient; 43 | uint256 amountIn; 44 | uint256 amountOutMinimum; 45 | } 46 | 47 | /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path 48 | /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, 49 | /// and swap the entire amount, enabling contracts to send tokens before calling this function. 50 | /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata 51 | /// @return amountOut The amount of the received token 52 | function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); 53 | 54 | struct ExactOutputSingleParams { 55 | address tokenIn; 56 | address tokenOut; 57 | uint24 fee; 58 | address recipient; 59 | uint256 amountOut; 60 | uint256 amountInMaximum; 61 | uint160 sqrtPriceLimitX96; 62 | } 63 | 64 | /// @notice Swaps as little as possible of one token for `amountOut` of another token 65 | /// that may remain in the router after the swap. 66 | /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata 67 | /// @return amountIn The amount of the input token 68 | function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); 69 | 70 | struct ExactOutputParams { 71 | bytes path; 72 | address recipient; 73 | uint256 amountOut; 74 | uint256 amountInMaximum; 75 | } 76 | 77 | /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) 78 | /// that may remain in the router after the swap. 79 | /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata 80 | /// @return amountIn The amount of the input token 81 | function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); 82 | } 83 | 84 | /// @title Periphery Payments 85 | /// @notice Functions to ease deposits and withdrawals of ETH 86 | interface IPeripheryPayments { 87 | /// @notice Unwraps the contract's WETH9 balance and sends it to recipient as ETH. 88 | /// @dev The amountMinimum parameter prevents malicious contracts from stealing WETH9 from users. 89 | /// @param amountMinimum The minimum amount of WETH9 to unwrap 90 | /// @param recipient The address receiving ETH 91 | function unwrapWETH9(uint256 amountMinimum, address recipient) external payable; 92 | 93 | /// @notice Refunds any ETH balance held by this contract to the `msg.sender` 94 | /// @dev Useful for bundling with mint or increase liquidity that uses ether, or exact output swaps 95 | /// that use ether for the input amount. And in PancakeSwap Router, this would be called 96 | /// at the very end of swap 97 | function refundETH() external payable; 98 | 99 | /// @notice Transfers the full amount of a token held by this contract to recipient 100 | /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users 101 | /// @param token The contract address of the token which will be transferred to `recipient` 102 | /// @param amountMinimum The minimum amount of token required for a transfer 103 | /// @param recipient The destination address of the token 104 | function sweepToken(address token, uint256 amountMinimum, address recipient) external payable; 105 | } 106 | 107 | /// @title Periphery Payments Extended 108 | /// @notice Functions to ease deposits and withdrawals of ETH and tokens 109 | interface IPeripheryPaymentsExtended is IPeripheryPayments { 110 | // function unwrapWETH(uint256 amount, address to) external payable; 111 | 112 | /// @notice Wraps the contract's ETH balance into WETH9 113 | /// @dev The resulting WETH9 is custodied by the router, thus will require further distribution 114 | /// @param value The amount of ETH to wrap 115 | function wrapETH(uint256 value) external payable; 116 | 117 | /// @notice Transfers the full amount of a token held by this contract to msg.sender 118 | /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users 119 | /// @param token The contract address of the token which will be transferred to msg.sender 120 | /// @param amountMinimum The minimum amount of token required for a transfer 121 | function sweepToken(address token, uint256 amountMinimum) external payable; 122 | 123 | /// @notice Transfers the specified amount of a token from the msg.sender to address(this) 124 | /// @param token The token to pull 125 | /// @param value The amount to pay 126 | function pull(address token, uint256 value) external payable; 127 | } 128 | 129 | /// @title Immutable state 130 | /// @notice Functions that return immutable state of the router 131 | interface IPeripheryImmutableState { 132 | /// @return Returns the address of the PancakeSwap V3 deployer 133 | function deployer() external view returns (address); 134 | 135 | /// @return Returns the address of the PancakeSwap V3 factory 136 | function factory() external view returns (address); 137 | 138 | /// @return Returns the address of WETH9 139 | function WETH9() external view returns (address); 140 | } 141 | 142 | /// @title Router token swapping functionality 143 | interface IPancakeSmartRouter is IV3SwapRouter, IPeripheryPaymentsExtended, IPeripheryImmutableState { 144 | 145 | } 146 | -------------------------------------------------------------------------------- /contracts/destination/xreceivers/ForwarderXReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import {IConnext} from "@connext/interfaces/core/IConnext.sol"; 5 | import {IXReceiver} from "@connext/interfaces/core/IXReceiver.sol"; 6 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | 8 | /** 9 | * @title ForwarderXReceiver 10 | * @author Connext 11 | * @notice Abstract contract to allow for forwarding a call. Handles security and error handling. 12 | * @dev This is meant to be used in unauthenticated flows, so the data passed in is not guaranteed to be correct. 13 | * This is meant to be used when there are funds passed into the contract that need to be forwarded to another contract. 14 | */ 15 | abstract contract ForwarderXReceiver is IXReceiver { 16 | // The Connext contract on this domain 17 | IConnext public immutable connext; 18 | 19 | /// EVENTS 20 | event ForwardedFunctionCallFailed(bytes32 _transferId); 21 | event ForwardedFunctionCallFailed(bytes32 _transferId, string _errorMessage); 22 | event ForwardedFunctionCallFailed(bytes32 _transferId, uint _errorCode); 23 | event ForwardedFunctionCallFailed(bytes32 _transferId, bytes _lowLevelData); 24 | event Prepared(bytes32 _transferId, bytes _data, uint256 _amount, address _asset); 25 | 26 | /// ERRORS 27 | error ForwarderXReceiver__onlyConnext(address sender); 28 | error ForwarderXReceiver__prepareAndForward_notThis(address sender); 29 | 30 | /// MODIFIERS 31 | /** @notice A modifier to ensure that only the Connext contract on this domain can be the caller. 32 | * If this is not enforced, then funds on this contract may potentially be claimed by any EOA. 33 | */ 34 | modifier onlyConnext() { 35 | if (msg.sender != address(connext)) { 36 | revert ForwarderXReceiver__onlyConnext(msg.sender); 37 | } 38 | _; 39 | } 40 | 41 | /** 42 | * @param _connext - The address of the Connext contract on this domain 43 | */ 44 | constructor(address _connext) { 45 | connext = IConnext(_connext); 46 | } 47 | 48 | /** 49 | * @notice Receives funds from Connext and forwards them to a contract, using a two step process which is defined by the developer. 50 | * @dev _originSender and _origin are not used in this implementation because this is meant for an "unauthenticated" call. This means 51 | * any router can call this function and no guarantees are made on the data passed in. This should only be used when there are 52 | * funds passed into the contract that need to be forwarded to another contract. This guarantees economically that there is no 53 | * reason to call this function maliciously, because the router would be spending their own funds. 54 | * @param _transferId - The transfer ID of the transfer that triggered this call. 55 | * @param _amount - The amount of funds received in this transfer. 56 | * @param _asset - The asset of the funds received in this transfer. 57 | * @param _callData - The data to be prepared and forwarded. Fallback address needs to be encoded in the data to be used in case the forward fails. 58 | */ 59 | function xReceive( 60 | bytes32 _transferId, 61 | uint256 _amount, // Final amount received via Connext (after AMM swaps, if applicable) 62 | address _asset, 63 | address /*_originSender*/, 64 | uint32 /*_origin*/, 65 | bytes calldata _callData 66 | ) external onlyConnext returns (bytes memory) { 67 | // Decode calldata 68 | (address _fallbackAddress, bytes memory _data) = abi.decode(_callData, (address, bytes)); 69 | 70 | bool successfulForward; 71 | try this.prepareAndForward(_transferId, _data, _amount, _asset) returns (bool success) { 72 | successfulForward = success; 73 | if (!success) { 74 | emit ForwardedFunctionCallFailed(_transferId); 75 | } 76 | // transfer to fallback address if forwardFunctionCall fails 77 | } catch Error(string memory _errorMessage) { 78 | // This is executed in case 79 | // revert was called with a reason string 80 | successfulForward = false; 81 | emit ForwardedFunctionCallFailed(_transferId, _errorMessage); 82 | } catch Panic(uint _errorCode) { 83 | // This is executed in case of a panic, 84 | // i.e. a serious error like division by zero 85 | // or overflow. The error code can be used 86 | // to determine the kind of error. 87 | successfulForward = false; 88 | emit ForwardedFunctionCallFailed(_transferId, _errorCode); 89 | } catch (bytes memory _lowLevelData) { 90 | // This is executed in case revert() was used. 91 | successfulForward = false; 92 | emit ForwardedFunctionCallFailed(_transferId, _lowLevelData); 93 | } 94 | if (!successfulForward) { 95 | IERC20(_asset).transfer(_fallbackAddress, _amount); 96 | } 97 | // Return the success status of the forwardFunctionCall 98 | return abi.encode(successfulForward); 99 | } 100 | 101 | /// INTERNAL 102 | /** 103 | * @notice Prepares the data for the function call and forwards it. This can execute 104 | * any arbitrary function call in a two step process. For example, _prepare can be used to swap funds 105 | * on a DEX, and _forwardFunctionCall can be used to call a contract with the swapped funds. 106 | * @dev This function is intended to be called by the xReceive function, and should not be called outside 107 | * of that context. The function is `public` so that it can be used with try-catch. 108 | * 109 | * @param _transferId - The transfer ID of the transfer that triggered this call 110 | * @param _data - The data to be prepared 111 | * @param _amount - The amount of funds received in this transfer 112 | * @param _asset - The asset of the funds received in this transfer 113 | */ 114 | function prepareAndForward( 115 | bytes32 _transferId, 116 | bytes memory _data, 117 | uint256 _amount, 118 | address _asset 119 | ) public returns (bool) { 120 | if (msg.sender != address(this)) { 121 | revert ForwarderXReceiver__prepareAndForward_notThis(msg.sender); 122 | } 123 | // Prepare for forwarding 124 | bytes memory _prepared = _prepare(_transferId, _data, _amount, _asset); 125 | emit Prepared(_transferId, _data, _amount, _asset); 126 | 127 | // Forward the function call 128 | return _forwardFunctionCall(_prepared, _transferId, _amount, _asset); 129 | } 130 | 131 | /// INTERNAL VIRTUAL 132 | /** 133 | * @notice Prepares the data for the function call. This can execute any arbitrary function call in a two step process. 134 | * For example, _prepare can be used to swap funds on a DEX, or do any other type of preparation, and pass on the 135 | * prepared data to _forwardFunctionCall. 136 | * @dev This function needs to be overriden in implementations of this contract. If no preparation is needed, this 137 | * function can be overriden to return the data as is. 138 | * 139 | * @param _transferId - The transfer ID of the transfer that triggered this call 140 | * @param _data - The data to be prepared 141 | * @param _amount - The amount of funds received in this transfer 142 | * @param _asset - The asset of the funds received in this transfer 143 | */ 144 | function _prepare( 145 | bytes32 _transferId, 146 | bytes memory _data, 147 | uint256 _amount, 148 | address _asset 149 | ) internal virtual returns (bytes memory) { 150 | return abi.encode(_data, _transferId, _amount, _asset); 151 | } 152 | 153 | /** 154 | * @notice Forwards the function call. This can execute any arbitrary function call in a two step process. 155 | * The first step is to prepare the data, and the second step is to forward the function call to a 156 | * given contract. 157 | * @dev This function needs to be overriden in implementations of this contract. 158 | * 159 | * @param _preparedData - The data to be forwarded, after processing in _prepare 160 | * @param _transferId - The transfer ID of the transfer that triggered this call 161 | * @param _amount - The amount of funds received in this transfer 162 | * @param _asset - The asset of the funds received in this transfer 163 | */ 164 | function _forwardFunctionCall( 165 | bytes memory _preparedData, 166 | bytes32 _transferId, 167 | uint256 _amount, 168 | address _asset 169 | ) internal virtual returns (bool) {} 170 | } 171 | -------------------------------------------------------------------------------- /test/helpers/abis/instaindex.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "address", 8 | "name": "sender", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "owner", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": true, 19 | "internalType": "address", 20 | "name": "account", 21 | "type": "address" 22 | }, 23 | { 24 | "indexed": true, 25 | "internalType": "address", 26 | "name": "origin", 27 | "type": "address" 28 | } 29 | ], 30 | "name": "LogAccountCreated", 31 | "type": "event" 32 | }, 33 | { 34 | "anonymous": false, 35 | "inputs": [ 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "_newAccount", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": true, 44 | "internalType": "address", 45 | "name": "_connectors", 46 | "type": "address" 47 | }, 48 | { 49 | "indexed": true, 50 | "internalType": "address", 51 | "name": "_check", 52 | "type": "address" 53 | } 54 | ], 55 | "name": "LogNewAccount", 56 | "type": "event" 57 | }, 58 | { 59 | "anonymous": false, 60 | "inputs": [ 61 | { 62 | "indexed": true, 63 | "internalType": "uint256", 64 | "name": "accountVersion", 65 | "type": "uint256" 66 | }, 67 | { 68 | "indexed": true, 69 | "internalType": "address", 70 | "name": "check", 71 | "type": "address" 72 | } 73 | ], 74 | "name": "LogNewCheck", 75 | "type": "event" 76 | }, 77 | { 78 | "anonymous": false, 79 | "inputs": [ 80 | { 81 | "indexed": true, 82 | "internalType": "address", 83 | "name": "master", 84 | "type": "address" 85 | } 86 | ], 87 | "name": "LogNewMaster", 88 | "type": "event" 89 | }, 90 | { 91 | "anonymous": false, 92 | "inputs": [ 93 | { 94 | "indexed": true, 95 | "internalType": "address", 96 | "name": "master", 97 | "type": "address" 98 | } 99 | ], 100 | "name": "LogUpdateMaster", 101 | "type": "event" 102 | }, 103 | { 104 | "inputs": [ 105 | { 106 | "internalType": "uint256", 107 | "name": "", 108 | "type": "uint256" 109 | } 110 | ], 111 | "name": "account", 112 | "outputs": [ 113 | { 114 | "internalType": "address", 115 | "name": "", 116 | "type": "address" 117 | } 118 | ], 119 | "stateMutability": "view", 120 | "type": "function" 121 | }, 122 | { 123 | "inputs": [ 124 | { 125 | "internalType": "address", 126 | "name": "_newAccount", 127 | "type": "address" 128 | }, 129 | { 130 | "internalType": "address", 131 | "name": "_connectors", 132 | "type": "address" 133 | }, 134 | { 135 | "internalType": "address", 136 | "name": "_check", 137 | "type": "address" 138 | } 139 | ], 140 | "name": "addNewAccount", 141 | "outputs": [], 142 | "stateMutability": "nonpayable", 143 | "type": "function" 144 | }, 145 | { 146 | "inputs": [ 147 | { 148 | "internalType": "address", 149 | "name": "_owner", 150 | "type": "address" 151 | }, 152 | { 153 | "internalType": "uint256", 154 | "name": "accountVersion", 155 | "type": "uint256" 156 | }, 157 | { 158 | "internalType": "address", 159 | "name": "_origin", 160 | "type": "address" 161 | } 162 | ], 163 | "name": "build", 164 | "outputs": [ 165 | { 166 | "internalType": "address", 167 | "name": "_account", 168 | "type": "address" 169 | } 170 | ], 171 | "stateMutability": "nonpayable", 172 | "type": "function" 173 | }, 174 | { 175 | "inputs": [ 176 | { 177 | "internalType": "address", 178 | "name": "_owner", 179 | "type": "address" 180 | }, 181 | { 182 | "internalType": "uint256", 183 | "name": "accountVersion", 184 | "type": "uint256" 185 | }, 186 | { 187 | "internalType": "address[]", 188 | "name": "_targets", 189 | "type": "address[]" 190 | }, 191 | { 192 | "internalType": "bytes[]", 193 | "name": "_datas", 194 | "type": "bytes[]" 195 | }, 196 | { 197 | "internalType": "address", 198 | "name": "_origin", 199 | "type": "address" 200 | } 201 | ], 202 | "name": "buildWithCast", 203 | "outputs": [ 204 | { 205 | "internalType": "address", 206 | "name": "_account", 207 | "type": "address" 208 | } 209 | ], 210 | "stateMutability": "payable", 211 | "type": "function" 212 | }, 213 | { 214 | "inputs": [ 215 | { 216 | "internalType": "uint256", 217 | "name": "accountVersion", 218 | "type": "uint256" 219 | }, 220 | { 221 | "internalType": "address", 222 | "name": "_newCheck", 223 | "type": "address" 224 | } 225 | ], 226 | "name": "changeCheck", 227 | "outputs": [], 228 | "stateMutability": "nonpayable", 229 | "type": "function" 230 | }, 231 | { 232 | "inputs": [ 233 | { 234 | "internalType": "address", 235 | "name": "_newMaster", 236 | "type": "address" 237 | } 238 | ], 239 | "name": "changeMaster", 240 | "outputs": [], 241 | "stateMutability": "nonpayable", 242 | "type": "function" 243 | }, 244 | { 245 | "inputs": [ 246 | { 247 | "internalType": "uint256", 248 | "name": "", 249 | "type": "uint256" 250 | } 251 | ], 252 | "name": "check", 253 | "outputs": [ 254 | { 255 | "internalType": "address", 256 | "name": "", 257 | "type": "address" 258 | } 259 | ], 260 | "stateMutability": "view", 261 | "type": "function" 262 | }, 263 | { 264 | "inputs": [ 265 | { 266 | "internalType": "uint256", 267 | "name": "", 268 | "type": "uint256" 269 | } 270 | ], 271 | "name": "connectors", 272 | "outputs": [ 273 | { 274 | "internalType": "address", 275 | "name": "", 276 | "type": "address" 277 | } 278 | ], 279 | "stateMutability": "view", 280 | "type": "function" 281 | }, 282 | { 283 | "inputs": [ 284 | { 285 | "internalType": "uint256", 286 | "name": "version", 287 | "type": "uint256" 288 | }, 289 | { 290 | "internalType": "address", 291 | "name": "query", 292 | "type": "address" 293 | } 294 | ], 295 | "name": "isClone", 296 | "outputs": [ 297 | { 298 | "internalType": "bool", 299 | "name": "result", 300 | "type": "bool" 301 | } 302 | ], 303 | "stateMutability": "view", 304 | "type": "function" 305 | }, 306 | { 307 | "inputs": [], 308 | "name": "list", 309 | "outputs": [ 310 | { 311 | "internalType": "address", 312 | "name": "", 313 | "type": "address" 314 | } 315 | ], 316 | "stateMutability": "view", 317 | "type": "function" 318 | }, 319 | { 320 | "inputs": [], 321 | "name": "master", 322 | "outputs": [ 323 | { 324 | "internalType": "address", 325 | "name": "", 326 | "type": "address" 327 | } 328 | ], 329 | "stateMutability": "view", 330 | "type": "function" 331 | }, 332 | { 333 | "inputs": [ 334 | { 335 | "internalType": "address", 336 | "name": "_master", 337 | "type": "address" 338 | }, 339 | { 340 | "internalType": "address", 341 | "name": "_list", 342 | "type": "address" 343 | }, 344 | { 345 | "internalType": "address", 346 | "name": "_account", 347 | "type": "address" 348 | }, 349 | { 350 | "internalType": "address", 351 | "name": "_connectors", 352 | "type": "address" 353 | } 354 | ], 355 | "name": "setBasics", 356 | "outputs": [], 357 | "stateMutability": "nonpayable", 358 | "type": "function" 359 | }, 360 | { 361 | "inputs": [], 362 | "name": "updateMaster", 363 | "outputs": [], 364 | "stateMutability": "nonpayable", 365 | "type": "function" 366 | }, 367 | { 368 | "inputs": [], 369 | "name": "versionCount", 370 | "outputs": [ 371 | { 372 | "internalType": "uint256", 373 | "name": "", 374 | "type": "uint256" 375 | } 376 | ], 377 | "stateMutability": "view", 378 | "type": "function" 379 | } 380 | ] 381 | -------------------------------------------------------------------------------- /broadcast/DeployLockboxAdapter.s.sol/1/run-latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactions": [ 3 | { 4 | "hash": "0xb76153356a3e1903e43e04697ffb421807abde67aaec9347cde4f3e27dbed133", 5 | "transactionType": "CREATE", 6 | "contractName": "LockboxAdapter", 7 | "contractAddress": "0x45BF3c737e57B059a5855280CA1ADb8e9606AC68", 8 | "function": null, 9 | "arguments": [ 10 | "0x8898B472C54c31894e3B9bb83cEA802a5d0e63C6", 11 | "0xBf29A2D67eFb6766E44c163B19C6F4118b164702" 12 | ], 13 | "transaction": { 14 | "type": "0x02", 15 | "from": "0xade09131c6f43fe22c2cbabb759636c43cfc181e", 16 | "gas": "0x106c6d", 17 | "value": "0x0", 18 | "data": "0x60c03461009557601f610ebe38819003918201601f19168301916001600160401b0383118484101761009a57808492604094855283398101031261009557610052602061004b836100b0565b92016100b0565b9060805260a052604051610df990816100c5823960805181818160e7015281816104ab015261059a015260a051818181610376015281816103d301526108b90152f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036100955756fe608080604052600436101561001d575b50361561001b57600080fd5b005b600090813560e01c908163479d955714610867575080638aac16ba146102c45763fd614f410361000f57346102c15760c03660031901126102c157602490813590610066610b15565b61006e610b2b565b5060843563ffffffff8116036102bc5760a43567ffffffffffffffff918282116102b857366023830112156102b8578160040135946100ac86610b77565b926100ba6040519485610b55565b86845260209687850191368a83830101116102b4578188928b8b93018537860101526001600160a01b03937f00000000000000000000000000000000000000000000000000000000000000008516330361029d57808891518101031261029957519280841680940361029957303b15610299579086916040519363479d955760e01b85528260048601521692838982015284604482015286808260648183305af1918261027e575b5050610275576101ac92610174610c0a565b60405163a9059cbb60e01b81526001600160a01b03909616600487015260248601929092529093928391908290889082906044820190565b03925af1801561026a57916101f5917fc800e8ca4bda8128731e0444109da53d19286048d068614656da8edcfc0f1d489361023d575b5060405191829187835287830190610b93565b0390a15b604051938385019182118583101761022957506040528252610225604051928284938452830190610b93565b0390f35b634e487b7160e01b60009081526041600452fd5b61025c90873d8911610263575b6102548183610b55565b810190610bf2565b50386101e2565b503d61024a565b6040513d86823e3d90fd5b505050506101f9565b61028b9192939450610b41565b610299579086918638610162565b8580fd5b60405163241314cd60e01b81523360048201528990fd5b8780fd5b8380fd5b600080fd5b80fd5b5060e03660031901126102c15760043563ffffffff811681036102bc576102e9610aff565b6102f1610b15565b916102fa610b2b565b9267ffffffffffffffff60c435116108635736602360c4350112156108635767ffffffffffffffff60c43560040135116108635736602460c4356004013560c43501011161086357608435156108515760405163476a536360e11b81526001600160a01b038281166004830152909490602090869060249082907f0000000000000000000000000000000000000000000000000000000000000000165afa948515610846578695610825575b5060405163eec9567960e01b81526001600160a01b038681166004830152909290602090849060249082907f0000000000000000000000000000000000000000000000000000000000000000165afa92831561063f5787936107f4575b506040516323ce9cbb60e11b81526020816004816001600160a01b0388165afa9081156107e95788916107ca575b50156106925750608435341061067257608435340334811161065e579186906001600160a01b0381163b1561060b57604051636db5a92360e11b815290829082906004908290608435906001600160a01b03165af180156106175761064a575b50505b60405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000166004820152608435602482015294602086806044810103818a6001600160a01b0386165af194851561063f5763ffffffff96602096610622575b506040519687956345560b5d60e11b875216600486015260018060a01b0316602485015260018060a01b0316604484015260018060a01b03166064830152608435608483015260a43560a483015260e060c483015260c4356004013560e48301528161010460c43560040135602460c4350182840137600460c43501358281018201889052601f01601f1916820182900301917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af19081156106175782916105dd575b602082604051908152f35b90506020813d60201161060f575b816105f860209383610b55565b8101031261060b576020915051386105d2565b5080fd5b3d91506105eb565b6040513d84823e3d90fd5b61063890873d8911610263576102548183610b55565b5038610505565b6040513d89823e3d90fd5b61065390610b41565b610299578538610491565b634e487b7160e01b87526011600452602487fd5b604051635bc10dc760e01b81523460048201526084356024820152604490fd5b6040516323b872dd60e01b60208201523360248201523060448201526084356064820152606481528060a081011067ffffffffffffffff60a0830111176107b45760a081016040526106ed906001600160a01b038316610c3a565b60405163095ea7b360e01b81526001600160a01b03848116600483015260843560248301529091602091839160449183918c91165af1801561063f57610795575b50349186906001600160a01b0381163b1561060b5760405163b6b55f2560e01b815260843560048201529082908290602490829084906001600160a01b03165af1801561061757610781575b5050610494565b61078a90610b41565b61029957853861077a565b6107ad9060203d602011610263576102548183610b55565b503861072e565b634e487b7160e01b600052604160045260246000fd5b6107e3915060203d602011610263576102548183610b55565b38610431565b6040513d8a823e3d90fd5b61081791935060203d60201161081e575b61080f8183610b55565b810190610bd3565b9138610403565b503d610805565b61083f91955060203d60201161081e5761080f8183610b55565b93386103a6565b6040513d88823e3d90fd5b60405163820bf1e560e01b8152600490fd5b8480fd5b82346102c15760603660031901126102c157600435610884610aff565b61088c610b15565b303303610aea5760405163eec9567960e01b81526001600160a01b039283166004820181905294955085947f00000000000000000000000000000000000000000000000000000000000000008416916020918282602481875afa9182156107e95760249484918a94610acb575b5060405195868092634e0dc55760e01b82528560048301525afa9384156107e9578894610aac575b506040516323ce9cbb60e11b815290828716908483600481855afa928315610aa15785918a918c95610a7e575b5060405163095ea7b360e01b81526001600160a01b0390961660048701526024860191909152929391928290818c81604481015b03925af18015610a7357610a56575b50803b156102b45787906024604051809b8193632e1a7d4d60e01b83528b60048401525af1801561063f57610a42575b8697506000146109f9575050839283928392165af16109de610c0a565b50156109e75780f35b60405163250c731360e11b8152600490fd5b60405163a9059cbb60e01b918101919091526001600160a01b03929092166024830152604480830194909452928152610a3f935091610a39606484610b55565b16610c3a565b80f35b959096610a4e90610b41565b9486906109c1565b610a6c90843d8611610263576102548183610b55565b5089610991565b6040513d8b823e3d90fd5b610982949550610a9a90843d8611610263576102548183610b55565b949361094e565b6040513d8c823e3d90fd5b610ac4919450833d851161081e5761080f8183610b55565b9289610921565b610ae3919450823d841161081e5761080f8183610b55565b928b6108f9565b630173cb6760e51b8552336004860152602485fd5b602435906001600160a01b03821682036102bc57565b604435906001600160a01b03821682036102bc57565b606435906001600160a01b03821682036102bc57565b67ffffffffffffffff81116107b457604052565b90601f8019910116810190811067ffffffffffffffff8211176107b457604052565b67ffffffffffffffff81116107b457601f01601f191660200190565b919082519283825260005b848110610bbf575050826000602080949584010152601f8019910116010190565b602081830181015184830182015201610b9e565b908160209103126102bc57516001600160a01b03811681036102bc5790565b908160209103126102bc575180151581036102bc5790565b3d15610c35573d90610c1b82610b77565b91610c296040519384610b55565b82523d6000602084013e565b606090565b60408051908101916001600160a01b031667ffffffffffffffff8311828410176107b457610caa926040526000806020958685527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656487860152868151910182855af1610ca4610c0a565b91610d26565b80519081610cb757505050565b8280610cc7938301019101610bf2565b15610ccf5750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b91929015610d885750815115610d3a575090565b3b15610d435790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015610d9b5750805190602001fd5b60405162461bcd60e51b815260206004820152908190610dbf906024830190610b93565b0390fdfea26469706673582212202b1b9516411eb8de83068e58191f07b143e93f75a5cb7ff9fb5c87c7b34fcad864736f6c634300081400330000000000000000000000008898b472c54c31894e3b9bb83cea802a5d0e63c6000000000000000000000000bf29a2d67efb6766e44c163b19c6f4118b164702", 19 | "nonce": "0x5f5", 20 | "accessList": [] 21 | }, 22 | "additionalContracts": [], 23 | "isFixedGasLimit": false 24 | } 25 | ], 26 | "receipts": [ 27 | { 28 | "transactionHash": "0xb76153356a3e1903e43e04697ffb421807abde67aaec9347cde4f3e27dbed133", 29 | "transactionIndex": "0x0", 30 | "blockHash": "0xe1edbbee005b71b207501063a60e230a4676c5216b54702b5b3ce7dd92be15f6", 31 | "blockNumber": "0x1094e01", 32 | "from": "0xe74133A5C86dE660d81C47fFc114f7aD19908f83", 33 | "to": "0x83eF3D446Bd1220C8261251f83AC5cc51311d600", 34 | "cumulativeGasUsed": "0x82f1", 35 | "gasUsed": "0x82f1", 36 | "contractAddress": null, 37 | "logs": [], 38 | "status": "0x1", 39 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 40 | "type": "0x2", 41 | "effectiveGasPrice": "0x46c7cfe000" 42 | } 43 | ], 44 | "libraries": [], 45 | "pending": [], 46 | "returns": {}, 47 | "timestamp": 1702615495, 48 | "chain": 1, 49 | "multi": false, 50 | "commit": "c427219" 51 | } --------------------------------------------------------------------------------