diff --git a/.env.example b/.env.example index 69a945bfc..794c272b4 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,6 @@ REPORT_GAS=false PRODUCTION=false POLYGON_RPC_URL= AVALANCHE_RPC_URL= -FANTOM_RPC_URL= BSC_RPC_URL= ARBITRUM_RPC_URL= OPTIMISM_RPC_URL= @@ -15,12 +14,13 @@ BSC_LOCAL_URL=http://127.0.0.1:8546 AVALANCHE_LOCAL_URL=http://127.0.0.1:8547 ARBITRUM_LOCAL_URL=http://127.0.0.1:8549 OPTIMISM_LOCAL_URL=http://127.0.0.1:8550 -FANTOM_LOCAL_URL=http://127.0.0.1:8551 BASE_LOCAL_URL=http://127.0.0.1:8552 GNOSIS_LOCAL_URL=http://127.0.0.1:8553 OWNER_ADDRESS= -MULTI_SIG_ADDRESS= DEPLOYER_KEY= FOUNDRY_EXPORTS_OVERWRITE_LATEST=true RUST_BACKTRACE=full -FOUNDRY_PROFILE=localdev \ No newline at end of file +FOUNDRY_PROFILE=localdev +TENDERLY_PROJECT_SLUG= +TENDERLY_ACCOUNT_ID= +TENDERLY_ACCESS_KEY= \ No newline at end of file diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1fe1536c1..a25e66193 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -18,13 +18,17 @@ env: POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }} AVALANCHE_RPC_URL: ${{ secrets.AVALANCHE_RPC_URL }} FANTOM_RPC_URL: ${{ secrets.FANTOM_RPC_URL }} + TENDERLY_ACCESS_KEY: ${{ secrets.TENDERLY_ACCESS_KEY }} + TENDERLY_PROJECT_SLUG: 'superform-v1-d5' # your project slug + TENDERLY_ACCOUNT_ID: 'superform' # your username or organization name + FOUNDRY_EXPORTS_OVERWRITE_LATEST: 'true' jobs: build: runs-on: SuperformCore2 steps: - name: "Check out the repo" - uses: "actions/checkout@v3" + uses: "actions/checkout@v4" with: submodules: "recursive" @@ -59,7 +63,7 @@ jobs: runs-on: SuperformCore2 steps: - name: "Check out the repo" - uses: "actions/checkout@v3" + uses: "actions/checkout@v4" with: submodules: "recursive" @@ -85,15 +89,14 @@ jobs: - name: "Add test summary" run: | - echo "## Fork tests result" >> $GITHUB_STEP_SUMMARY + echo "## Tests result" >> $GITHUB_STEP_SUMMARY echo "✅ Passed" >> $GITHUB_STEP_SUMMARY coverage: - needs: ["build"] runs-on: SuperformCore2 steps: - name: "Check out the repo" - uses: "actions/checkout@v3" + uses: "actions/checkout@v4" with: submodules: "recursive" @@ -114,3 +117,22 @@ jobs: run: | echo "## Coverage result" >> $GITHUB_STEP_SUMMARY echo "✅ Uploaded to Codecov" >> $GITHUB_STEP_SUMMARY + + deploy-tenderly-devnets: + runs-on: SuperformCore2 + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + with: + submodules: "recursive" + - name: "Install Foundry" + uses: "foundry-rs/foundry-toolchain@v1" + - name: 'Install Tenderly CLI' + run: curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-linux.sh | sudo sh + - name: 'Deploy to Tenderly Devnets' + run: ./utils/run_script_tenderly.sh + shell: bash + - name: "Add devnet deployment summary" + run: | + echo "## Devnet deployment result" >> $GITHUB_STEP_SUMMARY + echo "✅ Deployed to Tenderly Devnet" >> $GITHUB_STEP_SUMMARY diff --git a/README.md b/README.md index 85814ec31..9ad374e34 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,6 @@ $ forge test ## Audits -- [Gerard Pearson](https://twitter.com/gpersoon): [2023-09-superform.pdf](https://github.com/superform-xyz/superform-core/files/13300598/2023-09-superform.pdf) +- [Gerard Persoon](https://twitter.com/gpersoon): [2023-09-superform.pdf](https://github.com/superform-xyz/superform-core/files/13300598/2023-09-superform.pdf) - [Hans Friese](https://twitter.com/hansfriese): [Superform_Core_Review_Final_Hans_20230921.pdf](https://github.com/superform-xyz/superform-core/files/13300591/Superform_Core_Review_Final_Hans_20230921.pdf) diff --git a/foundry.toml b/foundry.toml index 5e053ddcf..049f0609c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -43,7 +43,6 @@ bsc = "${BSC_RPC_URL}" avalanche = "${AVALANCHE_RPC_URL}" arbitrum = "${ARBITRUM_RPC_URL}" optimism = "${OPTIMISM_RPC_URL}" -fantom = "${FANTOM_RPC_URL}" base = "${BASE_RPC_URL}" gnosis = "${GNOSIS_RPC_URL}" ethereum_fork = "${ETHEREUM_LOCAL_URL}" @@ -52,7 +51,6 @@ bsc_fork = "${BSC_LOCAL_URL}" avalanche_fork = "${AVALANCHE_LOCAL_URL}" arbitrum_fork = "${ARBITRUM_LOCAL_URL}" optimism_fork = "${OPTIMISM_LOCAL_URL}" -fantom_fork = "${FANTOM_LOCAL_URL}" base_fork = "${BASE_LOCAL_URL}" gnosis_fork = "${GNOSIS_LOCAL_URL}" diff --git a/lib/ERC1155A b/lib/ERC1155A index e7d53f306..f46fa5420 160000 --- a/lib/ERC1155A +++ b/lib/ERC1155A @@ -1 +1 @@ -Subproject commit e7d53f306989ba205c779973d1b5e86755a1b9c0 +Subproject commit f46fa542026b860717d48bd1c09acbb8b68a0b57 diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..39a2b6e9a --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} diff --git a/script/Abstract.Deploy.Single.s.sol b/script/Abstract.Deploy.Single.s.sol index 132ba4580..a5c12c2b8 100644 --- a/script/Abstract.Deploy.Single.s.sol +++ b/script/Abstract.Deploy.Single.s.sol @@ -88,22 +88,22 @@ abstract contract AbstractDeploySingle is Script { address public constant CANONICAL_PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; mapping(uint64 chainId => mapping(bytes32 implementation => address at)) public contracts; - string[23] public contractNames = [ + string[17] public contractNames = [ "CoreStateRegistry", - "TimelockStateRegistry", - "BroadcastRegistry", + //"TimelockStateRegistry", + //"BroadcastRegistry", "LayerzeroImplementation", "HyperlaneImplementation", "WormholeARImplementation", - "WormholeSRImplementation", + //"WormholeSRImplementation", "LiFiValidator", "SocketValidator", - "SocketOneInchValidator", + //"SocketOneInchValidator", "DstSwapper", "SuperformFactory", "ERC4626Form", - "ERC4626TimelockForm", - "ERC4626KYCDaoForm", + //"ERC4626TimelockForm", + //"ERC4626KYCDaoForm", "SuperformRouter", "SuperPositions", "SuperRegistry", @@ -114,8 +114,6 @@ abstract contract AbstractDeploySingle is Script { "EmergencyQueue" ]; - bytes32 constant salt = "CANTINA_DEPLOYMENT_4"; - enum Chains { Ethereum, Polygon, @@ -123,7 +121,6 @@ abstract contract AbstractDeploySingle is Script { Avalanche, Arbitrum, Optimism, - Fantom, Base, Gnosis, Ethereum_Fork, @@ -132,7 +129,6 @@ abstract contract AbstractDeploySingle is Script { Avalanche_Fork, Arbitrum_Fork, Optimism_Fork, - Fantom_Fork, Base_Fork, Gnosis_Fork } @@ -142,10 +138,6 @@ abstract contract AbstractDeploySingle is Script { Prod } - uint256 public deployerPrivateKey; - address public ownerAddress; - address public multiSigAddress; - /// @dev Mapping of chain enum to rpc url mapping(Chains chains => string rpcUrls) public forks; @@ -157,8 +149,9 @@ abstract contract AbstractDeploySingle is Script { uint32[] public FORM_IMPLEMENTATION_IDS = [uint32(1), uint32(2), uint32(3)]; string[] public VAULT_KINDS = ["Vault", "TimelockedVault", "KYCDaoVault"]; - /// @dev liquidity bridge ids 1 is lifi, 2 is socket, 3 is socket one inch implementation - uint8[] public bridgeIds = [1, 2, 3]; + /// @dev liquidity bridge ids 1 is lifi, 2 is socket, 3 is socket one inch implementation (not added to this + /// release) + uint8[] public bridgeIds = [1, 2]; mapping(uint64 chainId => address[] bridgeAddresses) public BRIDGE_ADDRESSES; @@ -166,9 +159,9 @@ abstract contract AbstractDeploySingle is Script { /// @notice id 1 is layerzero /// @notice id 2 is hyperlane /// @notice id 3 is wormhole AR - /// @notice 4 is wormhole SR - uint8[] public ambIds = [uint8(1), 2, 3, 4]; - bool[] public broadcastAMB = [false, false, false, true]; + /// @notice FIXME 4 is wormhole SR - not added + uint8[] public ambIds = [uint8(1), 2, 3]; + bool[] public broadcastAMB = [false, false, false]; /*////////////////////////////////////////////////////////////// AMB VARIABLES @@ -182,7 +175,6 @@ abstract contract AbstractDeploySingle is Script { address public constant POLY_lzEndpoint = 0x3c2269811836af69497E5F486A85D7316753cf62; address public constant ARBI_lzEndpoint = 0x3c2269811836af69497E5F486A85D7316753cf62; address public constant OP_lzEndpoint = 0x3c2269811836af69497E5F486A85D7316753cf62; - address public constant FTM_lzEndpoint = 0xb6319cC6c8c27A8F5dAF0dD3DF91EA35C4720dd7; address public constant BASE_lzEndpoint = 0xb6319cC6c8c27A8F5dAF0dD3DF91EA35C4720dd7; address public constant GNOSIS_lzEndpoint = 0x9740FF91F1985D8d2B71494aE1A2f723bb3Ed9E4; @@ -196,11 +188,9 @@ abstract contract AbstractDeploySingle is Script { 0x3c2269811836af69497E5F486A85D7316753cf62, 0x3c2269811836af69497E5F486A85D7316753cf62, 0xb6319cC6c8c27A8F5dAF0dD3DF91EA35C4720dd7, - 0xb6319cC6c8c27A8F5dAF0dD3DF91EA35C4720dd7, 0x9740FF91F1985D8d2B71494aE1A2f723bb3Ed9E4 ]; - /// @dev NOTE: hyperlane does not support FTM address[] public hyperlaneMailboxes = [ 0xc005dc82818d67AF737725bD4bf75435d065D239, 0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4, @@ -208,7 +198,6 @@ abstract contract AbstractDeploySingle is Script { 0x5d934f4e2f797775e53561bB72aca21ba36B96BB, 0x979Ca5202784112f4738403dBec5D0F3B9daabB9, 0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D, - address(0), 0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D, 0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f ]; @@ -220,7 +209,6 @@ abstract contract AbstractDeploySingle is Script { 0x0071740Bf129b05C4684abfbBeD248D80971cce2, 0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22, 0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C, - address(0), 0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94, 0xDd260B99d302f0A3fF885728c086f729c06f227f ]; @@ -232,7 +220,6 @@ abstract contract AbstractDeploySingle is Script { 0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7, 0xa5f208e072434bC67592E4C49C1B991BA79BCA46, 0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722, - 0x126783A6Cb203a3E35344528B26ca3a0489a1485, 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6, 0xa321448d90d4e5b0A732867c18eA198e75CAC48E ]; @@ -248,21 +235,31 @@ abstract contract AbstractDeploySingle is Script { uint64 public constant POLY = 137; uint64 public constant ARBI = 42_161; uint64 public constant OP = 10; - uint64 public constant FTM = 250; uint64 public constant BASE = 8453; uint64 public constant GNOSIS = 100; - uint64[] public chainIds = [1, 56, 43_114, 137, 42_161, 10, 250, 8453, 100]; + uint64[] public chainIds = [1, 56, 43_114, 137, 42_161, 10, 8453, 100]; string[] public chainNames = ["Ethereum", "Binance", "Avalanche", "Polygon", "Arbitrum", "Optimism", "Fantom", "Base", "Gnosis"]; /// @dev vendor chain ids - uint16[] public lz_chainIds = [101, 102, 106, 109, 110, 111, 112, 184, 145]; - uint32[] public hyperlane_chainIds = [1, 56, 43_114, 137, 42_161, 10, 250, 8453, 100]; - uint16[] public wormhole_chainIds = [2, 4, 6, 5, 23, 24, 10, 30, 25]; + uint16[] public lz_chainIds = [101, 102, 106, 109, 110, 111, 184, 145]; + uint32[] public hyperlane_chainIds = [1, 56, 43_114, 137, 42_161, 10, 8453, 100]; + uint16[] public wormhole_chainIds = [2, 4, 6, 5, 23, 24, 30, 25]; uint256 public constant milionTokensE18 = 1 ether; + uint256[] public gasPrices = [ + 35 * 10e9, // ETH + 5 * 10e9, // BSC + 45 * 10e9, // AVAX + 200 * 10e9, // POLY + 1 * 10e8, // ARBI + 1 * 10e8, // OP + 1 * 10e8, // BASE + 4 * 10e9 // GNOSIS + ]; + /*////////////////////////////////////////////////////////////// CHAINLINK VARIABLES //////////////////////////////////////////////////////////////*/ @@ -280,21 +277,46 @@ abstract contract AbstractDeploySingle is Script { 0x205E10d3c4C87E26eB66B1B270b71b7708494dB9, address(0), address(0), - address(0), address(0) ]; + /*////////////////////////////////////////////////////////////// + RBAC VARIABLES + //////////////////////////////////////////////////////////////*/ + + uint256 public deployerPrivateKey; + + address public ownerAddress; + + address constant EMERGENCY_ADMIN = 0x73009CE7cFFc6C4c5363734d1b429f0b848e0490; + + address[] public PROTOCOL_ADMINS = [ + 0xd26b38a64C812403fD3F87717624C80852cD6D61, + /// @dev ETH https://app.onchainden.com/safes/eth:0xd26b38a64c812403fd3f87717624c80852cd6d61 + 0xf70A19b67ACC4169cA6136728016E04931D550ae, + /// @dev BSC https://app.onchainden.com/safes/bnb:0xf70a19b67acc4169ca6136728016e04931d550ae + 0x79DD9868A1a89720981bF077A02a4A43c57080d2, + /// @dev AVAX https://app.onchainden.com/safes/avax:0x79dd9868a1a89720981bf077a02a4a43c57080d2 + 0x5022b05721025159c82E02abCb0Daa87e357f437, + /// @dev POLY https://app.onchainden.com/safes/matic:0x5022b05721025159c82e02abcb0daa87e357f437 + 0x7Fc07cAFb65d1552849BcF151F7035C5210B76f4, + /// @dev ARBI https://app.onchainden.com/safes/arb1:0x7fc07cafb65d1552849bcf151f7035c5210b76f4 + 0x99620a926D68746D5F085B3f7cD62F4fFB71f0C1, + /// @dev OP https://app.onchainden.com/safes/oeth:0x99620a926d68746d5f085b3f7cd62f4ffb71f0c1 + 0x2F973806f8863E860A553d4F2E7c2AB4A9F3b87C, + /// @dev BASE https://app.onchainden.com/safes/base:0x2f973806f8863e860a553d4f2e7c2ab4a9f3b87c + address(0) + /// @dev GNOSIS FIXME - PROTOCOL ADMIN NOT SET FOR GNOSIS + ]; + /// @dev environment variable setup for upgrade /// @param cycle deployment cycle (dev, prod) modifier setEnvDeploy(Cycle cycle) { if (cycle == Cycle.Dev) { - deployerPrivateKey = vm.envUint("LOCAL_PRIVATE_KEY"); - ownerAddress = vm.envAddress("LOCAL_OWNER_ADDRESS"); - multiSigAddress = vm.envAddress("MULTI_SIG_ADDRESS"); + (ownerAddress, deployerPrivateKey) = makeAddrAndKey("tenderly"); } else { deployerPrivateKey = vm.envUint("DEPLOYER_KEY"); ownerAddress = vm.envAddress("OWNER_ADDRESS"); - multiSigAddress = vm.envAddress("MULTI_SIG_ADDRESS"); } _; @@ -308,7 +330,6 @@ abstract contract AbstractDeploySingle is Script { forks[Chains.Avalanche] = "avalanche"; forks[Chains.Arbitrum] = "arbitrum"; forks[Chains.Optimism] = "optimism"; - forks[Chains.Fantom] = "fantom"; forks[Chains.Base] = "base"; forks[Chains.Gnosis] = "gnosis"; @@ -319,7 +340,6 @@ abstract contract AbstractDeploySingle is Script { forks[Chains.Avalanche_Fork] = "avalanche_fork"; forks[Chains.Arbitrum_Fork] = "arbitrum_fork"; forks[Chains.Optimism_Fork] = "optimism_fork"; - forks[Chains.Fantom_Fork] = "fantom_fork"; forks[Chains.Base_Fork] = "base_fork"; forks[Chains.Gnosis_Fork] = "gnosis_fork"; } @@ -332,7 +352,8 @@ abstract contract AbstractDeploySingle is Script { uint256 i, uint256 trueIndex, Cycle cycle, - uint64[] memory targetDeploymentChains + uint64[] memory targetDeploymentChains, + bytes32 salt ) internal setEnvDeploy(cycle) @@ -355,13 +376,16 @@ abstract contract AbstractDeploySingle is Script { emergencyAdmin: ownerAddress, paymentAdmin: 0xD911673eAF0D3e15fe662D58De15511c5509bAbB, csrProcessor: 0x23c658FE050B4eAeB9401768bF5911D11621629c, - tlProcessor: ownerAddress, - brProcessor: ownerAddress, + tlProcessor: EMERGENCY_ADMIN, + /// @dev Temporary, as we are not using this processor in this release + brProcessor: EMERGENCY_ADMIN, + /// @dev Temporary, as we are not using this processor in this release csrUpdater: 0xaEbb4b9f7e16BEE2a0963569a5E33eE10E478a5f, - srcVaaRelayer: ownerAddress, + srcVaaRelayer: EMERGENCY_ADMIN, + /// @dev Temporary, as we are not using this processor in this release dstSwapper: 0x1666660D2F506e754CB5c8E21BDedC7DdEc6Be1C, csrRescuer: 0x90ed07A867bDb6a73565D7abBc7434Dd810Fafc5, - csrDisputer: ownerAddress + csrDisputer: 0x7c9c8C0A9aA5D8a2c2e6C746641117Cc9591296a }) ) ); @@ -377,7 +401,7 @@ abstract contract AbstractDeploySingle is Script { vars.superRegistryC.setPermit2(CANONICAL_PERMIT2); /// @dev sets max number of vaults per destination - vars.superRegistryC.setVaultLimitPerTx(vars.chainId, 5); + vars.superRegistryC.setVaultLimitPerDestination(vars.chainId, 5); /// @dev 3.1 - deploy Core State Registry vars.coreStateRegistry = address(new CoreStateRegistry{ salt: salt }(vars.superRegistryC)); @@ -385,29 +409,34 @@ abstract contract AbstractDeploySingle is Script { vars.superRegistryC.setAddress(vars.superRegistryC.CORE_STATE_REGISTRY(), vars.coreStateRegistry, vars.chainId); - /// @dev 3.2 - deploy Form State Registry + /* + /// @dev 3.2 - deploy Timelock State Registry vars.timelockStateRegistry = address(new TimelockStateRegistry{ salt: salt }(vars.superRegistryC)); contracts[vars.chainId][bytes32(bytes("TimelockStateRegistry"))] = vars.timelockStateRegistry; + vars.superRegistryC.setAddress( vars.superRegistryC.TIMELOCK_STATE_REGISTRY(), vars.timelockStateRegistry, vars.chainId ); + */ + /* /// @dev 3.3 - deploy Broadcast State Registry vars.broadcastRegistry = address(new BroadcastRegistry{ salt: salt }(vars.superRegistryC)); contracts[vars.chainId][bytes32(bytes("BroadcastRegistry"))] = vars.broadcastRegistry; vars.superRegistryC.setAddress(vars.superRegistryC.BROADCAST_REGISTRY(), vars.broadcastRegistry, vars.chainId); + */ - address[] memory registryAddresses = new address[](3); + address[] memory registryAddresses = new address[](1); registryAddresses[0] = vars.coreStateRegistry; - registryAddresses[1] = vars.timelockStateRegistry; - registryAddresses[2] = vars.broadcastRegistry; + //registryAddresses[1] = vars.timelockStateRegistry; + //registryAddresses[2] = vars.broadcastRegistry; - uint8[] memory registryIds = new uint8[](3); + uint8[] memory registryIds = new uint8[](1); registryIds[0] = 1; - registryIds[1] = 2; - registryIds[2] = 3; + //registryIds[1] = 2; + //registryIds[2] = 3; vars.superRegistryC.setStateRegistryAddress(registryIds, registryAddresses); @@ -435,7 +464,9 @@ abstract contract AbstractDeploySingle is Script { contracts[vars.chainId][bytes32(bytes("WormholeARImplementation"))] = vars.wormholeImplementation; WormholeARImplementation(vars.wormholeImplementation).setWormholeRelayer(wormholeRelayer); + WormholeARImplementation(vars.wormholeImplementation).setRefundChainId(wormhole_chainIds[i]); + /* /// @dev 6.5- deploy Wormhole Specialized Relayer Implementation vars.wormholeSRImplementation = address(new WormholeSRImplementation{ salt: salt }(vars.superRegistryC)); contracts[vars.chainId][bytes32(bytes("WormholeSRImplementation"))] = vars.wormholeSRImplementation; @@ -443,11 +474,12 @@ abstract contract AbstractDeploySingle is Script { WormholeSRImplementation(vars.wormholeSRImplementation).setWormholeCore(wormholeCore[trueIndex]); /// @dev FIXME who is the wormhole relayer on mainnet? WormholeSRImplementation(vars.wormholeSRImplementation).setRelayer(ownerAddress); + */ vars.ambAddresses[0] = vars.lzImplementation; vars.ambAddresses[1] = vars.hyperlaneImplementation; vars.ambAddresses[2] = vars.wormholeImplementation; - vars.ambAddresses[3] = vars.wormholeSRImplementation; + //vars.ambAddresses[3] = vars.wormholeSRImplementation; /// @dev 6- deploy liquidity validators vars.lifiValidator = address(new LiFiValidator{ salt: salt }(vars.superRegistry)); @@ -456,12 +488,14 @@ abstract contract AbstractDeploySingle is Script { vars.socketValidator = address(new SocketValidator{ salt: salt }(vars.superRegistry)); contracts[vars.chainId][bytes32(bytes("SocketValidator"))] = vars.socketValidator; + /* vars.socketOneInchValidator = address(new SocketOneInchValidator{ salt: salt }(vars.superRegistry)); contracts[vars.chainId][bytes32(bytes("SocketOneInchValidator"))] = vars.socketOneInchValidator; + */ bridgeValidators[0] = vars.lifiValidator; bridgeValidators[1] = vars.socketValidator; - bridgeValidators[2] = vars.socketOneInchValidator; + //bridgeValidators[2] = vars.socketOneInchValidator; /// @dev 7 - Deploy SuperformFactory vars.factory = address(new SuperformFactory{ salt: salt }(vars.superRegistry)); @@ -478,19 +512,21 @@ abstract contract AbstractDeploySingle is Script { contracts[vars.chainId][bytes32(bytes("ERC4626Form"))] = vars.erc4626Form; // Timelock + ERC4626 Form - vars.erc4626TimelockForm = address(new ERC4626TimelockForm{ salt: salt }(vars.superRegistry)); - contracts[vars.chainId][bytes32(bytes("ERC4626TimelockForm"))] = vars.erc4626TimelockForm; + //vars.erc4626TimelockForm = address(new ERC4626TimelockForm{ salt: salt }(vars.superRegistry)); + //contracts[vars.chainId][bytes32(bytes("ERC4626TimelockForm"))] = vars.erc4626TimelockForm; /// 9 KYCDao ERC4626 Form - vars.kycDao4626Form = address(new ERC4626KYCDaoForm{ salt: salt }(vars.superRegistry)); - contracts[vars.chainId][bytes32(bytes("ERC4626KYCDaoForm"))] = vars.kycDao4626Form; + //vars.kycDao4626Form = address(new ERC4626KYCDaoForm{ salt: salt }(vars.superRegistry)); + //contracts[vars.chainId][bytes32(bytes("ERC4626KYCDaoForm"))] = vars.kycDao4626Form; - /// @dev 9 - Add newly deployed form implementations to Factory, formBeaconId 1 - ISuperformFactory(vars.factory).addFormImplementation(vars.erc4626Form, FORM_IMPLEMENTATION_IDS[0]); + /// @dev 9 - Add newly deployed form implementations to Factory, formImplementationId 1 + ISuperformFactory(vars.factory).addFormImplementation(vars.erc4626Form, FORM_IMPLEMENTATION_IDS[0], 1); - ISuperformFactory(vars.factory).addFormImplementation(vars.erc4626TimelockForm, FORM_IMPLEMENTATION_IDS[1]); + /// passing 2 cuz timelock state registry id is 2 + //ISuperformFactory(vars.factory).addFormImplementation(vars.erc4626TimelockForm, FORM_IMPLEMENTATION_IDS[1], + // 2); - ISuperformFactory(vars.factory).addFormImplementation(vars.kycDao4626Form, FORM_IMPLEMENTATION_IDS[2]); + //ISuperformFactory(vars.factory).addFormImplementation(vars.kycDao4626Form, FORM_IMPLEMENTATION_IDS[2], 1); /// @dev 10 - Deploy SuperformRouter vars.superformRouter = address(new SuperformRouter{ salt: salt }(vars.superRegistry)); @@ -499,8 +535,12 @@ abstract contract AbstractDeploySingle is Script { vars.superRegistryC.setAddress(vars.superRegistryC.SUPERFORM_ROUTER(), vars.superformRouter, vars.chainId); /// @dev 11 - Deploy SuperPositions - vars.superPositions = - address(new SuperPositions{ salt: salt }("https://apiv2-dev.superform.xyz/", vars.superRegistry)); + vars.superPositions = address( + new SuperPositions{ salt: salt }( + "https://ipfs-gateway.superform.xyz/ipns/k51qzi5uqu5dg90fqdo9j63m556wlddeux4mlgyythp30zousgh3huhyzouyq8/JSON/", + vars.superRegistry + ) + ); contracts[vars.chainId][bytes32(bytes("SuperPositions"))] = vars.superPositions; vars.superRegistryC.setAddress(vars.superRegistryC.SUPER_POSITIONS(), vars.superPositions, vars.chainId); @@ -555,18 +595,27 @@ abstract contract AbstractDeploySingle is Script { vars.superRegistryC.setAddress( vars.superRegistryC.CORE_REGISTRY_PROCESSOR(), 0x23c658FE050B4eAeB9401768bF5911D11621629c, vars.chainId ); - vars.superRegistryC.setAddress(vars.superRegistryC.BROADCAST_REGISTRY_PROCESSOR(), ownerAddress, vars.chainId); - vars.superRegistryC.setAddress(vars.superRegistryC.TIMELOCK_REGISTRY_PROCESSOR(), ownerAddress, vars.chainId); + /// @dev FIXME setting this temporarily to EMERGENCY_ADMIN as we are not using it in this release + vars.superRegistryC.setAddress( + vars.superRegistryC.BROADCAST_REGISTRY_PROCESSOR(), EMERGENCY_ADMIN, vars.chainId + ); + /// @dev FIXME setting this temporarily to EMERGENCY_ADMIN as we are not using it in this release + vars.superRegistryC.setAddress(vars.superRegistryC.TIMELOCK_REGISTRY_PROCESSOR(), EMERGENCY_ADMIN, vars.chainId); vars.superRegistryC.setAddress( vars.superRegistryC.CORE_REGISTRY_UPDATER(), 0xaEbb4b9f7e16BEE2a0963569a5E33eE10E478a5f, vars.chainId ); vars.superRegistryC.setAddress( vars.superRegistryC.CORE_REGISTRY_RESCUER(), 0x90ed07A867bDb6a73565D7abBc7434Dd810Fafc5, vars.chainId ); - vars.superRegistryC.setAddress(vars.superRegistryC.CORE_REGISTRY_DISPUTER(), ownerAddress, vars.chainId); + vars.superRegistryC.setAddress( + vars.superRegistryC.CORE_REGISTRY_DISPUTER(), 0x7c9c8C0A9aA5D8a2c2e6C746641117Cc9591296a, vars.chainId + ); vars.superRegistryC.setAddress( vars.superRegistryC.DST_SWAPPER_PROCESSOR(), 0x1666660D2F506e754CB5c8E21BDedC7DdEc6Be1C, vars.chainId ); + vars.superRegistryC.setAddress( + vars.superRegistryC.SUPERFORM_RECEIVER(), 0x1a6805487322565202848f239C1B5bC32303C2FE, vars.chainId + ); vars.superRegistryC.setDelay(86_400); /// @dev 17 deploy emergency queue @@ -578,10 +627,13 @@ abstract contract AbstractDeploySingle is Script { PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain( vars.chainId, 1, abi.encode(PRICE_FEEDS[vars.chainId][vars.chainId]) ); - PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 8, abi.encode(50 * 10 ** 9 wei)); + /// 500000000000 + PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 8, abi.encode(gasPrices[trueIndex])); PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 9, abi.encode(750)); PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 10, abi.encode(40_000)); PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 11, abi.encode(50_000)); + /// @dev FIXME emergencyCost value + PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 12, abi.encode(10_000)); vm.stopBroadcast(); @@ -608,12 +660,14 @@ abstract contract AbstractDeploySingle is Script { // j = 0 // vars.chainId = targetDeploymentChains[i]; + vm.startBroadcast(deployerPrivateKey); vars.lzImplementation = _readContract(chainNames[trueIndex], vars.chainId, "LayerzeroImplementation"); vars.hyperlaneImplementation = _readContract(chainNames[trueIndex], vars.chainId, "HyperlaneImplementation"); vars.wormholeImplementation = _readContract(chainNames[trueIndex], vars.chainId, "WormholeARImplementation"); - vars.wormholeSRImplementation = _readContract(chainNames[trueIndex], vars.chainId, "WormholeSRImplementation"); + //vars.wormholeSRImplementation = _readContract(chainNames[trueIndex], vars.chainId, + // "WormholeSRImplementation"); vars.superRegistry = _readContract(chainNames[trueIndex], vars.chainId, "SuperRegistry"); vars.paymentHelper = _readContract(chainNames[trueIndex], vars.chainId, "PaymentHelper"); vars.superRegistryC = SuperRegistry(vars.superRegistry); @@ -662,11 +716,13 @@ abstract contract AbstractDeploySingle is Script { vars.chainId = s_superFormChainIds[i]; vm.startBroadcast(deployerPrivateKey); + SuperRBAC srbac = SuperRBAC(payable(_readContract(chainNames[trueIndex], vars.chainId, "SuperRBAC"))); bytes32 protocolAdminRole = srbac.PROTOCOL_ADMIN_ROLE(); bytes32 emergencyAdminRole = srbac.EMERGENCY_ADMIN_ROLE(); - srbac.grantRole(protocolAdminRole, multiSigAddress); - srbac.grantRole(emergencyAdminRole, multiSigAddress); + + srbac.grantRole(protocolAdminRole, PROTOCOL_ADMINS[trueIndex]); + srbac.grantRole(emergencyAdminRole, EMERGENCY_ADMIN); srbac.revokeRole(emergencyAdminRole, ownerAddress); srbac.revokeRole(protocolAdminRole, ownerAddress); @@ -690,12 +746,14 @@ abstract contract AbstractDeploySingle is Script { SetupVars memory vars; vars.chainId = previousDeploymentChains[i]; + vm.startBroadcast(deployerPrivateKey); vars.lzImplementation = _readContract(chainNames[trueIndex], vars.chainId, "LayerzeroImplementation"); vars.hyperlaneImplementation = _readContract(chainNames[trueIndex], vars.chainId, "HyperlaneImplementation"); vars.wormholeImplementation = _readContract(chainNames[trueIndex], vars.chainId, "WormholeARImplementation"); - vars.wormholeSRImplementation = _readContract(chainNames[trueIndex], vars.chainId, "WormholeSRImplementation"); + //vars.wormholeSRImplementation = _readContract(chainNames[trueIndex], vars.chainId, + // "WormholeSRImplementation"); vars.superRegistry = _readContract(chainNames[trueIndex], vars.chainId, "SuperRegistry"); vars.paymentHelper = _readContract(chainNames[trueIndex], vars.chainId, "PaymentHelper"); vars.superRegistryC = SuperRegistry(payable(vars.superRegistry)); @@ -763,8 +821,10 @@ abstract contract AbstractDeploySingle is Script { _readContract(chainNames[vars.dstTrueIndex], vars.dstChainId, "HyperlaneImplementation"); vars.dstWormholeARImplementation = _readContract(chainNames[vars.dstTrueIndex], vars.dstChainId, "WormholeARImplementation"); + /* vars.dstWormholeSRImplementation = _readContract(chainNames[vars.dstTrueIndex], vars.dstChainId, "WormholeSRImplementation"); + */ LayerzeroImplementation(payable(vars.lzImplementation)).setTrustedRemote( vars.dstLzChainId, abi.encodePacked(vars.dstLzImplementation, vars.lzImplementation) ); @@ -795,6 +855,7 @@ abstract contract AbstractDeploySingle is Script { vars.dstChainId, vars.dstWormholeChainId ); + /* WormholeSRImplementation(payable(vars.wormholeSRImplementation)).setChainId( vars.dstChainId, vars.dstWormholeChainId ); @@ -803,10 +864,12 @@ abstract contract AbstractDeploySingle is Script { vars.dstWormholeChainId, vars.dstWormholeSRImplementation ); - /// @dev FIXME missing attribution of WORMHOLE_VAA_RELAYER_ROLE + /// @dev FIXME missing attribution of WORMHOLE_VAA_RELAYER_ROLE + */ SuperRegistry(payable(vars.superRegistry)).setRequiredMessagingQuorum(vars.dstChainId, 1); - vars.superRegistryC.setVaultLimitPerTx(vars.dstChainId, 5); + + vars.superRegistryC.setVaultLimitPerDestination(vars.dstChainId, 5); /// @dev these values are mocks and has to be replaced /// swap gas cost: 50000 @@ -825,14 +888,15 @@ abstract contract AbstractDeploySingle is Script { 80_000, 12e8, /// 12 usd - 28 gwei, + gasPrices[vars.dstTrueIndex], 750, 10_000, + 10_000, 10_000 ) ); - PaymentHelper(payable(vars.paymentHelper)).updateRegisterAERC20Params(0, generateBroadcastParams(5, 1)); + PaymentHelper(payable(vars.paymentHelper)).updateRegisterAERC20Params(abi.encode(4, abi.encode(0, ""))); vars.superRegistryC.setAddress( vars.superRegistryC.SUPERFORM_ROUTER(), @@ -870,17 +934,21 @@ abstract contract AbstractDeploySingle is Script { vars.dstChainId ); + /* vars.superRegistryC.setAddress( vars.superRegistryC.TIMELOCK_STATE_REGISTRY(), _readContract(chainNames[vars.dstTrueIndex], vars.dstChainId, "TimelockStateRegistry"), vars.dstChainId ); + */ + /* vars.superRegistryC.setAddress( vars.superRegistryC.BROADCAST_REGISTRY(), _readContract(chainNames[vars.dstTrueIndex], vars.dstChainId, "BroadcastRegistry"), vars.dstChainId ); + */ vars.superRegistryC.setAddress( vars.superRegistryC.SUPER_POSITIONS(), @@ -916,19 +984,30 @@ abstract contract AbstractDeploySingle is Script { vars.superRegistryC.setAddress( vars.superRegistryC.CORE_REGISTRY_UPDATER(), 0xaEbb4b9f7e16BEE2a0963569a5E33eE10E478a5f, vars.dstChainId ); + /// @dev FIXME setting this temporarily to EMERGENCY_ADMIN as we are not using it in this release + + vars.superRegistryC.setAddress( + vars.superRegistryC.BROADCAST_REGISTRY_PROCESSOR(), EMERGENCY_ADMIN, vars.dstChainId + ); + /// @dev FIXME setting this temporarily to EMERGENCY_ADMIN as we are not using it in this release + vars.superRegistryC.setAddress( - vars.superRegistryC.BROADCAST_REGISTRY_PROCESSOR(), ownerAddress, vars.dstChainId + vars.superRegistryC.TIMELOCK_REGISTRY_PROCESSOR(), EMERGENCY_ADMIN, vars.dstChainId ); - vars.superRegistryC.setAddress(vars.superRegistryC.TIMELOCK_REGISTRY_PROCESSOR(), ownerAddress, vars.dstChainId); vars.superRegistryC.setAddress( vars.superRegistryC.CORE_REGISTRY_RESCUER(), 0x90ed07A867bDb6a73565D7abBc7434Dd810Fafc5, vars.dstChainId ); - vars.superRegistryC.setAddress(vars.superRegistryC.CORE_REGISTRY_DISPUTER(), ownerAddress, vars.dstChainId); + vars.superRegistryC.setAddress( + vars.superRegistryC.CORE_REGISTRY_DISPUTER(), 0x7c9c8C0A9aA5D8a2c2e6C746641117Cc9591296a, vars.dstChainId + ); vars.superRegistryC.setAddress( vars.superRegistryC.DST_SWAPPER_PROCESSOR(), 0x1666660D2F506e754CB5c8E21BDedC7DdEc6Be1C, vars.dstChainId ); + vars.superRegistryC.setAddress( + vars.superRegistryC.SUPERFORM_RECEIVER(), 0x1a6805487322565202848f239C1B5bC32303C2FE, vars.dstChainId + ); } function _preDeploymentSetup() internal { @@ -939,51 +1018,33 @@ abstract contract AbstractDeploySingle is Script { lzEndpointsStorage[POLY] = POLY_lzEndpoint; lzEndpointsStorage[ARBI] = ARBI_lzEndpoint; lzEndpointsStorage[OP] = OP_lzEndpoint; - lzEndpointsStorage[FTM] = FTM_lzEndpoint; lzEndpointsStorage[BASE] = BASE_lzEndpoint; lzEndpointsStorage[GNOSIS] = GNOSIS_lzEndpoint; mapping(uint64 chainId => address[] bridgeAddresses) storage bridgeAddresses = BRIDGE_ADDRESSES; - bridgeAddresses[ETH] = [ - 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, - 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0, - 0x2ddf16BA6d0180e5357d5e170eF1917a01b41fc0 - ]; - bridgeAddresses[BSC] = [ - 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, - 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0, - 0xd286595d2e3D879596FAB51f83A702D10a6db27b + bridgeAddresses[ETH] = [0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0 + //0x2ddf16BA6d0180e5357d5e170eF1917a01b41fc0 ]; - bridgeAddresses[AVAX] = [ - 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, - 0x2b42AFFD4b7C14d9B7C2579229495c052672Ccd3, - 0xbDf50eAe568ECef74796ed6022a0d453e8432410 + bridgeAddresses[BSC] = [0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0 + //0xd286595d2e3D879596FAB51f83A702D10a6db27b ]; - bridgeAddresses[POLY] = [ - 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, - 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0, - 0x2ddf16BA6d0180e5357d5e170eF1917a01b41fc0 + bridgeAddresses[AVAX] = [0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, 0x2b42AFFD4b7C14d9B7C2579229495c052672Ccd3 + //0xbDf50eAe568ECef74796ed6022a0d453e8432410 ]; - bridgeAddresses[ARBI] = [ - 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, - 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0, - 0xaa3d9fA3aB930aE635b001d00C612aa5b14d750e + bridgeAddresses[POLY] = [0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0 + //0x2ddf16BA6d0180e5357d5e170eF1917a01b41fc0 ]; - bridgeAddresses[OP] = [ - 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, - 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0, - 0xbDf50eAe568ECef74796ed6022a0d453e8432410 + bridgeAddresses[ARBI] = [0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0 + //0xaa3d9fA3aB930aE635b001d00C612aa5b14d750e ]; - bridgeAddresses[FTM] = [ - 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, - 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0, - 0x957301825Dc21d4A92919C9E72dC9E6C6a29e7f8 + bridgeAddresses[OP] = [0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0 + //0xbDf50eAe568ECef74796ed6022a0d453e8432410 ]; - bridgeAddresses[BASE] = [0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, address(0), address(0)]; + bridgeAddresses[BASE] = [0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, address(0)]; bridgeAddresses[GNOSIS] = [ 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE, - 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0, - 0x565810cbfa3Cf1390963E5aFa2fB953795686339 + 0xc30141B657f4216252dc59Af2e7CdB9D8792e1B0 + //0x565810cbfa3Cf1390963E5aFa2fB953795686339 ]; /// price feeds on all chains diff --git a/script/Mainnet.Deploy.NewChain.s.sol b/script/Mainnet.Deploy.NewChain.s.sol index bc7cc0c2f..e5d59ca66 100644 --- a/script/Mainnet.Deploy.NewChain.s.sol +++ b/script/Mainnet.Deploy.NewChain.s.sol @@ -19,10 +19,14 @@ contract MainnetDeployNewChain is AbstractDeploySingle { uint64[] TARGET_DEPLOYMENT_CHAINS = [ETH, AVAX, GNOSIS]; uint64[] FINAL_DEPLOYED_CHAINS = [ETH, AVAX, GNOSIS]; */ + //!WARNING ENUSRE output folder has correct addresses of the deployment! - uint64[] TARGET_DEPLOYMENT_CHAINS = [BSC]; - uint64[] FINAL_DEPLOYED_CHAINS = [BSC, POLY, AVAX, GNOSIS]; - uint64[] PREVIOUS_DEPLOYMENT = [POLY, AVAX, GNOSIS]; + uint64[] TARGET_DEPLOYMENT_CHAINS = [ETH, BASE, OP, ARBI]; + uint64[] FINAL_DEPLOYED_CHAINS = [ETH, BASE, OP, ARBI, BSC, POLY, AVAX]; + uint64[] PREVIOUS_DEPLOYMENT = [BSC, POLY, AVAX]; + + ///@dev ORIGINAL SALT + bytes32 constant salt = "NO_SLEEP_VIK"; /// @notice The main stage 1 script entrypoint function deployStage1(uint256 selectedChainIndex) external { @@ -36,7 +40,7 @@ contract MainnetDeployNewChain is AbstractDeploySingle { } } - _deployStage1(selectedChainIndex, trueIndex, Cycle.Prod, TARGET_DEPLOYMENT_CHAINS); + _deployStage1(selectedChainIndex, trueIndex, Cycle.Prod, TARGET_DEPLOYMENT_CHAINS, salt); } /// @dev stage 2 must be called only after stage 1 is complete for all chains! diff --git a/script/Mainnet.Deploy.s.sol b/script/Mainnet.Deploy.s.sol index da7fab55a..3dcbbf840 100644 --- a/script/Mainnet.Deploy.s.sol +++ b/script/Mainnet.Deploy.s.sol @@ -10,6 +10,9 @@ contract MainnetDeploy is AbstractDeploySingle { uint64[] TARGET_DEPLOYMENT_CHAINS = [BSC, POLY, AVAX]; + ///@dev ORIGINAL SALT + bytes32 constant salt = "NO_SLEEP_VIK"; + /// @notice The main stage 1 script entrypoint function deployStage1(uint256 selectedChainIndex) external { _preDeploymentSetup(); @@ -22,7 +25,7 @@ contract MainnetDeploy is AbstractDeploySingle { } } - _deployStage1(selectedChainIndex, trueIndex, Cycle.Prod, TARGET_DEPLOYMENT_CHAINS); + _deployStage1(selectedChainIndex, trueIndex, Cycle.Prod, TARGET_DEPLOYMENT_CHAINS, salt); } /// @dev stage 2 must be called only after stage 1 is complete for all chains! diff --git a/script/Tenderly.Deploy.s.sol b/script/Tenderly.Deploy.s.sol new file mode 100644 index 000000000..5100760a4 --- /dev/null +++ b/script/Tenderly.Deploy.s.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +import { AbstractDeploySingle } from "./Abstract.Deploy.Single.s.sol"; + +contract TenderlyDeploy is AbstractDeploySingle { + /*////////////////////////////////////////////////////////////// + SELECT CHAIN IDS TO DEPLOY HERE + //////////////////////////////////////////////////////////////*/ + + uint64[] TARGET_DEPLOYMENT_CHAINS = [ETH, OP, ARBI]; + + /// @notice The main stage 1 script entrypoint + function deployStage1(uint256 selectedChainIndex, uint256 salt) external { + _preDeploymentSetup(); + uint256 trueIndex; + for (uint256 i = 0; i < chainIds.length; i++) { + if (TARGET_DEPLOYMENT_CHAINS[selectedChainIndex] == chainIds[i]) { + trueIndex = i; + + break; + } + } + + _deployStage1(selectedChainIndex, trueIndex, Cycle.Dev, TARGET_DEPLOYMENT_CHAINS, keccak256(abi.encode(salt))); + } + + /// @dev stage 2 must be called only after stage 1 is complete for all chains! + function deployStage2(uint256 selectedChainIndex) external { + _preDeploymentSetup(); + + uint256 trueIndex; + for (uint256 i = 0; i < chainIds.length; i++) { + if (TARGET_DEPLOYMENT_CHAINS[selectedChainIndex] == chainIds[i]) { + trueIndex = i; + break; + } + } + + _deployStage2(selectedChainIndex, trueIndex, Cycle.Dev, TARGET_DEPLOYMENT_CHAINS, TARGET_DEPLOYMENT_CHAINS); + } + + /// @dev stage 3 must be called only after stage 1 is complete for all chains! + function deployStage3(uint256 selectedChainIndex) external { + _preDeploymentSetup(); + + uint256 trueIndex; + for (uint256 i = 0; i < chainIds.length; i++) { + if (TARGET_DEPLOYMENT_CHAINS[selectedChainIndex] == chainIds[i]) { + trueIndex = i; + break; + } + } + + _deployStage3(selectedChainIndex, trueIndex, Cycle.Dev, TARGET_DEPLOYMENT_CHAINS); + } +} diff --git a/script/ctf_deployment/1/Ethereum-latest.json b/script/ctf_deployment/1/Ethereum-latest.json new file mode 100644 index 000000000..e69de29bb diff --git a/script/ctf_deployment/10/Optimism-latest.json b/script/ctf_deployment/10/Optimism-latest.json new file mode 100644 index 000000000..e69de29bb diff --git a/script/ctf_deployment/137/Polygon-latest.json b/script/ctf_deployment/137/Polygon-latest.json new file mode 100644 index 000000000..475c33dc1 --- /dev/null +++ b/script/ctf_deployment/137/Polygon-latest.json @@ -0,0 +1,19 @@ +{ + "CoreStateRegistry": "0x67812f7490d0931dA9f2A47Cc402476B08f78502", + "DstSwapper": "0x377E5829f552cd3435538006e754e24fA304ABd4", + "ERC4626Form": "0x20aA26bC4e64F5D0Bf74e6Bb1387eba124314ca3", + "EmergencyQueue": "0xBd0e276779619b8FA82041810A94B52795eFAAf7", + "HyperlaneImplementation": "0x8D64B4b4be39769441dCA258AA2aD035E2A876f6", + "LayerzeroImplementation": "0x40aF1811116541A62982a486A6B9E9287d1F115A", + "LiFiValidator": "0x9DC12F4CCcf34Cb3CF744696b5b98D85F12D2977", + "PayMaster": "0x3cA25b41bb127F43B5eC5b283457e8065138A850", + "PayloadHelper": "0x962435D50C95EBf870aC9E98954b6f0170167fa6", + "PaymentHelper": "0x1f03015196953f393aEb8a7bA423bE6Dd850Cf8d", + "SocketValidator": "0x750f09A785cf308015EAd09D62fB0AdF92b6e56D", + "SuperPositions": "0x4D4a0F29EFf9091360fd475fA3dB1Cc966faD958", + "SuperRBAC": "0x5512926dfD71dFE572B8ABCf8dc16521cD8dc03C", + "SuperRegistry": "0xB97612A25491E34F5fd11D521c14A042eca039Fa", + "SuperformFactory": "0x5BC1905b3195A85DE0e831e0514005807765bB54", + "SuperformRouter": "0xf274b178423B0A80E4c731419DcaD4363f6f9254", + "WormholeARImplementation": "0x2a489b88EcA3Ace75AA0f00AFa6659EDa5e4a7Ed" +} \ No newline at end of file diff --git a/script/ctf_deployment/42161/Arbitrum-latest.json b/script/ctf_deployment/42161/Arbitrum-latest.json new file mode 100644 index 000000000..e69de29bb diff --git a/script/ctf_deployment/43114/Avalanche-latest.json b/script/ctf_deployment/43114/Avalanche-latest.json new file mode 100644 index 000000000..475c33dc1 --- /dev/null +++ b/script/ctf_deployment/43114/Avalanche-latest.json @@ -0,0 +1,19 @@ +{ + "CoreStateRegistry": "0x67812f7490d0931dA9f2A47Cc402476B08f78502", + "DstSwapper": "0x377E5829f552cd3435538006e754e24fA304ABd4", + "ERC4626Form": "0x20aA26bC4e64F5D0Bf74e6Bb1387eba124314ca3", + "EmergencyQueue": "0xBd0e276779619b8FA82041810A94B52795eFAAf7", + "HyperlaneImplementation": "0x8D64B4b4be39769441dCA258AA2aD035E2A876f6", + "LayerzeroImplementation": "0x40aF1811116541A62982a486A6B9E9287d1F115A", + "LiFiValidator": "0x9DC12F4CCcf34Cb3CF744696b5b98D85F12D2977", + "PayMaster": "0x3cA25b41bb127F43B5eC5b283457e8065138A850", + "PayloadHelper": "0x962435D50C95EBf870aC9E98954b6f0170167fa6", + "PaymentHelper": "0x1f03015196953f393aEb8a7bA423bE6Dd850Cf8d", + "SocketValidator": "0x750f09A785cf308015EAd09D62fB0AdF92b6e56D", + "SuperPositions": "0x4D4a0F29EFf9091360fd475fA3dB1Cc966faD958", + "SuperRBAC": "0x5512926dfD71dFE572B8ABCf8dc16521cD8dc03C", + "SuperRegistry": "0xB97612A25491E34F5fd11D521c14A042eca039Fa", + "SuperformFactory": "0x5BC1905b3195A85DE0e831e0514005807765bB54", + "SuperformRouter": "0xf274b178423B0A80E4c731419DcaD4363f6f9254", + "WormholeARImplementation": "0x2a489b88EcA3Ace75AA0f00AFa6659EDa5e4a7Ed" +} \ No newline at end of file diff --git a/script/ctf_deployment/56/Binance-latest.json b/script/ctf_deployment/56/Binance-latest.json new file mode 100644 index 000000000..475c33dc1 --- /dev/null +++ b/script/ctf_deployment/56/Binance-latest.json @@ -0,0 +1,19 @@ +{ + "CoreStateRegistry": "0x67812f7490d0931dA9f2A47Cc402476B08f78502", + "DstSwapper": "0x377E5829f552cd3435538006e754e24fA304ABd4", + "ERC4626Form": "0x20aA26bC4e64F5D0Bf74e6Bb1387eba124314ca3", + "EmergencyQueue": "0xBd0e276779619b8FA82041810A94B52795eFAAf7", + "HyperlaneImplementation": "0x8D64B4b4be39769441dCA258AA2aD035E2A876f6", + "LayerzeroImplementation": "0x40aF1811116541A62982a486A6B9E9287d1F115A", + "LiFiValidator": "0x9DC12F4CCcf34Cb3CF744696b5b98D85F12D2977", + "PayMaster": "0x3cA25b41bb127F43B5eC5b283457e8065138A850", + "PayloadHelper": "0x962435D50C95EBf870aC9E98954b6f0170167fa6", + "PaymentHelper": "0x1f03015196953f393aEb8a7bA423bE6Dd850Cf8d", + "SocketValidator": "0x750f09A785cf308015EAd09D62fB0AdF92b6e56D", + "SuperPositions": "0x4D4a0F29EFf9091360fd475fA3dB1Cc966faD958", + "SuperRBAC": "0x5512926dfD71dFE572B8ABCf8dc16521cD8dc03C", + "SuperRegistry": "0xB97612A25491E34F5fd11D521c14A042eca039Fa", + "SuperformFactory": "0x5BC1905b3195A85DE0e831e0514005807765bB54", + "SuperformRouter": "0xf274b178423B0A80E4c731419DcaD4363f6f9254", + "WormholeARImplementation": "0x2a489b88EcA3Ace75AA0f00AFa6659EDa5e4a7Ed" +} \ No newline at end of file diff --git a/script/ctf_deployment/8453/Base-latest.json b/script/ctf_deployment/8453/Base-latest.json new file mode 100644 index 000000000..e69de29bb diff --git a/script/output/1/Ethereum-latest.json b/script/output/1/Ethereum-latest.json new file mode 100644 index 000000000..e69de29bb diff --git a/script/output/10/Optimism-latest.json b/script/output/10/Optimism-latest.json new file mode 100644 index 000000000..e69de29bb diff --git a/script/output/137/Polygon-latest.json b/script/output/137/Polygon-latest.json index 16602521e..c8500329b 100644 --- a/script/output/137/Polygon-latest.json +++ b/script/output/137/Polygon-latest.json @@ -1,25 +1,19 @@ { - "BroadcastRegistry": "0x91A4a6E08401FFC5Cf155d7d6A7234AA0302B160", - "CoreStateRegistry": "0xC525e2EE88E0014F46cb031C33c5148A153108eB", - "DstSwapper": "0x363A6294897577522795c80447f2f2829DCc85c4", - "ERC4626Form": "0x3bfEe78d526063e0207B1a55fD4B284306882247", - "ERC4626KYCDaoForm": "0xd72Ec34384b1ba5bCeC852Fa88420D0f6F90363c", - "ERC4626TimelockForm": "0x87D539863EaB9f3CB99B405929464f6aAb77e81B", - "EmergencyQueue": "0x8Ef837576f37C29BAcd1efb2Eb0ddAadB01DA189", - "HyperlaneImplementation": "0x922d821ddfA8958890d69053CA79a7E619A2790E", - "LayerzeroImplementation": "0x22adbC6BBb1A8C4eFB579268772E7516869E2Cc6", - "LiFiValidator": "0xe1E7b5011E0DcFb823916E8a91DBA9eC383E82C2", - "PayMaster": "0x917455CE06FCD3acFB697781D5E937e1ABcb7694", - "PayloadHelper": "0x3fF04DB7C5D74A544bC76BFabE37732196d56e34", - "PaymentHelper": "0xa4498fa6b5f964a84432Fa007B04255610E21992", - "SocketOneInchValidator": "0xceb161DAC215389B44FE15634A3e207bC886fa09", - "SocketValidator": "0x156889A11Ed41fdAAF3da7394D5cF577bb69eaC4", - "SuperPositions": "0xD240a9674E9a306009071Bd60966F66eA33341Fd", - "SuperRBAC": "0x3487eD8b15c93138573CDC1226CC3ecBb4CD31af", - "SuperRegistry": "0xB0bBFC88896c1a218fbAFab6C8dcf43eD587Ad63", - "SuperformFactory": "0x7791026F1770780c00cEe224F9d0B61c962E9fF8", - "SuperformRouter": "0x51d0a7a59F4e54a273FCbA4Fd4ea9E647e785d95", - "TimelockStateRegistry": "0x1cbf6A3B019dAef506e13bf6A353e3C47Cd8250c", - "WormholeARImplementation": "0x6f1d34A20C0d62A4Fc2eCA0919Fb6fDD73F37428", - "WormholeSRImplementation": "0x63a62c7AF0Ed62C394420dBC7Aa0f803236886aC" + "CoreStateRegistry": "0x67812f7490d0931dA9f2A47Cc402476B08f78502", + "DstSwapper": "0x377E5829f552cd3435538006e754e24fA304ABd4", + "ERC4626Form": "0x20aA26bC4e64F5D0Bf74e6Bb1387eba124314ca3", + "EmergencyQueue": "0xBd0e276779619b8FA82041810A94B52795eFAAf7", + "HyperlaneImplementation": "0x8D64B4b4be39769441dCA258AA2aD035E2A876f6", + "LayerzeroImplementation": "0x40aF1811116541A62982a486A6B9E9287d1F115A", + "LiFiValidator": "0x9DC12F4CCcf34Cb3CF744696b5b98D85F12D2977", + "PayMaster": "0x3cA25b41bb127F43B5eC5b283457e8065138A850", + "PayloadHelper": "0x962435D50C95EBf870aC9E98954b6f0170167fa6", + "PaymentHelper": "0x1f03015196953f393aEb8a7bA423bE6Dd850Cf8d", + "SocketValidator": "0x750f09A785cf308015EAd09D62fB0AdF92b6e56D", + "SuperPositions": "0x4D4a0F29EFf9091360fd475fA3dB1Cc966faD958", + "SuperRBAC": "0x5512926dfD71dFE572B8ABCf8dc16521cD8dc03C", + "SuperRegistry": "0xB97612A25491E34F5fd11D521c14A042eca039Fa", + "SuperformFactory": "0x5BC1905b3195A85DE0e831e0514005807765bB54", + "SuperformRouter": "0xf274b178423B0A80E4c731419DcaD4363f6f9254", + "WormholeARImplementation": "0x2a489b88EcA3Ace75AA0f00AFa6659EDa5e4a7Ed" } \ No newline at end of file diff --git a/script/output/42161/Arbitrum-latest.json b/script/output/42161/Arbitrum-latest.json new file mode 100644 index 000000000..e69de29bb diff --git a/script/output/43114/Avalanche-latest.json b/script/output/43114/Avalanche-latest.json index 16602521e..c8500329b 100644 --- a/script/output/43114/Avalanche-latest.json +++ b/script/output/43114/Avalanche-latest.json @@ -1,25 +1,19 @@ { - "BroadcastRegistry": "0x91A4a6E08401FFC5Cf155d7d6A7234AA0302B160", - "CoreStateRegistry": "0xC525e2EE88E0014F46cb031C33c5148A153108eB", - "DstSwapper": "0x363A6294897577522795c80447f2f2829DCc85c4", - "ERC4626Form": "0x3bfEe78d526063e0207B1a55fD4B284306882247", - "ERC4626KYCDaoForm": "0xd72Ec34384b1ba5bCeC852Fa88420D0f6F90363c", - "ERC4626TimelockForm": "0x87D539863EaB9f3CB99B405929464f6aAb77e81B", - "EmergencyQueue": "0x8Ef837576f37C29BAcd1efb2Eb0ddAadB01DA189", - "HyperlaneImplementation": "0x922d821ddfA8958890d69053CA79a7E619A2790E", - "LayerzeroImplementation": "0x22adbC6BBb1A8C4eFB579268772E7516869E2Cc6", - "LiFiValidator": "0xe1E7b5011E0DcFb823916E8a91DBA9eC383E82C2", - "PayMaster": "0x917455CE06FCD3acFB697781D5E937e1ABcb7694", - "PayloadHelper": "0x3fF04DB7C5D74A544bC76BFabE37732196d56e34", - "PaymentHelper": "0xa4498fa6b5f964a84432Fa007B04255610E21992", - "SocketOneInchValidator": "0xceb161DAC215389B44FE15634A3e207bC886fa09", - "SocketValidator": "0x156889A11Ed41fdAAF3da7394D5cF577bb69eaC4", - "SuperPositions": "0xD240a9674E9a306009071Bd60966F66eA33341Fd", - "SuperRBAC": "0x3487eD8b15c93138573CDC1226CC3ecBb4CD31af", - "SuperRegistry": "0xB0bBFC88896c1a218fbAFab6C8dcf43eD587Ad63", - "SuperformFactory": "0x7791026F1770780c00cEe224F9d0B61c962E9fF8", - "SuperformRouter": "0x51d0a7a59F4e54a273FCbA4Fd4ea9E647e785d95", - "TimelockStateRegistry": "0x1cbf6A3B019dAef506e13bf6A353e3C47Cd8250c", - "WormholeARImplementation": "0x6f1d34A20C0d62A4Fc2eCA0919Fb6fDD73F37428", - "WormholeSRImplementation": "0x63a62c7AF0Ed62C394420dBC7Aa0f803236886aC" + "CoreStateRegistry": "0x67812f7490d0931dA9f2A47Cc402476B08f78502", + "DstSwapper": "0x377E5829f552cd3435538006e754e24fA304ABd4", + "ERC4626Form": "0x20aA26bC4e64F5D0Bf74e6Bb1387eba124314ca3", + "EmergencyQueue": "0xBd0e276779619b8FA82041810A94B52795eFAAf7", + "HyperlaneImplementation": "0x8D64B4b4be39769441dCA258AA2aD035E2A876f6", + "LayerzeroImplementation": "0x40aF1811116541A62982a486A6B9E9287d1F115A", + "LiFiValidator": "0x9DC12F4CCcf34Cb3CF744696b5b98D85F12D2977", + "PayMaster": "0x3cA25b41bb127F43B5eC5b283457e8065138A850", + "PayloadHelper": "0x962435D50C95EBf870aC9E98954b6f0170167fa6", + "PaymentHelper": "0x1f03015196953f393aEb8a7bA423bE6Dd850Cf8d", + "SocketValidator": "0x750f09A785cf308015EAd09D62fB0AdF92b6e56D", + "SuperPositions": "0x4D4a0F29EFf9091360fd475fA3dB1Cc966faD958", + "SuperRBAC": "0x5512926dfD71dFE572B8ABCf8dc16521cD8dc03C", + "SuperRegistry": "0xB97612A25491E34F5fd11D521c14A042eca039Fa", + "SuperformFactory": "0x5BC1905b3195A85DE0e831e0514005807765bB54", + "SuperformRouter": "0xf274b178423B0A80E4c731419DcaD4363f6f9254", + "WormholeARImplementation": "0x2a489b88EcA3Ace75AA0f00AFa6659EDa5e4a7Ed" } \ No newline at end of file diff --git a/script/output/56/Binance-latest.json b/script/output/56/Binance-latest.json index 16602521e..c8500329b 100644 --- a/script/output/56/Binance-latest.json +++ b/script/output/56/Binance-latest.json @@ -1,25 +1,19 @@ { - "BroadcastRegistry": "0x91A4a6E08401FFC5Cf155d7d6A7234AA0302B160", - "CoreStateRegistry": "0xC525e2EE88E0014F46cb031C33c5148A153108eB", - "DstSwapper": "0x363A6294897577522795c80447f2f2829DCc85c4", - "ERC4626Form": "0x3bfEe78d526063e0207B1a55fD4B284306882247", - "ERC4626KYCDaoForm": "0xd72Ec34384b1ba5bCeC852Fa88420D0f6F90363c", - "ERC4626TimelockForm": "0x87D539863EaB9f3CB99B405929464f6aAb77e81B", - "EmergencyQueue": "0x8Ef837576f37C29BAcd1efb2Eb0ddAadB01DA189", - "HyperlaneImplementation": "0x922d821ddfA8958890d69053CA79a7E619A2790E", - "LayerzeroImplementation": "0x22adbC6BBb1A8C4eFB579268772E7516869E2Cc6", - "LiFiValidator": "0xe1E7b5011E0DcFb823916E8a91DBA9eC383E82C2", - "PayMaster": "0x917455CE06FCD3acFB697781D5E937e1ABcb7694", - "PayloadHelper": "0x3fF04DB7C5D74A544bC76BFabE37732196d56e34", - "PaymentHelper": "0xa4498fa6b5f964a84432Fa007B04255610E21992", - "SocketOneInchValidator": "0xceb161DAC215389B44FE15634A3e207bC886fa09", - "SocketValidator": "0x156889A11Ed41fdAAF3da7394D5cF577bb69eaC4", - "SuperPositions": "0xD240a9674E9a306009071Bd60966F66eA33341Fd", - "SuperRBAC": "0x3487eD8b15c93138573CDC1226CC3ecBb4CD31af", - "SuperRegistry": "0xB0bBFC88896c1a218fbAFab6C8dcf43eD587Ad63", - "SuperformFactory": "0x7791026F1770780c00cEe224F9d0B61c962E9fF8", - "SuperformRouter": "0x51d0a7a59F4e54a273FCbA4Fd4ea9E647e785d95", - "TimelockStateRegistry": "0x1cbf6A3B019dAef506e13bf6A353e3C47Cd8250c", - "WormholeARImplementation": "0x6f1d34A20C0d62A4Fc2eCA0919Fb6fDD73F37428", - "WormholeSRImplementation": "0x63a62c7AF0Ed62C394420dBC7Aa0f803236886aC" + "CoreStateRegistry": "0x67812f7490d0931dA9f2A47Cc402476B08f78502", + "DstSwapper": "0x377E5829f552cd3435538006e754e24fA304ABd4", + "ERC4626Form": "0x20aA26bC4e64F5D0Bf74e6Bb1387eba124314ca3", + "EmergencyQueue": "0xBd0e276779619b8FA82041810A94B52795eFAAf7", + "HyperlaneImplementation": "0x8D64B4b4be39769441dCA258AA2aD035E2A876f6", + "LayerzeroImplementation": "0x40aF1811116541A62982a486A6B9E9287d1F115A", + "LiFiValidator": "0x9DC12F4CCcf34Cb3CF744696b5b98D85F12D2977", + "PayMaster": "0x3cA25b41bb127F43B5eC5b283457e8065138A850", + "PayloadHelper": "0x962435D50C95EBf870aC9E98954b6f0170167fa6", + "PaymentHelper": "0x1f03015196953f393aEb8a7bA423bE6Dd850Cf8d", + "SocketValidator": "0x750f09A785cf308015EAd09D62fB0AdF92b6e56D", + "SuperPositions": "0x4D4a0F29EFf9091360fd475fA3dB1Cc966faD958", + "SuperRBAC": "0x5512926dfD71dFE572B8ABCf8dc16521cD8dc03C", + "SuperRegistry": "0xB97612A25491E34F5fd11D521c14A042eca039Fa", + "SuperformFactory": "0x5BC1905b3195A85DE0e831e0514005807765bB54", + "SuperformRouter": "0xf274b178423B0A80E4c731419DcaD4363f6f9254", + "WormholeARImplementation": "0x2a489b88EcA3Ace75AA0f00AFa6659EDa5e4a7Ed" } \ No newline at end of file diff --git a/script/output/8453/Base-latest.json b/script/output/8453/Base-latest.json new file mode 100644 index 000000000..e69de29bb diff --git a/src/BaseForm.sol b/src/BaseForm.sol index 04683b8d8..750915233 100644 --- a/src/BaseForm.sol +++ b/src/BaseForm.sol @@ -1,21 +1,22 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { IBaseForm } from "src/interfaces/IBaseForm.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; +import { IEmergencyQueue } from "src/interfaces/IEmergencyQueue.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { Error } from "src/libraries/Error.sol"; +import { InitSingleVaultData } from "src/types/DataTypes.sol"; import { Initializable } from "openzeppelin-contracts/contracts/proxy/utils/Initializable.sol"; import { ERC165 } from "openzeppelin-contracts/contracts/utils/introspection/ERC165.sol"; import { IERC165 } from "openzeppelin-contracts/contracts/utils/introspection/IERC165.sol"; -import { InitSingleVaultData } from "./types/DataTypes.sol"; -import { IBaseForm } from "./interfaces/IBaseForm.sol"; -import { ISuperRegistry } from "./interfaces/ISuperRegistry.sol"; -import { Error } from "./libraries/Error.sol"; -import { ISuperformFactory } from "./interfaces/ISuperformFactory.sol"; -import { IEmergencyQueue } from "./interfaces/IEmergencyQueue.sol"; -import { DataLib } from "./libraries/DataLib.sol"; /// @title BaseForm -/// @author Zeropoint Labs. -/// @dev Abstract contract to be inherited by different form implementations -abstract contract BaseForm is Initializable, ERC165, IBaseForm { +/// @dev Abstract contract to be inherited by different Form implementations +/// @author Zeropoint Labs +abstract contract BaseForm is IBaseForm, Initializable, ERC165 { + using DataLib for uint256; ////////////////////////////////////////////////////////////// @@ -82,6 +83,10 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ////////////////////////////////////////////////////////////// constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + if (block.chainid > type(uint64).max) { revert Error.BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); } @@ -96,10 +101,6 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { // EXTERNAL VIEW FUNCTIONS // ////////////////////////////////////////////////////////////// - function supportsInterface(bytes4 interfaceId_) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId_ == type(IBaseForm).interfaceId || super.supportsInterface(interfaceId_); - } - /// @inheritdoc IBaseForm function superformYieldTokenName() external view virtual override returns (string memory); @@ -156,11 +157,9 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// - receive() external payable { } - - /// @param superRegistry_ ISuperRegistry address deployed - /// @param vault_ The vault address this form pertains to - /// @dev sets caller as the admin of the contract. + /// @param superRegistry_ ISuperRegistry address deployed + /// @param vault_ The vault address this form pertains to + /// @param asset_ The underlying asset address of the vault this form pertains to function initialize(address superRegistry_, address vault_, address asset_) external initializer { if (ISuperRegistry(superRegistry_) != superRegistry) revert Error.NOT_SUPER_REGISTRY(); if (vault_ == address(0) || asset_ == address(0)) revert Error.ZERO_ADDRESS(); @@ -178,9 +177,9 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { override onlySuperRouter notPaused(singleVaultData_) - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _directDepositIntoVault(singleVaultData_, srcSender_); + shares = _directDepositIntoVault(singleVaultData_, srcSender_); } /// @inheritdoc IBaseForm @@ -191,14 +190,12 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { external override onlySuperRouter - returns (uint256 dstAmount) + returns (uint256 assets) { if (!_isPaused(singleVaultData_.superformId)) { - dstAmount = _directWithdrawFromVault(singleVaultData_, srcSender_); + assets = _directWithdrawFromVault(singleVaultData_, srcSender_); } else { - IEmergencyQueue(superRegistry.getAddress(keccak256("EMERGENCY_QUEUE"))).queueWithdrawal( - singleVaultData_, srcSender_ - ); + IEmergencyQueue(superRegistry.getAddress(keccak256("EMERGENCY_QUEUE"))).queueWithdrawal(singleVaultData_); } } @@ -212,9 +209,13 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { override onlyCoreStateRegistry notPaused(singleVaultData_) - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _xChainDepositIntoVault(singleVaultData_, srcSender_, srcChainId_); + if (srcChainId_ != 0 && srcChainId_ != CHAIN_ID) { + shares = _xChainDepositIntoVault(singleVaultData_, srcSender_, srcChainId_); + } else { + revert Error.INVALID_CHAIN_ID(); + } } /// @inheritdoc IBaseForm @@ -226,33 +227,36 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { external override onlyCoreStateRegistry - returns (uint256 dstAmount) + returns (uint256 assets) { - if (!_isPaused(singleVaultData_.superformId)) { - dstAmount = _xChainWithdrawFromVault(singleVaultData_, srcSender_, srcChainId_); + if (srcChainId_ != 0 && srcChainId_ != CHAIN_ID) { + if (!_isPaused(singleVaultData_.superformId)) { + assets = _xChainWithdrawFromVault(singleVaultData_, srcSender_, srcChainId_); + } else { + IEmergencyQueue(superRegistry.getAddress(keccak256("EMERGENCY_QUEUE"))).queueWithdrawal( + singleVaultData_ + ); + } } else { - IEmergencyQueue(superRegistry.getAddress(keccak256("EMERGENCY_QUEUE"))).queueWithdrawal( - singleVaultData_, srcSender_ - ); + revert Error.INVALID_CHAIN_ID(); } } /// @inheritdoc IBaseForm - function emergencyWithdraw( - address srcSender_, - address refundAddress_, - uint256 amount_ - ) - external - override - onlyEmergencyQueue - { - _emergencyWithdraw(srcSender_, refundAddress_, amount_); + function emergencyWithdraw(address receiverAddress_, uint256 amount_) external override onlyEmergencyQueue { + _emergencyWithdraw(receiverAddress_, amount_); } /// @inheritdoc IBaseForm - function forwardDustToPaymaster() external override { - _forwardDustToPaymaster(); + function forwardDustToPaymaster(address token_) external override { + if (token_ == vault) revert Error.CANNOT_FORWARD_4646_TOKEN(); + _forwardDustToPaymaster(token_); + } + + /// @dev Checks if the Form implementation has the appropriate interface support + /// @param interfaceId_ is the interfaceId to check + function supportsInterface(bytes4 interfaceId_) public view virtual override(ERC165, IERC165) returns (bool) { + return interfaceId_ == type(IBaseForm).interfaceId || super.supportsInterface(interfaceId_); } ////////////////////////////////////////////////////////////// @@ -266,7 +270,7 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ) internal virtual - returns (uint256 dstAmount); + returns (uint256 shares); /// @dev Deposits underlying tokens into a vault function _xChainDepositIntoVault( @@ -276,7 +280,7 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ) internal virtual - returns (uint256 dstAmount); + returns (uint256 shares); /// @dev Withdraws underlying tokens from a vault function _directWithdrawFromVault( @@ -285,7 +289,7 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ) internal virtual - returns (uint256 dstAmount_); + returns (uint256 assets); /// @dev Withdraws underlying tokens from a vault function _xChainWithdrawFromVault( @@ -295,24 +299,23 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ) internal virtual - returns (uint256 dstAmount); + returns (uint256 assets); /// @dev withdraws vault shares from form during emergency - function _emergencyWithdraw(address srcSender_, address refundAddress_, uint256 amount_) internal virtual; + function _emergencyWithdraw(address receiverAddress_, uint256 amount_) internal virtual; /// @dev forwards dust to paymaster - function _forwardDustToPaymaster() internal virtual; + function _forwardDustToPaymaster(address token_) internal virtual; /// @dev returns if a form id is paused function _isPaused(uint256 superformId) internal view returns (bool) { - if (!ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))).isSuperform(superformId)) { + address factory = superRegistry.getAddress(keccak256("SUPERFORM_FACTORY")); + if (!ISuperformFactory(factory).isSuperform(superformId)) { revert Error.SUPERFORM_ID_NONEXISTENT(); } (, uint32 formImplementationId_,) = superformId.getSuperform(); - return ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))).isFormImplementationPaused( - formImplementationId_ - ); + return ISuperformFactory(factory).isFormImplementationPaused(formImplementationId_); } } diff --git a/src/BaseRouter.sol b/src/BaseRouter.sol index 98dcd67bb..7000011c5 100644 --- a/src/BaseRouter.sol +++ b/src/BaseRouter.sol @@ -1,17 +1,23 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { IBaseRouter } from "src/interfaces/IBaseRouter.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { Error } from "src/libraries/Error.sol"; +import { + SingleDirectSingleVaultStateReq, + SingleXChainSingleVaultStateReq, + SingleDirectMultiVaultStateReq, + SingleXChainMultiVaultStateReq, + MultiDstSingleVaultStateReq, + MultiDstMultiVaultStateReq +} from "src/types/DataTypes.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IBaseRouter } from "./interfaces/IBaseRouter.sol"; -import { ISuperRegistry } from "./interfaces/ISuperRegistry.sol"; -import "./libraries/Error.sol"; -import "./types/DataTypes.sol"; /// @title BaseRouter -/// @author Zeropoint Labs. -/// @dev Routes users funds and action information to a remote execution chain. -/// @dev abstract implementation that allows inheriting contract to implement the logic +/// @dev Abstract implementation that allows Routers to implement the logic +/// @author Zeropoint Labs abstract contract BaseRouter is IBaseRouter { using SafeERC20 for IERC20; @@ -29,6 +35,10 @@ abstract contract BaseRouter is IBaseRouter { /// @param superRegistry_ the superform registry contract constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + if (block.chainid > type(uint64).max) { revert Error.BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); } @@ -112,4 +122,7 @@ abstract contract BaseRouter is IBaseRouter { /// @inheritdoc IBaseRouter function multiDstMultiVaultWithdraw(MultiDstMultiVaultStateReq calldata req_) external payable virtual override; + + /// @inheritdoc IBaseRouter + function forwardDustToPaymaster(address token_) external virtual override; } diff --git a/src/BaseRouterImplementation.sol b/src/BaseRouterImplementation.sol index 2133b478f..03cf94ca5 100644 --- a/src/BaseRouterImplementation.sol +++ b/src/BaseRouterImplementation.sol @@ -1,26 +1,44 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { BaseRouter } from "./BaseRouter.sol"; +import { BaseRouter } from "src/BaseRouter.sol"; +import { IBaseRouterImplementation } from "src/interfaces/IBaseRouterImplementation.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; +import { IPayMaster } from "src/interfaces/IPayMaster.sol"; +import { IPaymentHelper } from "src/interfaces/IPaymentHelper.sol"; +import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; +import { IBaseForm } from "src/interfaces/IBaseForm.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { ISuperPositions } from "src/interfaces/ISuperPositions.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { Error } from "src/libraries/Error.sol"; +import { IPermit2 } from "src/vendor/dragonfly-xyz/IPermit2.sol"; +import { LiquidityHandler } from "src/crosschain-liquidity/LiquidityHandler.sol"; +import { + SingleDirectSingleVaultStateReq, + SingleXChainSingleVaultStateReq, + SingleDirectMultiVaultStateReq, + SingleXChainMultiVaultStateReq, + MultiDstSingleVaultStateReq, + MultiDstMultiVaultStateReq, + LiqRequest, + InitSingleVaultData, + InitMultiVaultData, + MultiVaultSFData, + SingleVaultSFData, + AMBMessage, + CallbackType, + TransactionType +} from "src/types/DataTypes.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IBaseStateRegistry } from "./interfaces/IBaseStateRegistry.sol"; -import { IBaseRouterImplementation } from "./interfaces/IBaseRouterImplementation.sol"; -import { IPayMaster } from "./interfaces/IPayMaster.sol"; -import { IPaymentHelper } from "./interfaces/IPaymentHelper.sol"; -import { ISuperformFactory } from "./interfaces/ISuperformFactory.sol"; -import { IBaseForm } from "./interfaces/IBaseForm.sol"; -import { IBridgeValidator } from "./interfaces/IBridgeValidator.sol"; -import { ISuperPositions } from "./interfaces/ISuperPositions.sol"; -import { DataLib } from "./libraries/DataLib.sol"; -import { Error } from "./libraries/Error.sol"; -import { IPermit2 } from "./vendor/dragonfly-xyz/IPermit2.sol"; -import "./crosschain-liquidity/LiquidityHandler.sol"; -import "./types/DataTypes.sol"; +import { IERC1155Receiver } from "openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol"; +import { IERC165 } from "openzeppelin-contracts/contracts/utils/introspection/IERC165.sol"; +import { IERC1155Errors } from "openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol"; /// @title BaseRouterImplementation -/// @author Zeropoint Labs /// @dev Extends BaseRouter with standard internal execution functions +/// @author Zeropoint Labs abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRouter, LiquidityHandler { using SafeERC20 for IERC20; using DataLib for uint256; @@ -31,6 +49,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou /// @dev tracks the total payloads uint256 public payloadIds; + uint256 internal constant ENTIRE_SLIPPAGE = 10_000; ////////////////////////////////////////////////////////////// // STRUCTS // @@ -45,18 +64,51 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou bool deposit; } + struct DirectDepositArgs { + address superform; + uint256 payloadId; + uint256 superformId; + uint256 amount; + uint256 outputAmount; + uint256 maxSlippage; + bool retain4626; + LiqRequest liqData; + address receiverAddress; + bytes extraFormData; + uint256 msgValue; + address srcSender; + } + + struct SingleDepositLocalVars { + address superform; + uint256 shares; + } + + struct SingleDepositArgs { + address srcSender; + bytes permit2data; + address receiverAddressSP; + InitSingleVaultData vaultData; + } + struct MultiDepositLocalVars { uint256 len; address[] superforms; - uint256[] dstAmounts; + uint256[] shares; bool[] mints; } + struct MultiDepositArgs { + address srcSender; + bytes permit2data; + address receiverAddressSP; + InitMultiVaultData vaultData; + } + struct SingleTokenForwardLocalVars { IERC20 token; uint256 txDataLength; uint256 totalAmount; - uint256 approvalAmount; uint256 amountIn; uint8 bridgeId; address permit2; @@ -68,7 +120,6 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou uint256 totalAmount; uint256 permit2dataLen; uint256 targetLen; - uint256[] approvalAmounts; uint256[] amountsIn; uint8[] bridgeIds; address permit2; @@ -90,18 +141,26 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou return superRegistry.PERMIT2(); } + /// @dev returns the address from super registry + function _getAddress(bytes32 id_) internal view returns (address) { + return superRegistry.getAddress(id_); + } + /// @dev handles same-chain single vault deposit function _singleDirectSingleVaultDeposit(SingleDirectSingleVaultStateReq memory req_) internal virtual { /// @dev validate superformData if ( !_validateSuperformData( req_.superformData.superformId, - req_.superformData.maxSlippage, req_.superformData.amount, + req_.superformData.outputAmount, + req_.superformData.maxSlippage, req_.superformData.receiverAddress, + req_.superformData.receiverAddressSP, CHAIN_ID, true, - ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))) + ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))), + false ) ) { revert Error.INVALID_SUPERFORMS_DATA(); @@ -111,6 +170,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou 0, req_.superformData.superformId, req_.superformData.amount, + req_.superformData.outputAmount, req_.superformData.maxSlippage, req_.superformData.liqRequest, false, @@ -119,8 +179,12 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou req_.superformData.extraFormData ); - /// @dev same chain action & forward residual payment to payment collector - _directSingleDeposit(msg.sender, req_.superformData.permit2data, vaultData); + /// @dev same chain action & forward residual payment to Paymaster + _directSingleDeposit( + SingleDepositArgs( + msg.sender, req_.superformData.permit2data, req_.superformData.receiverAddressSP, vaultData + ) + ); emit Completed(); } @@ -135,12 +199,15 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou if ( !_validateSuperformData( req_.superformData.superformId, - req_.superformData.maxSlippage, req_.superformData.amount, + req_.superformData.outputAmount, + req_.superformData.maxSlippage, req_.superformData.receiverAddress, + req_.superformData.receiverAddressSP, req_.dstChainId, true, - ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))) + ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))), + false ) ) { revert Error.INVALID_SUPERFORMS_DATA(); @@ -152,6 +219,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.currentPayloadId, req_.superformData.superformId, req_.superformData.amount, + req_.superformData.outputAmount, req_.superformData.maxSlippage, req_.superformData.liqRequest, req_.superformData.hasDstSwap, @@ -163,7 +231,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou (address superform,,) = req_.superformData.superformId.getSuperform(); (uint256 amountIn, uint8 bridgeId) = - _singleVaultTokenForward(msg.sender, address(0), req_.superformData.permit2data, ambData); + _singleVaultTokenForward(msg.sender, address(0), req_.superformData.permit2data, ambData, true); LiqRequest memory emptyRequest; @@ -196,7 +264,8 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.srcChainId, req_.dstChainId, vars.currentPayloadId - ) + ), + req_.superformData.receiverAddressSP ); emit CrossChainInitiatedDepositSingle( @@ -215,6 +284,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou 0, req_.superformData.superformIds, req_.superformData.amounts, + req_.superformData.outputAmounts, req_.superformData.maxSlippages, req_.superformData.liqRequests, new bool[](req_.superformData.amounts.length), @@ -223,8 +293,12 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou req_.superformData.extraFormData ); - /// @dev same chain action & forward residual payment to payment collector - _directMultiDeposit(msg.sender, req_.superformData.permit2data, vaultData); + /// @dev same chain action & forward residual payment to Paymaster + _directMultiDeposit( + MultiDepositArgs( + msg.sender, req_.superformData.permit2data, req_.superformData.receiverAddressSP, vaultData + ) + ); emit Completed(); } @@ -246,6 +320,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.currentPayloadId, req_.superformsData.superformIds, req_.superformsData.amounts, + req_.superformsData.outputAmounts, req_.superformsData.maxSlippages, req_.superformsData.liqRequests, req_.superformsData.hasDstSwaps, @@ -265,10 +340,10 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou /// @dev this loop is what allows to deposit to >1 different underlying on destination /// @dev if a loop fails in a validation the whole chain should be reverted - for (uint256 j; j < len; ++j) { - vars.liqRequest = req_.superformsData.liqRequests[j]; + for (uint256 i; i < len; ++i) { + vars.liqRequest = req_.superformsData.liqRequests[i]; - (superform,,) = req_.superformsData.superformIds[j].getSuperform(); + (superform,,) = req_.superformsData.superformIds[i].getSuperform(); /// @dev dispatch liquidity data if ( @@ -277,7 +352,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.liqRequest, superform, vars.srcChainId, req_.dstChainId, msg.sender, true ) ) - ) ambData.liqData[j].interimToken = vars.liqRequest.interimToken; + ) ambData.liqData[i].interimToken = vars.liqRequest.interimToken; } /// @dev dispatch message information, notice multiVaults is set to 1 @@ -292,7 +367,8 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.srcChainId, req_.dstChainId, vars.currentPayloadId - ) + ), + req_.superformsData.receiverAddressSP ); emit CrossChainInitiatedDepositMulti( @@ -302,25 +378,25 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou /// @dev handles same-chain single vault withdraw function _singleDirectSingleVaultWithdraw(SingleDirectSingleVaultStateReq memory req_) internal virtual { - ActionLocalVars memory vars; - vars.srcChainId = CHAIN_ID; - /// @dev validate Superform data if ( !_validateSuperformData( req_.superformData.superformId, - req_.superformData.maxSlippage, req_.superformData.amount, + req_.superformData.outputAmount, + req_.superformData.maxSlippage, req_.superformData.receiverAddress, - vars.srcChainId, + req_.superformData.receiverAddressSP, + CHAIN_ID, false, - ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))) + ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))), + false ) ) { revert Error.INVALID_SUPERFORMS_DATA(); } - ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).burnSingle( + ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).burnSingle( msg.sender, req_.superformData.superformId, req_.superformData.amount ); @@ -328,10 +404,11 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou 0, req_.superformData.superformId, req_.superformData.amount, + req_.superformData.outputAmount, req_.superformData.maxSlippage, req_.superformData.liqRequest, false, - false, + req_.superformData.retain4626, req_.superformData.receiverAddress, req_.superformData.extraFormData ); @@ -355,18 +432,21 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou if ( !_validateSuperformData( req_.superformData.superformId, - req_.superformData.maxSlippage, req_.superformData.amount, + req_.superformData.outputAmount, + req_.superformData.maxSlippage, req_.superformData.receiverAddress, + req_.superformData.receiverAddressSP, req_.dstChainId, false, - ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))) + ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))), + false ) ) { revert Error.INVALID_SUPERFORMS_DATA(); } - ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).burnSingle( + ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).burnSingle( msg.sender, req_.superformData.superformId, req_.superformData.amount ); @@ -376,10 +456,11 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.currentPayloadId, req_.superformData.superformId, req_.superformData.amount, + req_.superformData.outputAmount, req_.superformData.maxSlippage, req_.superformData.liqRequest, false, - false, + req_.superformData.retain4626, req_.superformData.receiverAddress, req_.superformData.extraFormData ); @@ -399,7 +480,8 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.srcChainId, req_.dstChainId, vars.currentPayloadId - ) + ), + req_.superformData.receiverAddressSP ); emit CrossChainInitiatedWithdrawSingle( @@ -418,7 +500,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou } /// @dev SuperPositions are burnt optimistically here - ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).burnBatch( + ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).burnBatch( msg.sender, req_.superformData.superformIds, req_.superformData.amounts ); @@ -426,15 +508,16 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou 0, req_.superformData.superformIds, req_.superformData.amounts, + req_.superformData.outputAmounts, req_.superformData.maxSlippages, req_.superformData.liqRequests, new bool[](req_.superformData.superformIds.length), - new bool[](req_.superformData.superformIds.length), + req_.superformData.retain4626s, req_.superformData.receiverAddress, req_.superformData.extraFormData ); - /// @dev same chain action & forward residual payment to payment collector + /// @dev same chain action & forward residual payment to Paymaster _directMultiWithdraw(vaultData, msg.sender); emit Completed(); } @@ -453,7 +536,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou revert Error.INVALID_SUPERFORMS_DATA(); } - ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).burnBatch( + ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).burnBatch( msg.sender, req_.superformsData.superformIds, req_.superformsData.amounts ); @@ -463,10 +546,11 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.currentPayloadId, req_.superformsData.superformIds, req_.superformsData.amounts, + req_.superformsData.outputAmounts, req_.superformsData.maxSlippages, req_.superformsData.liqRequests, new bool[](req_.superformsData.amounts.length), - new bool[](req_.superformsData.amounts.length), + req_.superformsData.retain4626s, req_.superformsData.receiverAddress, req_.superformsData.extraFormData ); @@ -483,7 +567,8 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vars.srcChainId, req_.dstChainId, vars.currentPayloadId - ) + ), + req_.superformsData.receiverAddressSP ); emit CrossChainInitiatedWithdrawMulti( @@ -522,29 +607,28 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou ); } - function _dispatchAmbMessage(DispatchAMBMessageVars memory vars_) internal virtual { - AMBMessage memory ambMessage = AMBMessage( - DataLib.packTxInfo( - uint8(vars_.txType), - uint8(CallbackType.INIT), - vars_.multiVaults, - STATE_REGISTRY_TYPE, - vars_.srcSender, - vars_.srcChainId - ), - vars_.ambData + function _dispatchAmbMessage(DispatchAMBMessageVars memory vars_, address receiverAddressSP_) internal virtual { + uint256 txInfo = DataLib.packTxInfo( + uint8(vars_.txType), + uint8(CallbackType.INIT), + vars_.multiVaults, + STATE_REGISTRY_TYPE, + vars_.srcSender, + vars_.srcChainId ); - (uint256 fees, bytes memory extraData) = IPaymentHelper(superRegistry.getAddress(keccak256("PAYMENT_HELPER"))) - .calculateAMBData(vars_.dstChainId, vars_.ambIds, abi.encode(ambMessage)); + bytes memory ambMessage = abi.encode(AMBMessage(txInfo, vars_.ambData)); + + (uint256 fees, bytes memory extraData) = IPaymentHelper(_getAddress(keccak256("PAYMENT_HELPER"))) + .calculateAMBData(vars_.dstChainId, vars_.ambIds, ambMessage); - ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).updateTxHistory( - vars_.currentPayloadId, ambMessage.txInfo + ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).updateTxHistory( + vars_.currentPayloadId, txInfo, receiverAddressSP_ ); /// @dev this call dispatches the message to the AMB bridge through dispatchPayload - IBaseStateRegistry(superRegistry.getAddress(keccak256("CORE_STATE_REGISTRY"))).dispatchPayload{ value: fees }( - vars_.srcSender, vars_.ambIds, vars_.dstChainId, abi.encode(ambMessage), extraData + IBaseStateRegistry(_getAddress(keccak256("CORE_STATE_REGISTRY"))).dispatchPayload{ value: fees }( + vars_.srcSender, vars_.ambIds, vars_.dstChainId, ambMessage, extraData ); } @@ -553,128 +637,103 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou ////////////////////////////////////////////////////////////// /// @notice fulfils the final stage of same chain deposit action - function _directDeposit( - address superform_, - uint256 payloadId_, - uint256 superformId_, - uint256 amount_, - uint256 maxSlippage_, - bool retain4626_, - LiqRequest memory liqData_, - address receiverAddress_, - bytes memory extraFormData_, - uint256 msgValue_, - address srcSender_ - ) - internal - virtual - returns (uint256 dstAmount) - { - /// @dev deposits token to a given vault and mint vault positions directly through the form - dstAmount = IBaseForm(superform_).directDepositIntoVault{ value: msgValue_ }( + function _directDeposit(DirectDepositArgs memory args) internal virtual returns (uint256 shares) { + // @dev deposits token to a given vault and mint vault positions directly through the form + shares = IBaseForm(args.superform).directDepositIntoVault{ value: args.msgValue }( InitSingleVaultData( - payloadId_, - superformId_, - amount_, - maxSlippage_, - liqData_, + args.payloadId, + args.superformId, + args.amount, + args.outputAmount, + args.maxSlippage, + args.liqData, false, - retain4626_, - receiverAddress_, - /// needed if user if keeping 4626 - extraFormData_ + args.retain4626, + args.receiverAddress, + args.extraFormData ), - srcSender_ + args.srcSender ); } /// @notice deposits to single vault on the same chain /// @dev calls `_directDeposit` - function _directSingleDeposit( - address srcSender_, - bytes memory permit2data_, - InitSingleVaultData memory vaultData_ - ) - internal - virtual - { - address superform; - uint256 dstAmount; - - /// @dev decode superforms - (superform,,) = vaultData_.superformId.getSuperform(); - - _singleVaultTokenForward(srcSender_, superform, permit2data_, vaultData_); - - /// @dev deposits token to a given vault and mint vault positions. - dstAmount = _directDeposit( - superform, - vaultData_.payloadId, - vaultData_.superformId, - vaultData_.amount, - vaultData_.maxSlippage, - vaultData_.retain4626, - vaultData_.liqData, - vaultData_.receiverAddress, - vaultData_.extraFormData, - vaultData_.liqData.nativeAmount, - srcSender_ + function _directSingleDeposit(SingleDepositArgs memory args_) internal virtual { + SingleDepositLocalVars memory v; + + // @dev decode superforms + (v.superform,,) = args_.vaultData.superformId.getSuperform(); + + _singleVaultTokenForward(args_.srcSender, v.superform, args_.permit2data, args_.vaultData, false); + + // @dev deposits token to a given vault and mint vault positions. + v.shares = _directDeposit( + DirectDepositArgs( + v.superform, + args_.vaultData.payloadId, + args_.vaultData.superformId, + args_.vaultData.amount, + args_.vaultData.outputAmount, + args_.vaultData.maxSlippage, + args_.vaultData.retain4626, + args_.vaultData.liqData, + args_.vaultData.receiverAddress, + args_.vaultData.extraFormData, + args_.vaultData.liqData.nativeAmount, + args_.srcSender + ) ); - if (dstAmount != 0 && !vaultData_.retain4626) { - /// @dev mint super positions at the end of the deposit action if user doesn't retain 4626 - ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).mintSingle( - srcSender_, vaultData_.superformId, dstAmount + if (v.shares != 0 && !args_.vaultData.retain4626) { + // @dev mint super positions at the end of the deposit action if user doesn't retain 4626 + ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).mintSingle( + args_.receiverAddressSP, args_.vaultData.superformId, v.shares ); } } /// @notice deposits to multiple vaults on the same chain /// @dev loops and call `_directDeposit` - function _directMultiDeposit( - address srcSender_, - bytes memory permit2data_, - InitMultiVaultData memory vaultData_ - ) - internal - virtual - { + function _directMultiDeposit(MultiDepositArgs memory args_) internal virtual { MultiDepositLocalVars memory v; - v.len = vaultData_.superformIds.length; + v.len = args_.vaultData.superformIds.length; v.superforms = new address[](v.len); - v.dstAmounts = new uint256[](v.len); + v.shares = new uint256[](v.len); /// @dev decode superforms - v.superforms = DataLib.getSuperforms(vaultData_.superformIds); + v.superforms = DataLib.getSuperforms(args_.vaultData.superformIds); - _multiVaultTokenForward(srcSender_, v.superforms, permit2data_, vaultData_, false); + _multiVaultTokenForward(args_.srcSender, v.superforms, args_.permit2data, args_.vaultData, false); for (uint256 i; i < v.len; ++i) { /// @dev deposits token to a given vault and mint vault positions. - v.dstAmounts[i] = _directDeposit( - v.superforms[i], - vaultData_.payloadId, - vaultData_.superformIds[i], - vaultData_.amounts[i], - vaultData_.maxSlippages[i], - vaultData_.retain4626s[i], - vaultData_.liqData[i], - vaultData_.receiverAddress, - vaultData_.extraFormData, - vaultData_.liqData[i].nativeAmount, - srcSender_ + v.shares[i] = _directDeposit( + DirectDepositArgs( + v.superforms[i], + args_.vaultData.payloadId, + args_.vaultData.superformIds[i], + args_.vaultData.amounts[i], + args_.vaultData.outputAmounts[i], + args_.vaultData.maxSlippages[i], + args_.vaultData.retain4626s[i], + args_.vaultData.liqData[i], + args_.vaultData.receiverAddress, + args_.vaultData.extraFormData, + args_.vaultData.liqData[i].nativeAmount, + args_.srcSender + ) ); /// @dev if retain4626 is set to True, set the amount of SuperPositions to mint to 0 - if (v.dstAmounts[i] != 0 && vaultData_.retain4626s[i]) { - v.dstAmounts[i] = 0; + if (v.shares[i] != 0 && args_.vaultData.retain4626s[i]) { + v.shares[i] = 0; } } /// @dev in direct deposits, SuperPositions are minted right after depositing to vaults - ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).mintBatch( - srcSender_, vaultData_.superformIds, v.dstAmounts + ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).mintBatch( + args_.receiverAddressSP, args_.vaultData.superformIds, v.shares ); } @@ -688,8 +747,10 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou uint256 payloadId_, uint256 superformId_, uint256 amount_, + uint256 outputAmount_, uint256 maxSlippage_, LiqRequest memory liqData_, + bool retain4626_, address receiverAddress_, bytes memory extraFormData_, address srcSender_ @@ -703,10 +764,11 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou payloadId_, superformId_, amount_, + outputAmount_, maxSlippage_, liqData_, false, - false, + retain4626_, receiverAddress_, extraFormData_ ), @@ -725,8 +787,10 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vaultData_.payloadId, vaultData_.superformId, vaultData_.amount, + vaultData_.outputAmount, vaultData_.maxSlippage, vaultData_.liqData, + vaultData_.retain4626, vaultData_.receiverAddress, vaultData_.extraFormData, srcSender_ @@ -747,8 +811,10 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou vaultData_.payloadId, vaultData_.superformIds[i], vaultData_.amounts[i], + vaultData_.outputAmounts[i], vaultData_.maxSlippages[i], vaultData_.liqData[i], + vaultData_.retain4626s[i], vaultData_.receiverAddress, vaultData_.extraFormData, srcSender_ @@ -756,21 +822,36 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou } } + function _forwardDustToPaymaster(address token_) internal { + if (token_ == address(0)) revert Error.ZERO_ADDRESS(); + + address paymaster = _getAddress(keccak256("PAYMASTER")); + IERC20 token = IERC20(token_); + + uint256 dust = token.balanceOf(address(this)); + if (dust != 0) { + token.safeTransfer(paymaster, dust); + emit RouterDustForwardedToPaymaster(token_, dust); + } + } + ////////////////////////////////////////////////////////////// // INTERNAL VALIDATION HELPERS // ////////////////////////////////////////////////////////////// function _validateSuperformData( uint256 superformId_, - uint256 maxSlippage_, uint256 amount_, + uint256 outputAmount_, + uint256 maxSlippage_, address receiverAddress_, + address receiverAddressSP_, uint64 dstChainId_, bool isDeposit_, - ISuperformFactory factory_ + ISuperformFactory factory_, + bool multi_ ) internal - view virtual returns (bool) { @@ -786,23 +867,37 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou if (dstChainId_ != sfDstChainId) return false; /// @dev 10000 = 100% slippage - if (maxSlippage_ > 10_000) return false; + if (maxSlippage_ > ENTIRE_SLIPPAGE) return false; - /// @dev amount can't be 0 - if (amount_ == 0) return false; - - if (isDeposit_ && factory_.isFormImplementationPaused(formImplementationId)) return false; + /// @dev amounts can't be 0 + if (amount_ == 0 || outputAmount_ == 0) return false; + /// @dev only validate this for non multi case (multi case is validated in _validateSuperformsData) /// @dev ensure that receiver address is set always /// @dev in deposits, this is important for receive4626 (on destination). It is also important for refunds on /// destination - /// @dev in withdraws, this is important for cross chain cases where user uses smart contract wallets without - /// create2 - if (receiverAddress_ == address(0)) { + /// @dev in withdraws, this is important for the user to receive their tokens in the liqDstChainId + if (!multi_ && receiverAddress_ == address(0)) { return false; } - /// if it reaches this point then is valid + /// @dev redundant check on same chain, but helpful on xchain actions to halt deposits earlier + if (isDeposit_) { + if (factory_.isFormImplementationPaused(formImplementationId)) { + return false; + } + + /// @dev only validate this for non multi case (multi case is validated in _validateSuperformsData) + if (!multi_) { + if (receiverAddressSP_ == address(0)) { + return false; + } else { + /// @dev if receiverAddressSP_ is set and is a contract, it must implement onERC1155Received + _doSafeTransferAcceptanceCheck(receiverAddressSP_); + } + } + } + return true; } @@ -812,7 +907,6 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou bool deposit_ ) internal - view virtual returns (bool) { @@ -824,31 +918,67 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou if (len == 0 || liqRequestsLen == 0) return false; if (len != liqRequestsLen) return false; - /// @dev deposits beyond max vaults per tx is blocked only for xchain - if (lenSuperforms > superRegistry.getVaultLimitPerTx(dstChainId_)) { + /// @dev all other length checks + if ( + lenSuperforms != len || lenSuperforms != superformsData_.outputAmounts.length + || lenSuperforms != superformsData_.maxSlippages.length + || lenSuperforms != superformsData_.hasDstSwaps.length + || lenSuperforms != superformsData_.retain4626s.length + ) { return false; } - /// @dev superformIds/amounts/slippages array sizes validation - if (!(lenSuperforms == len && lenSuperforms == superformsData_.maxSlippages.length)) { + /// @dev deposits beyond multi vault limit for a given destination chain blocked + if (lenSuperforms > superRegistry.getVaultLimitPerDestination(dstChainId_)) { return false; } - ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); - bool valid; + + /// @dev since this is a multi case, validate receiverAddress here once + if (superformsData_.receiverAddress == address(0)) { + return false; + } + + /// @dev since this is a multi case, validate receiverAddressSP here once + if (deposit_) { + if (superformsData_.receiverAddressSP == address(0)) { + return false; + } else { + /// @dev if receiverAddressSP_ is set and is a contract, it must implement onERC1155Received + _doSafeTransferAcceptanceCheck(superformsData_.receiverAddressSP); + } + } + + ISuperformFactory factory = ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))); + /// @dev slippage, amount, paused status validation for (uint256 i; i < len; ++i) { - valid = _validateSuperformData( - superformsData_.superformIds[i], - superformsData_.maxSlippages[i], - superformsData_.amounts[i], - superformsData_.receiverAddress, - dstChainId_, - deposit_, - factory - ); + if ( + !_validateSuperformData( + superformsData_.superformIds[i], + superformsData_.amounts[i], + superformsData_.outputAmounts[i], + superformsData_.maxSlippages[i], + superformsData_.receiverAddress, + superformsData_.receiverAddressSP, + dstChainId_, + deposit_, + factory, + true + ) + ) { + return false; + } - if (!valid) { - return valid; + /// @dev ensure interimTokens aren't repeated on destination chains + address interimToken = superformsData_.liqRequests[i].interimToken; + if (interimToken != address(0)) { + for (uint256 j; j < i; ++j) { + if (j != i) { + if (interimToken == superformsData_.liqRequests[j].interimToken) { + return false; + } + } + } } } @@ -859,15 +989,15 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou // INTERNAL FEE FORWARDING HELPERS // ////////////////////////////////////////////////////////////// - /// @dev forwards the residual payment to payment collector + /// @dev forwards the residual payment to Paymaster function _forwardPayment(uint256 _balanceBefore) internal virtual { - /// @dev deducts what's already available sends what's left in msg.value to payment collector + if (address(this).balance < _balanceBefore) revert Error.INSUFFICIENT_BALANCE(); + + /// @dev deducts what's already available sends what's left in msg.value to Paymaster uint256 residualPayment = address(this).balance - _balanceBefore; if (residualPayment != 0) { - IPayMaster(superRegistry.getAddress(keccak256("PAYMASTER"))).makePayment{ value: residualPayment }( - msg.sender - ); + IPayMaster(_getAddress(keccak256("PAYMASTER"))).makePayment{ value: residualPayment }(msg.sender); } } @@ -879,7 +1009,8 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou address srcSender_, address target_, bytes memory permit2data_, - InitSingleVaultData memory vaultData_ + InitSingleVaultData memory vaultData_, + bool xChain ) internal virtual @@ -891,22 +1022,21 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou v.txDataLength = vaultData_.liqData.txData.length; + if (v.txDataLength == 0 && xChain) { + revert Error.NO_TXDATA_PRESENT(); + } + if (v.txDataLength != 0) { v.amountIn = IBridgeValidator(superRegistry.getBridgeValidator(v.bridgeId)).decodeAmountIn( vaultData_.liqData.txData, false ); + } else { + v.amountIn = vaultData_.amount; } if (vaultData_.liqData.token != NATIVE) { v.token = IERC20(vaultData_.liqData.token); - if (v.txDataLength == 0) { - v.approvalAmount = vaultData_.amount; - } else { - v.approvalAmount = v.amountIn; - /// e.g asset in is USDC (6 decimals), we use this amount to approve the transfer to superform - } - if (permit2data_.length != 0) { v.permit2 = _getPermit2(); @@ -914,16 +1044,15 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou abi.decode(permit2data_, (uint256, uint256, bytes)); /// @dev moves the tokens from the user to the router - IPermit2(v.permit2).permitTransferFrom( // The permit message. IPermit2.PermitTransferFrom({ - permitted: IPermit2.TokenPermissions({ token: v.token, amount: v.approvalAmount }), + permitted: IPermit2.TokenPermissions({ token: v.token, amount: v.amountIn }), nonce: nonce, deadline: deadline }), // The transfer recipient and amount. - IPermit2.SignatureTransferDetails({ to: address(this), requestedAmount: v.approvalAmount }), + IPermit2.SignatureTransferDetails({ to: address(this), requestedAmount: v.amountIn }), // The owner of the tokens, which must also be // the signer of the message, otherwise this call // will fail. @@ -933,17 +1062,17 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou signature ); } else { - if (v.token.allowance(srcSender_, address(this)) < v.approvalAmount) { - revert Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE(); + if (v.token.allowance(srcSender_, address(this)) < v.amountIn) { + revert Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT(); } /// @dev moves the tokens from the user to the router - v.token.safeTransferFrom(srcSender_, address(this), v.approvalAmount); + v.token.safeTransferFrom(srcSender_, address(this), v.amountIn); } if (target_ != address(0)) { /// @dev approves the input amount to the target - v.token.safeIncreaseAllowance(target_, v.approvalAmount); + v.token.safeIncreaseAllowance(target_, v.amountIn); } } @@ -975,16 +1104,15 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou v.amountsIn[i] = IBridgeValidator(superRegistry.getBridgeValidator(v.bridgeIds[i])).decodeAmountIn( vaultData_.liqData[i].txData, false ); + } else { + v.amountsIn[i] = vaultData_.amounts[i]; } } if (token != NATIVE) { v.token = IERC20(token); - v.totalAmount; - v.permit2dataLen = permit2data_.length; - v.approvalAmounts = new uint256[](v.len); for (uint256 i; i < v.len; ++i) { if (vaultData_.liqData[i].token != address(v.token)) { @@ -992,15 +1120,11 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou } uint256 txDataLength = vaultData_.liqData[i].txData.length; - if (txDataLength == 0 && !xChain) { - v.approvalAmounts[i] = vaultData_.amounts[i]; - } else if (txDataLength == 0 && xChain) { + if (txDataLength == 0 && xChain) { revert Error.NO_TXDATA_PRESENT(); - } else { - v.approvalAmounts[i] = v.amountsIn[i]; } - v.totalAmount += v.approvalAmounts[i]; + v.totalAmount += v.amountsIn[i]; } if (v.totalAmount == 0) { @@ -1012,6 +1136,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou abi.decode(permit2data_, (uint256, uint256, bytes)); v.permit2 = _getPermit2(); + /// @dev moves the tokens from the user to the router IPermit2(v.permit2).permitTransferFrom( // The permit message. @@ -1032,7 +1157,7 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou ); } else { if (v.token.allowance(srcSender_, address(this)) < v.totalAmount) { - revert Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE(); + revert Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT(); } /// @dev moves the tokens from the user to the router @@ -1041,12 +1166,23 @@ abstract contract BaseRouterImplementation is IBaseRouterImplementation, BaseRou /// @dev approves individual final targets if needed here v.targetLen = targets_.length; - for (uint256 j; j < v.targetLen; ++j) { + for (uint256 i; i < v.targetLen; ++i) { /// @dev approves the superform - v.token.safeIncreaseAllowance(targets_[j], v.approvalAmounts[j]); + v.token.safeIncreaseAllowance(targets_[i], v.amountsIn[i]); } } return (v.amountsIn, v.bridgeIds); } + + /// @dev implementation copied from OpenZeppelin 5.0 and stripped down + function _doSafeTransferAcceptanceCheck(address to) private view { + if (to.code.length > 0) { + try IERC165(to).supportsInterface(type(IERC1155Receiver).interfaceId) returns (bool supported) { + if (!supported) revert IERC1155Errors.ERC1155InvalidReceiver(to); + } catch { + revert IERC1155Errors.ERC1155InvalidReceiver(to); + } + } + } } diff --git a/src/EmergencyQueue.sol b/src/EmergencyQueue.sol index de8a66251..76d272d18 100644 --- a/src/EmergencyQueue.sol +++ b/src/EmergencyQueue.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { DataLib } from "./libraries/DataLib.sol"; -import { IBaseForm } from "./interfaces/IBaseForm.sol"; -import { ISuperRegistry } from "./interfaces/ISuperRegistry.sol"; -import { ISuperRBAC } from "./interfaces/ISuperRBAC.sol"; -import { IEmergencyQueue } from "./interfaces/IEmergencyQueue.sol"; -import { Error } from "./libraries/Error.sol"; -import { ISuperformFactory } from "./interfaces/ISuperformFactory.sol"; -import "./types/DataTypes.sol"; +import { IEmergencyQueue } from "src/interfaces/IEmergencyQueue.sol"; +import { IBaseForm } from "src/interfaces/IBaseForm.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { Error } from "src/libraries/Error.sol"; +import { InitSingleVaultData, QueuedWithdrawal } from "src/types/DataTypes.sol"; /// @title EmergencyQueue +/// @dev Stores withdrawal requests when forms are paused /// @author Zeropoint Labs -/// @dev stores withdrawal requests when forms are paused contract EmergencyQueue is IEmergencyQueue { using DataLib for uint256; @@ -60,6 +60,10 @@ contract EmergencyQueue is IEmergencyQueue { /// @param superRegistry_ the superform registry contract constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + if (block.chainid > type(uint64).max) { revert Error.BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); } @@ -82,22 +86,13 @@ contract EmergencyQueue is IEmergencyQueue { ////////////////////////////////////////////////////////////// /// @inheritdoc IEmergencyQueue - function queueWithdrawal( - InitSingleVaultData memory data_, - address srcSender_ - ) - external - override - onlySuperform(data_.superformId) - { + function queueWithdrawal(InitSingleVaultData memory data_) external override onlySuperform(data_.superformId) { ++queueCounter; queuedWithdrawal[queueCounter] = - QueuedWithdrawal(srcSender_, data_.receiverAddress, data_.superformId, data_.amount, data_.payloadId, false); + QueuedWithdrawal(data_.receiverAddress, data_.superformId, data_.amount, data_.payloadId, false); - emit WithdrawalQueued( - srcSender_, data_.receiverAddress, queueCounter, data_.superformId, data_.amount, data_.payloadId - ); + emit WithdrawalQueued(data_.receiverAddress, queueCounter, data_.superformId, data_.amount, data_.payloadId); } /// @inheritdoc IEmergencyQueue @@ -126,8 +121,8 @@ contract EmergencyQueue is IEmergencyQueue { data.isProcessed = true; (address superform,,) = data.superformId.getSuperform(); - IBaseForm(superform).emergencyWithdraw(data.srcSender, data.refundAddress, data.amount); + IBaseForm(superform).emergencyWithdraw(data.receiverAddress, data.amount); - emit WithdrawalProcessed(data.refundAddress, id_, data.superformId, data.amount); + emit WithdrawalProcessed(data.receiverAddress, id_, data.superformId, data.amount); } } diff --git a/src/SuperPositions.sol b/src/SuperPositions.sol index b5891b974..eb3423e60 100644 --- a/src/SuperPositions.sol +++ b/src/SuperPositions.sol @@ -3,6 +3,15 @@ pragma solidity ^0.8.23; import { ERC1155A } from "ERC1155A/ERC1155A.sol"; import { aERC20 } from "ERC1155A/aERC20.sol"; +import { ISuperPositions } from "src/interfaces/ISuperPositions.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; +import { IBaseForm } from "src/interfaces/IBaseForm.sol"; +import { IBroadcastRegistry } from "./interfaces/IBroadcastRegistry.sol"; +import { IPaymentHelper } from "./interfaces/IPaymentHelper.sol"; +import { Error } from "src/libraries/Error.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; import { TransactionType, ReturnMultiData, @@ -12,34 +21,28 @@ import { BroadcastMessage } from "src/types/DataTypes.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; -import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; -import { ISuperPositions } from "src/interfaces/ISuperPositions.sol"; -import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; -import { IBaseForm } from "src/interfaces/IBaseForm.sol"; -import { IBroadcastRegistry } from "./interfaces/IBroadcastRegistry.sol"; -import { IPaymentHelper } from "./interfaces/IPaymentHelper.sol"; -import { Error } from "src/libraries/Error.sol"; -import { DataLib } from "src/libraries/DataLib.sol"; /// @title SuperPositions -/// @author Zeropoint Labs. +/// @dev Cross-chain LP token minted on source chain +/// @author Zeropoint Labs contract SuperPositions is ISuperPositions, ERC1155A { using DataLib for uint256; ////////////////////////////////////////////////////////////// // CONSTANTS // ////////////////////////////////////////////////////////////// + ISuperRegistry public immutable superRegistry; uint64 public immutable CHAIN_ID; - bytes32 constant DEPLOY_NEW_AERC20 = keccak256("DEPLOY_NEW_AERC20"); + uint8 internal constant CORE_STATE_REGISTRY_ID = 1; + bytes32 internal constant DEPLOY_NEW_AERC20 = keccak256("DEPLOY_NEW_AERC20"); ////////////////////////////////////////////////////////////// // STATE VARIABLES // ////////////////////////////////////////////////////////////// /// @dev maps all transaction data routed through the smart contract. - mapping(uint256 transactionId => uint256 txInfo) public override txHistory; + mapping(uint256 transactionId => TxHistory txHistory) public override txHistory; /// @dev is the base uri set by admin string public dynamicURI; @@ -66,15 +69,18 @@ contract SuperPositions is ISuperPositions, ERC1155A { _; } + /// @dev is used in same chain case (as superform is available on the chain to validate caller) modifier onlyMinter(uint256 superformId) { address router = superRegistry.getAddress(keccak256("SUPERFORM_ROUTER")); - /// if msg.sender isn't superformRouter then it must be state registry for that superform + /// if msg.sender isn't superformRouter then it must be state registry of that form if (msg.sender != router) { - (, uint32 formBeaconId,) = DataLib.getSuperform(superformId); uint8 registryId = superRegistry.getStateRegistryId(msg.sender); - if (uint32(registryId) != formBeaconId) { + (address superform,,) = DataLib.getSuperform(superformId); + uint8 formRegistryId = IBaseForm(superform).getStateRegistryId(); + + if (registryId != formRegistryId) { revert Error.NOT_MINTER(); } } @@ -96,10 +102,10 @@ contract SuperPositions is ISuperPositions, ERC1155A { if (msg.sender != router) { uint256 len = superformIds.length; for (uint256 i; i < len; ++i) { - (, uint32 formBeaconId,) = DataLib.getSuperform(superformIds[i]); + (, uint32 formImplementationId,) = DataLib.getSuperform(superformIds[i]); uint8 registryId = superRegistry.getStateRegistryId(msg.sender); - if (uint32(registryId) != formBeaconId) { + if (uint32(registryId) != formImplementationId) { revert Error.NOT_MINTER(); } } @@ -138,18 +144,28 @@ contract SuperPositions is ISuperPositions, ERC1155A { ////////////////////////////////////////////////////////////// /// @inheritdoc ISuperPositions - function updateTxHistory(uint256 payloadId_, uint256 txInfo_) external override onlyRouter { - txHistory[payloadId_] = txInfo_; + function updateTxHistory( + uint256 payloadId_, + uint256 txInfo_, + address receiverAddressSP_ + ) + external + override + onlyRouter + { + txHistory[payloadId_] = TxHistory({ txInfo: txInfo_, receiverAddressSP: receiverAddressSP_ }); + + emit TxHistorySet(payloadId_, txInfo_, receiverAddressSP_); } /// @inheritdoc ISuperPositions - function mintSingle(address srcSender_, uint256 id_, uint256 amount_) external override onlyMinter(id_) { - _mint(srcSender_, msg.sender, id_, amount_, ""); + function mintSingle(address receiverAddressSP_, uint256 id_, uint256 amount_) external override onlyMinter(id_) { + _mint(receiverAddressSP_, msg.sender, id_, amount_, ""); } /// @inheritdoc ISuperPositions function mintBatch( - address srcSender_, + address receiverAddressSP_, uint256[] memory ids_, uint256[] memory amounts_ ) @@ -157,7 +173,8 @@ contract SuperPositions is ISuperPositions, ERC1155A { override onlyBatchMinter(ids_) { - _batchMint(srcSender_, msg.sender, ids_, amounts_, ""); + if (ids_.length != amounts_.length) revert Error.ARRAY_LENGTH_MISMATCH(); + _batchMint(receiverAddressSP_, msg.sender, ids_, amounts_, ""); } /// @inheritdoc ISuperPositions @@ -175,6 +192,7 @@ contract SuperPositions is ISuperPositions, ERC1155A { override onlyRouter { + if (ids_.length != amounts_.length) revert Error.ARRAY_LENGTH_MISMATCH(); _batchBurn(srcSender_, msg.sender, ids_, amounts_); } @@ -182,8 +200,7 @@ contract SuperPositions is ISuperPositions, ERC1155A { function stateMultiSync(AMBMessage memory data_) external override returns (uint64 srcChainId_) { /// @dev here we decode the txInfo and params from the data brought back from destination - (uint256 returnTxType, uint256 callbackType, uint8 multi,, address returnDataSrcSender,) = - data_.txInfo.decodeTxInfo(); + (uint256 returnTxType, uint256 callbackType, uint8 multi,,,) = data_.txInfo.decodeTxInfo(); if (callbackType != uint256(CallbackType.RETURN) && callbackType != uint256(CallbackType.FAIL)) { revert Error.INVALID_PAYLOAD_TYPE(); @@ -194,23 +211,20 @@ contract SuperPositions is ISuperPositions, ERC1155A { _validateStateSyncer(returnData.superformIds); - uint256 txInfo = txHistory[returnData.payloadId]; + uint256 txInfo = txHistory[returnData.payloadId].txInfo; /// @dev if txInfo is zero then the payloadId is invalid for ack if (txInfo == 0) { revert Error.TX_HISTORY_NOT_FOUND(); } - address srcSender; uint256 txType; /// @dev decode initial payload info stored on source chain in this contract - (txType,,,, srcSender, srcChainId_) = txInfo.decodeTxInfo(); + (txType,,,,, srcChainId_) = txInfo.decodeTxInfo(); /// @dev verify this is a not single vault mint if (multi != 1) revert Error.INVALID_PAYLOAD_TYPE(); - /// @dev compare final shares beneficiary to be the same (dst/src) - if (returnDataSrcSender != srcSender) revert Error.SRC_SENDER_MISMATCH(); /// @dev compare txType to be the same (dst/src) if (returnTxType != txType) revert Error.SRC_TX_TYPE_MISMATCH(); @@ -219,7 +233,13 @@ contract SuperPositions is ISuperPositions, ERC1155A { (txType == uint256(TransactionType.DEPOSIT) && callbackType == uint256(CallbackType.RETURN)) || (txType == uint256(TransactionType.WITHDRAW) && callbackType == uint256(CallbackType.FAIL)) ) { - _batchMint(srcSender, msg.sender, returnData.superformIds, returnData.amounts, ""); + _batchMint( + txHistory[returnData.payloadId].receiverAddressSP, + msg.sender, + returnData.superformIds, + returnData.amounts, + "" + ); } else { revert Error.INVALID_PAYLOAD_TYPE(); } @@ -231,8 +251,7 @@ contract SuperPositions is ISuperPositions, ERC1155A { function stateSync(AMBMessage memory data_) external override returns (uint64 srcChainId_) { /// @dev here we decode the txInfo and params from the data brought back from destination - (uint256 returnTxType, uint256 callbackType, uint8 multi,, address returnDataSrcSender,) = - data_.txInfo.decodeTxInfo(); + (uint256 returnTxType, uint256 callbackType, uint8 multi,,,) = data_.txInfo.decodeTxInfo(); if (callbackType != uint256(CallbackType.RETURN) && callbackType != uint256(CallbackType.FAIL)) { revert Error.INVALID_PAYLOAD_TYPE(); @@ -242,7 +261,7 @@ contract SuperPositions is ISuperPositions, ERC1155A { ReturnSingleData memory returnData = abi.decode(data_.params, (ReturnSingleData)); _validateStateSyncer(returnData.superformId); - uint256 txInfo = txHistory[returnData.payloadId]; + uint256 txInfo = txHistory[returnData.payloadId].txInfo; /// @dev if txInfo is zero then the payloadId is invalid for ack if (txInfo == 0) { @@ -250,16 +269,12 @@ contract SuperPositions is ISuperPositions, ERC1155A { } uint256 txType; - address srcSender; /// @dev decode initial payload info stored on source chain in this contract - (txType,,,, srcSender, srcChainId_) = txInfo.decodeTxInfo(); + (txType,,,,, srcChainId_) = txInfo.decodeTxInfo(); /// @dev this is a not multi vault mint if (multi != 0) revert Error.INVALID_PAYLOAD_TYPE(); - - /// @dev compare final shares beneficiary to be the same (dst/src) - if (returnDataSrcSender != srcSender) revert Error.SRC_SENDER_MISMATCH(); /// @dev compare txType to be the same (dst/src) if (returnTxType != txType) revert Error.SRC_TX_TYPE_MISMATCH(); @@ -268,7 +283,13 @@ contract SuperPositions is ISuperPositions, ERC1155A { (txType == uint256(TransactionType.DEPOSIT) && callbackType == uint256(CallbackType.RETURN)) || (txType == uint256(TransactionType.WITHDRAW) && callbackType == uint256(CallbackType.FAIL)) ) { - _mint(srcSender, msg.sender, returnData.superformId, returnData.amount, ""); + _mint( + txHistory[returnData.payloadId].receiverAddressSP, + msg.sender, + returnData.superformId, + returnData.amount, + "" + ); } else { revert Error.INVALID_PAYLOAD_TYPE(); } @@ -309,6 +330,7 @@ contract SuperPositions is ISuperPositions, ERC1155A { } /// @dev helps validate the state registry id for minting superform id + /// @dev is used in cross chain case (as superform is not available on the chain to validate caller) function _validateStateSyncer(uint256 superformId_) internal view { uint8 registryId = superRegistry.getStateRegistryId(msg.sender); _isValidStateSyncer(registryId, superformId_); @@ -322,21 +344,21 @@ contract SuperPositions is ISuperPositions, ERC1155A { } } - function _isValidStateSyncer(uint8 registryId_, uint256 superformId_) internal pure { - /// @dev Directly check if the registryId is 0 or doesn't match the allowed cases. - if (registryId_ == 0) { - revert Error.NOT_MINTER_STATE_REGISTRY_ROLE(); - } + function _isValidStateSyncer(uint8 registryId_, uint256 superformId_) internal view { + /// @dev registryId_ zero check is done in superRegistry.getStateRegistryId() + /// @dev If registryId is 1, meaning CoreStateRegistry, no further checks are necessary. /// @dev This is because CoreStateRegistry is the default minter for all kinds of forms /// @dev In case registryId is > 1, we need to check if the registryId matches the formImplementationId - if (registryId_ == 1) { + if (registryId_ == CORE_STATE_REGISTRY_ID) { return; } (, uint32 formImplementationId,) = DataLib.getSuperform(superformId_); + uint8 formRegistryId = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))) + .getFormStateRegistryId(formImplementationId); - if (uint32(registryId_) != formImplementationId) { + if (registryId_ != formRegistryId) { revert Error.NOT_MINTER_STATE_REGISTRY_ROLE(); } } @@ -347,9 +369,8 @@ contract SuperPositions is ISuperPositions, ERC1155A { } (address superform,,) = id.getSuperform(); - string memory name = - string(abi.encodePacked("SuperPositions AERC20 ", IBaseForm(superform).superformYieldTokenName())); - string memory symbol = string(abi.encodePacked("aERC20-", IBaseForm(superform).superformYieldTokenSymbol())); + string memory name = string.concat("SuperPositions AERC20 ", IBaseForm(superform).superformYieldTokenName()); + string memory symbol = string.concat("aERC20-", IBaseForm(superform).superformYieldTokenSymbol()); uint8 decimal = uint8(IBaseForm(superform).getVaultDecimals()); aErc20Token = address(new aERC20(name, symbol, decimal)); /// @dev broadcast and deploy to the other destination chains @@ -369,20 +390,31 @@ contract SuperPositions is ISuperPositions, ERC1155A { /// @dev interacts with broadcast state registry to broadcasting state changes to all connected remote chains /// @param message_ is the crosschain message to be sent. function _broadcast(bytes memory message_) internal { - (uint256 totalFees, bytes memory extraData) = + bytes memory registerTransmuterAMBData = IPaymentHelper(superRegistry.getAddress(keccak256("PAYMENT_HELPER"))).getRegisterTransmuterAMBData(); - (uint8 ambId, bytes memory broadcastParams) = abi.decode(extraData, (uint8, bytes)); + (uint8 ambId, bytes memory broadcastParams) = abi.decode(registerTransmuterAMBData, (uint8, bytes)); - if (msg.value < totalFees) { + /// @dev if the broadcastParams are wrong this will revert + (uint256 gasFee, bytes memory extraData) = abi.decode(broadcastParams, (uint256, bytes)); + + if (msg.value < gasFee) { revert Error.INVALID_BROADCAST_FEE(); } /// @dev ambIds are validated inside the broadcast state registry - /// @dev broadcastParams if wrong will revert in the amb implementation - IBroadcastRegistry(superRegistry.getAddress(keccak256("BROADCAST_REGISTRY"))).broadcastPayload{ - value: msg.value - }(msg.sender, ambId, message_, broadcastParams); + IBroadcastRegistry(superRegistry.getAddress(keccak256("BROADCAST_REGISTRY"))).broadcastPayload{ value: gasFee }( + msg.sender, ambId, gasFee, message_, extraData + ); + + if (msg.value > gasFee) { + /// @dev forwards the rest to msg.sender + (bool success,) = payable(msg.sender).call{ value: msg.value - gasFee }(""); + + if (!success) { + revert Error.FAILED_TO_SEND_NATIVE(); + } + } } /// @dev deploys new transmuter on broadcasting diff --git a/src/SuperformFactory.sol b/src/SuperformFactory.sol index d38e823ea..05a0b93d5 100644 --- a/src/SuperformFactory.sol +++ b/src/SuperformFactory.sol @@ -1,31 +1,30 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; +import { BaseForm } from "src/BaseForm.sol"; +import { BroadcastMessage } from "src/types/DataTypes.sol"; +import { IBaseForm } from "src/interfaces/IBaseForm.sol"; +import { IBroadcastRegistry } from "src/interfaces/IBroadcastRegistry.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { Error } from "src/libraries/Error.sol"; import { ERC165Checker } from "openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol"; import { IERC4626 } from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; -import { BaseForm } from "./BaseForm.sol"; -import { BroadcastMessage } from "./types/DataTypes.sol"; -import { ISuperformFactory } from "./interfaces/ISuperformFactory.sol"; -import { IBaseForm } from "./interfaces/IBaseForm.sol"; -import { IBroadcastRegistry } from "./interfaces/IBroadcastRegistry.sol"; -import { ISuperRBAC } from "./interfaces/ISuperRBAC.sol"; -import { ISuperRegistry } from "./interfaces/ISuperRegistry.sol"; -import { Error } from "./libraries/Error.sol"; -import { DataLib } from "./libraries/DataLib.sol"; import { Clones } from "openzeppelin-contracts/contracts/proxy/Clones.sol"; -/// @title Superforms Factory -/// @dev A secure, and easily queryable central point of access for all Superforms on any given chain, -/// @author Zeropoint Labs. +/// @title SuperformFactory +/// @dev Central point of read & write access for all Superforms on this chain +/// @author Zeropoint Labs contract SuperformFactory is ISuperformFactory { + using DataLib for uint256; using Clones for address; ////////////////////////////////////////////////////////////// // CONSTANTS // ////////////////////////////////////////////////////////////// - uint8 private constant NON_PAUSED = 1; - uint8 private constant PAUSED = 2; ISuperRegistry public immutable superRegistry; uint64 public immutable CHAIN_ID; @@ -36,9 +35,8 @@ contract SuperformFactory is ISuperformFactory { ////////////////////////////////////////////////////////////// uint256 public xChainPayloadCounter; - uint256 public superformCounter; - /// @dev all form beacon addresses + /// @dev all form implementation addresses address[] public formImplementations; /// @dev all superform ids @@ -46,7 +44,12 @@ contract SuperformFactory is ISuperformFactory { mapping(uint256 superformId => bool superformIdExists) public isSuperform; /// @notice If formImplementationId is 0, formImplementation is not part of the protocol - mapping(uint32 formImplementationId => address formBeaconAddress) public formImplementation; + mapping(uint32 formImplementationId => address formImplementationAddress) public formImplementation; + + /// @dev each form implementation address can correspond only to a single formImplementationId + mapping(address formImplementationAddress => uint32 formImplementationId) public formImplementationIds; + /// @dev this mapping is used only for crosschain cases and should be same across all the chains + mapping(uint32 formImplementationId => uint8 formRegistryId) public formStateRegistryId; mapping(uint32 formImplementationId => PauseStatus) public formImplementationPaused; @@ -89,6 +92,10 @@ contract SuperformFactory is ISuperformFactory { /// @param superRegistry_ the superform registry contract constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + if (block.chainid > type(uint64).max) { revert Error.BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); } @@ -116,6 +123,17 @@ contract SuperformFactory is ISuperformFactory { return formImplementation[formImplementationId_]; } + /// @inheritdoc ISuperformFactory + function getFormStateRegistryId(uint32 formImplementationId_) + external + view + override + returns (uint8 formStateRegistryId_) + { + formStateRegistryId_ = formStateRegistryId[formImplementationId_]; + if (formStateRegistryId_ == 0) revert Error.INVALID_FORM_REGISTRY_ID(); + } + /// @inheritdoc ISuperformFactory function isFormImplementationPaused(uint32 formImplementationId_) external view override returns (bool) { return formImplementationPaused[formImplementationId_] == PauseStatus.PAUSED; @@ -147,22 +165,6 @@ contract SuperformFactory is ISuperformFactory { } } - /// @inheritdoc ISuperformFactory - function getAllSuperforms() - external - view - override - returns (uint256[] memory superformIds_, address[] memory superforms_) - { - superformIds_ = superforms; - uint256 len = superformIds_.length; - superforms_ = new address[](len); - - for (uint256 i; i < len; ++i) { - (superforms_[i],,) = superformIds_[i].getSuperform(); - } - } - ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// @@ -170,15 +172,20 @@ contract SuperformFactory is ISuperformFactory { /// @inheritdoc ISuperformFactory function addFormImplementation( address formImplementation_, - uint32 formImplementationId_ + uint32 formImplementationId_, + uint8 formStateRegistryId_ ) public override onlyProtocolAdmin { if (formImplementation_ == address(0)) revert Error.ZERO_ADDRESS(); + if (!ERC165Checker.supportsERC165(formImplementation_)) revert Error.ERC165_UNSUPPORTED(); if (formImplementation[formImplementationId_] != address(0)) { + revert Error.FORM_IMPLEMENTATION_ALREADY_EXISTS(); + } + if (formImplementationIds[formImplementation_] != 0) { revert Error.FORM_IMPLEMENTATION_ID_ALREADY_EXISTS(); } if (!ERC165Checker.supportsInterface(formImplementation_, type(IBaseForm).interfaceId)) { @@ -187,10 +194,21 @@ contract SuperformFactory is ISuperformFactory { /// @dev save the newly added address in the mapping and array registry formImplementation[formImplementationId_] = formImplementation_; + formImplementationIds[formImplementation_] = formImplementationId_; + + /// @dev set CoreStateRegistry if the form implementation needs no special state registry + /// @dev if the form needs any special state registry, set this value to the id of the special state registry + /// and core state registry can be used by default. + /// @dev if this value is != 1, then the form supports two state registries (CoreStateRegistry + its special + /// state registry) + if (formStateRegistryId_ == 0) { + revert Error.INVALID_FORM_REGISTRY_ID(); + } + formStateRegistryId[formImplementationId_] = formStateRegistryId_; formImplementations.push(formImplementation_); - emit FormImplementationAdded(formImplementation_, formImplementationId_); + emit FormImplementationAdded(formImplementation_, formImplementationId_, formStateRegistryId_); } /// @inheritdoc ISuperformFactory @@ -207,15 +225,15 @@ contract SuperformFactory is ISuperformFactory { address tFormImplementation = formImplementation[formImplementationId_]; if (tFormImplementation == address(0)) revert Error.FORM_DOES_NOT_EXIST(); - /// @dev Same vault and beacon can be used only once to create superform + /// @dev Same vault and implementation can be used only once to create superform bytes32 vaultFormImplementationCombination = keccak256(abi.encode(tFormImplementation, vault_)); if (vaultFormImplCombinationToSuperforms[vaultFormImplementationCombination] != 0) { revert Error.VAULT_FORM_IMPLEMENTATION_COMBINATION_EXISTS(); } /// @dev instantiate the superform - superform_ = tFormImplementation.cloneDeterministic(keccak256(abi.encode(uint256(CHAIN_ID), superformCounter))); - ++superformCounter; + superform_ = tFormImplementation.cloneDeterministic( + keccak256(abi.encode(uint256(CHAIN_ID), formImplementationId_, vault_))); BaseForm(payable(superform_)).initialize(address(superRegistry), vault_, address(IERC4626(vault_).asset())); @@ -224,7 +242,7 @@ contract SuperformFactory is ISuperformFactory { vaultToSuperforms[vault_].push(superformId_); - /// @dev Mapping vaults to formImplementationId for use in Backend + /// @dev map vaults to formImplementationId vaultToFormImplementationId[vault_].push(formImplementationId_); vaultFormImplCombinationToSuperforms[vaultFormImplementationCombination] = superformId_; @@ -258,6 +276,8 @@ contract SuperformFactory is ISuperformFactory { ); _broadcast(abi.encode(factoryPayload), extraData_); + } else if (msg.value != 0) { + revert Error.MSG_VALUE_NOT_ZERO(); } emit FormImplementationPaused(formImplementationId_, status_); @@ -282,11 +302,26 @@ contract SuperformFactory is ISuperformFactory { function _broadcast(bytes memory message_, bytes memory extraData_) internal { (uint8 ambId, bytes memory broadcastParams) = abi.decode(extraData_, (uint8, bytes)); + /// @dev if the broadcastParams are wrong this will revert + (uint256 gasFee, bytes memory extraData) = abi.decode(broadcastParams, (uint256, bytes)); + + if (msg.value < gasFee) { + revert Error.INVALID_BROADCAST_FEE(); + } + /// @dev ambIds are validated inside the broadcast state registry - /// @dev broadcastParams if wrong will revert in the amb implementation - IBroadcastRegistry(superRegistry.getAddress(keccak256("BROADCAST_REGISTRY"))).broadcastPayload{ - value: msg.value - }(msg.sender, ambId, message_, broadcastParams); + IBroadcastRegistry(superRegistry.getAddress(keccak256("BROADCAST_REGISTRY"))).broadcastPayload{ value: gasFee }( + msg.sender, ambId, gasFee, message_, extraData + ); + + if (msg.value > gasFee) { + /// @dev forwards the rest to msg.sender + (bool success,) = payable(msg.sender).call{ value: msg.value - gasFee }(""); + + if (!success) { + revert Error.FAILED_TO_SEND_NATIVE(); + } + } } /// @dev synchronize paused status update message from remote chain diff --git a/src/SuperformRouter.sol b/src/SuperformRouter.sol index 52e0a1275..7f72d6d45 100644 --- a/src/SuperformRouter.sol +++ b/src/SuperformRouter.sol @@ -1,14 +1,21 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { BaseRouterImplementation } from "./BaseRouterImplementation.sol"; -import { BaseRouter } from "./BaseRouter.sol"; -import { IBaseRouter } from "./interfaces/IBaseRouter.sol"; -import "./types/DataTypes.sol"; +import { BaseRouterImplementation } from "src/BaseRouterImplementation.sol"; +import { BaseRouter } from "src/BaseRouter.sol"; +import { IBaseRouter } from "src/interfaces/IBaseRouter.sol"; +import { + SingleDirectSingleVaultStateReq, + SingleXChainSingleVaultStateReq, + SingleDirectMultiVaultStateReq, + SingleXChainMultiVaultStateReq, + MultiDstSingleVaultStateReq, + MultiDstMultiVaultStateReq +} from "src/types/DataTypes.sol"; /// @title SuperformRouter -/// @author Zeropoint Labs. -/// @dev SuperformRouter users funds and action information to a remote execution chain. +/// @dev Routes funds and action information to a remote execution chain +/// @author Zeropoint Labs contract SuperformRouter is BaseRouterImplementation { ////////////////////////////////////////////////////////////// // CONSTRUCTOR // @@ -75,12 +82,11 @@ contract SuperformRouter is BaseRouterImplementation { payable override(BaseRouter, IBaseRouter) { - uint64 srcChainId = CHAIN_ID; uint256 balanceBefore = address(this).balance - msg.value; uint256 len = req_.dstChainIds.length; for (uint256 i; i < len; ++i) { - if (srcChainId == req_.dstChainIds[i]) { + if (CHAIN_ID == req_.dstChainIds[i]) { _singleDirectSingleVaultDeposit(SingleDirectSingleVaultStateReq(req_.superformsData[i])); } else { _singleXChainSingleVaultDeposit( @@ -98,11 +104,11 @@ contract SuperformRouter is BaseRouterImplementation { payable override(BaseRouter, IBaseRouter) { - uint64 chainId = CHAIN_ID; uint256 balanceBefore = address(this).balance - msg.value; uint256 len = req_.dstChainIds.length; + for (uint256 i; i < len; ++i) { - if (chainId == req_.dstChainIds[i]) { + if (CHAIN_ID == req_.dstChainIds[i]) { _singleDirectMultiVaultDeposit(SingleDirectMultiVaultStateReq(req_.superformsData[i])); } else { _singleXChainMultiVaultDeposit( @@ -190,12 +196,11 @@ contract SuperformRouter is BaseRouterImplementation { payable override(BaseRouter, IBaseRouter) { - uint64 chainId = CHAIN_ID; uint256 balanceBefore = address(this).balance - msg.value; uint256 len = req_.dstChainIds.length; for (uint256 i; i < len; ++i) { - if (chainId == req_.dstChainIds[i]) { + if (CHAIN_ID == req_.dstChainIds[i]) { _singleDirectMultiVaultWithdraw(SingleDirectMultiVaultStateReq(req_.superformsData[i])); } else { _singleXChainMultiVaultWithdraw( @@ -206,4 +211,9 @@ contract SuperformRouter is BaseRouterImplementation { _forwardPayment(balanceBefore); } + + /// @inheritdoc IBaseRouter + function forwardDustToPaymaster(address token_) external override(BaseRouter, IBaseRouter) { + _forwardDustToPaymaster(token_); + } } diff --git a/src/crosschain-data/BaseStateRegistry.sol b/src/crosschain-data/BaseStateRegistry.sol index 31dfe8b98..6cae8cdb9 100644 --- a/src/crosschain-data/BaseStateRegistry.sol +++ b/src/crosschain-data/BaseStateRegistry.sol @@ -1,21 +1,22 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { Error } from "../libraries/Error.sol"; -import { IQuorumManager } from "../interfaces/IQuorumManager.sol"; -import { ISuperRegistry } from "../interfaces/ISuperRegistry.sol"; -import { IBaseStateRegistry } from "../interfaces/IBaseStateRegistry.sol"; -import { IAmbImplementation } from "../interfaces/IAmbImplementation.sol"; -import { PayloadState, AMBMessage, AMBExtraData } from "../types/DataTypes.sol"; -import { ProofLib } from "../libraries/ProofLib.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; +import { IAmbImplementation } from "src/interfaces/IAmbImplementation.sol"; +import { IQuorumManager } from "src/interfaces/IQuorumManager.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { Error } from "src/libraries/Error.sol"; +import { ProofLib } from "src/libraries/ProofLib.sol"; +import { PayloadState, AMBMessage, AMBExtraData } from "src/types/DataTypes.sol"; /// @title BaseStateRegistry -/// @author Zeropoint Labs -/// @dev contract module that allows inheriting contracts to implement crosschain messaging & processing mechanisms. -/// @dev This is a lightweight version that allows only dispatching and receiving crosschain +/// @dev Contract module that allows inheriting contracts to implement crosschain messaging & processing mechanisms. +/// @dev this is a lightweight version that allows only dispatching and receiving crosschain /// @dev payloads (messages). Inheriting children contracts have the flexibility to define their own processing /// mechanisms. +/// @author Zeropoint Labs abstract contract BaseStateRegistry is IBaseStateRegistry { + using ProofLib for AMBMessage; using ProofLib for bytes; @@ -51,12 +52,12 @@ abstract contract BaseStateRegistry is IBaseStateRegistry { // MODIFIERS // ////////////////////////////////////////////////////////////// - /// @dev sender varies based on functionality - /// @notice inheriting contracts should override this function (else not safe) + /// @dev inheriting contracts should override this function based on functionality modifier onlySender() virtual { _; } + /// @dev ensures that only added AMB implementations are accepted modifier onlyValidAmbImplementation() { if (!superRegistry.isValidAmbImpl(msg.sender)) { revert Error.NOT_AMB_IMPLEMENTATION(); @@ -175,7 +176,7 @@ abstract contract BaseStateRegistry is IBaseStateRegistry { } if (i - 1 != 0 && ambIds_[i] <= ambIds_[i - 1]) { - revert Error.DUPLICATE_PROOF_BRIDGE_ID(); + revert Error.INVALID_PROOF_BRIDGE_IDS(); } /// @dev proof is dispatched in the form of a payload diff --git a/src/crosschain-data/BroadcastRegistry.sol b/src/crosschain-data/BroadcastRegistry.sol index ee5de1b55..5d8ecccf6 100644 --- a/src/crosschain-data/BroadcastRegistry.sol +++ b/src/crosschain-data/BroadcastRegistry.sol @@ -1,26 +1,27 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { Error } from "src/libraries/Error.sol"; +import { IBroadcastAmbImplementation } from "src/interfaces/IBroadcastAmbImplementation.sol"; import { IBroadcastRegistry } from "src/interfaces/IBroadcastRegistry.sol"; import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { Error } from "src/libraries/Error.sol"; +import { ProofLib } from "src/libraries/ProofLib.sol"; import { BroadcastMessage, PayloadState } from "src/types/DataTypes.sol"; -import { IBroadcastAmbImplementation } from "src/interfaces/IBroadcastAmbImplementation.sol"; -import { ProofLib } from "../libraries/ProofLib.sol"; interface Target { function stateSyncBroadcast(bytes memory data_) external; } /// @title BroadcastRegistry +/// @dev Helps core contracts communicate with multiple dst chains through supported AMBs /// @author ZeroPoint Labs -/// @notice helps core contract communicate with multiple dst chains through supported AMBs contract BroadcastRegistry is IBroadcastRegistry { + using ProofLib for bytes; ////////////////////////////////////////////////////////////// - // CONSTANTS // + // CONSTANTS // ////////////////////////////////////////////////////////////// ISuperRegistry public immutable superRegistry; @@ -89,6 +90,7 @@ contract BroadcastRegistry is IBroadcastRegistry { function broadcastPayload( address srcSender_, uint8 ambId_, + uint256 gasFee_, bytes memory message_, bytes memory extraData_ ) @@ -97,9 +99,20 @@ contract BroadcastRegistry is IBroadcastRegistry { override onlySender { - (uint256 gasFee, bytes memory extraData) = abi.decode(extraData_, (uint256, bytes)); + if (msg.value < gasFee_) { + revert Error.INVALID_BROADCAST_FEE(); + } + + _broadcastPayload(srcSender_, ambId_, gasFee_, message_, extraData_); - _broadcastPayload(srcSender_, ambId_, gasFee, message_, extraData); + /// @dev refunds any overpaid msg.value + if (msg.value > gasFee_) { + (bool success,) = payable(srcSender_).call{ value: msg.value - gasFee_ }(""); + + if (!success) { + revert Error.FAILED_TO_SEND_NATIVE(); + } + } } /// @inheritdoc IBroadcastRegistry @@ -144,13 +157,13 @@ contract BroadcastRegistry is IBroadcastRegistry { function _broadcastPayload( address srcSender_, uint8 ambId_, - uint256 gasToPay_, + uint256 gasFee_, bytes memory message_, bytes memory extraData_ ) internal { - IBroadcastAmbImplementation(superRegistry.getAmbAddress(ambId_)).broadcastPayload{ value: gasToPay_ }( + IBroadcastAmbImplementation(superRegistry.getAmbAddress(ambId_)).broadcastPayload{ value: gasFee_ }( srcSender_, message_, extraData_ ); } diff --git a/src/crosschain-data/adapters/hyperlane/HyperlaneImplementation.sol b/src/crosschain-data/adapters/hyperlane/HyperlaneImplementation.sol index f739ee970..ce51f0743 100644 --- a/src/crosschain-data/adapters/hyperlane/HyperlaneImplementation.sol +++ b/src/crosschain-data/adapters/hyperlane/HyperlaneImplementation.sol @@ -1,22 +1,23 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; import { IAmbImplementation } from "src/interfaces/IAmbImplementation.sol"; -import { IMailbox } from "src/vendor/hyperlane/IMailbox.sol"; -import { StandardHookMetadata } from "src/vendor/hyperlane/StandardHookMetadata.sol"; -import { IMessageRecipient } from "src/vendor/hyperlane/IMessageRecipient.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; -import { IInterchainGasPaymaster } from "src/vendor/hyperlane/IInterchainGasPaymaster.sol"; -import { AMBMessage } from "src/types/DataTypes.sol"; -import { Error } from "src/libraries/Error.sol"; import { DataLib } from "src/libraries/DataLib.sol"; +import { Error } from "src/libraries/Error.sol"; +import { AMBMessage } from "src/types/DataTypes.sol"; +import { IMailbox } from "src/vendor/hyperlane/IMailbox.sol"; +import { IMessageRecipient } from "src/vendor/hyperlane/IMessageRecipient.sol"; +import { IInterchainGasPaymaster } from "src/vendor/hyperlane/IInterchainGasPaymaster.sol"; +import { StandardHookMetadata } from "src/vendor/hyperlane/StandardHookMetadata.sol"; /// @title HyperlaneImplementation +/// @dev Allows state registries to use Hyperlane v3 for crosschain communication /// @author Zeropoint Labs -/// @dev allows state registries to use hyperlane for crosschain communication contract HyperlaneImplementation is IAmbImplementation, IMessageRecipient { + using DataLib for uint256; ////////////////////////////////////////////////////////////// @@ -41,8 +42,8 @@ contract HyperlaneImplementation is IAmbImplementation, IMessageRecipient { // EVENTS // ////////////////////////////////////////////////////////////// - event MailboxAdded(address _newMailbox); - event GasPayMasterAdded(address _igp); + event MailboxAdded(address indexed _newMailbox); + event GasPayMasterAdded(address indexed _igp); ////////////////////////////////////////////////////////////// // MODIFIERS // @@ -85,6 +86,7 @@ contract HyperlaneImplementation is IAmbImplementation, IMessageRecipient { /// @param mailbox_ is the address of hyperlane mailbox /// @param igp_ is the address of hyperlane gas paymaster function setHyperlaneConfig(IMailbox mailbox_, IInterchainGasPaymaster igp_) external onlyProtocolAdmin { + if (address(mailbox_) == address(0) || address(igp_) == address(0)) revert Error.ZERO_ADDRESS(); mailbox = mailbox_; igp = igp_; @@ -137,6 +139,10 @@ contract HyperlaneImplementation is IAmbImplementation, IMessageRecipient { { uint32 domain = ambChainId[dstChainId_]; + if (domain == 0) { + revert Error.INVALID_CHAIN_ID(); + } + mailbox.dispatch{ value: msg.value }( domain, _castAddr(authorizedImpl[domain]), message_, _generateHookMetadata(extraData_, srcSender_) ); @@ -145,6 +151,13 @@ contract HyperlaneImplementation is IAmbImplementation, IMessageRecipient { /// @inheritdoc IAmbImplementation function retryPayload(bytes memory data_) external payable override { (bytes32 messageId, uint32 destinationDomain, uint256 gasAmount) = abi.decode(data_, (bytes32, uint32, uint256)); + uint256 fees = igp.quoteGasPayment(destinationDomain, gasAmount); + + if (msg.value < fees) { + revert Error.INVALID_RETRY_FEE(); + } + + /// refunds any excess msg.value to msg.sender igp.payForGas{ value: msg.value }(messageId, destinationDomain, gasAmount, msg.sender); } @@ -189,10 +202,12 @@ contract HyperlaneImplementation is IAmbImplementation, IMessageRecipient { } authorizedImpl[domain_] = authorizedImpl_; + + emit AuthorizedImplAdded(domain_, authorizedImpl_); } /// @inheritdoc IMessageRecipient - function handle(uint32 origin_, bytes32 sender_, bytes calldata body_) external override onlyMailbox { + function handle(uint32 origin_, bytes32 sender_, bytes calldata body_) external payable override onlyMailbox { /// @dev 1. validate caller /// @dev 2. validate src chain sender /// @dev 3. validate message uniqueness @@ -216,7 +231,13 @@ contract HyperlaneImplementation is IAmbImplementation, IMessageRecipient { (,,, uint8 registryId,,) = decoded.txInfo.decodeTxInfo(); IBaseStateRegistry targetRegistry = IBaseStateRegistry(superRegistry.getStateRegistry(registryId)); - targetRegistry.receivePayload(superChainId[origin_], body_); + uint64 origin = superChainId[origin_]; + + if (origin == 0) { + revert Error.INVALID_CHAIN_ID(); + } + + targetRegistry.receivePayload(origin, body_); } ////////////////////////////////////////////////////////////// diff --git a/src/crosschain-data/adapters/layerzero/LayerzeroImplementation.sol b/src/crosschain-data/adapters/layerzero/LayerzeroImplementation.sol index 8a69db1f3..bf4ff4037 100644 --- a/src/crosschain-data/adapters/layerzero/LayerzeroImplementation.sol +++ b/src/crosschain-data/adapters/layerzero/LayerzeroImplementation.sol @@ -1,21 +1,22 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; import { IAmbImplementation } from "src/interfaces/IAmbImplementation.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; -import { AMBMessage } from "src/types/DataTypes.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; import { Error } from "src/libraries/Error.sol"; +import { AMBMessage } from "src/types/DataTypes.sol"; +import { ILayerZeroEndpoint } from "src/vendor/layerzero/ILayerZeroEndpoint.sol"; import { ILayerZeroReceiver } from "src/vendor/layerzero/ILayerZeroReceiver.sol"; import { ILayerZeroUserApplicationConfig } from "src/vendor/layerzero/ILayerZeroUserApplicationConfig.sol"; -import { ILayerZeroEndpoint } from "src/vendor/layerzero/ILayerZeroEndpoint.sol"; -import { DataLib } from "src/libraries/DataLib.sol"; /// @title LayerzeroImplementation +/// @dev Allows state registries to use Layerzero for crosschain communication /// @author Zeropoint Labs -/// @dev allows state registries to use Layerzero for crosschain communication contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicationConfig, ILayerZeroReceiver { + using DataLib for uint256; ////////////////////////////////////////////////////////////// @@ -30,8 +31,6 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio ILayerZeroEndpoint public lzEndpoint; - /// @dev prevents layerzero relayer from replaying payload - mapping(uint16 => mapping(uint64 => bool)) public isValid; mapping(uint64 => uint16) public ambChainId; mapping(uint16 => uint64) public superChainId; mapping(uint16 => bytes) public trustedRemoteLookup; @@ -41,9 +40,9 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio // EVENTS // ////////////////////////////////////////////////////////////// - event EndpointUpdated(address oldEndpoint_, address newEndpoint_); - event MessageFailed(uint16 srcChainId_, bytes srcAddress_, uint64 nonce_, bytes payload_); - event SetTrustedRemote(uint16 srcChainId_, bytes srcAddress_); + event EndpointUpdated(address indexed oldEndpoint_, address indexed newEndpoint_); + event MessageFailed(uint16 indexed srcChainId_, bytes indexed srcAddress_, uint64 indexed nonce_, bytes payload_); + event SetTrustedRemote(uint16 indexed srcChainId_, bytes indexed srcAddress_); ////////////////////////////////////////////////////////////// // MODIFIERS // @@ -102,7 +101,10 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio } } - /// @dev returns the configuration of this contract + /// @dev gets the configuration of the current LayerZero implementation supported + /// @param version_ is the messaging library version + /// @param chainId_ is the layerzero chainId for the pending config change + /// @param configType_ is the type of configuration function getConfig( uint16 version_, uint16 chainId_, @@ -116,7 +118,7 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio return lzEndpoint.getConfig(version_, chainId_, address(this), configType_); } - /// @dev allows protocol admin to configure UA on layerzero + /// @inheritdoc ILayerZeroUserApplicationConfig function setConfig( uint16 version_, uint16 chainId_, @@ -130,22 +132,24 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio lzEndpoint.setConfig(version_, chainId_, configType_, config_); } - /// @dev allows protocol admin to configure send version on layerzero + /// @inheritdoc ILayerZeroUserApplicationConfig function setSendVersion(uint16 version_) external override onlyProtocolAdmin { lzEndpoint.setSendVersion(version_); } - /// @dev allows protocol admin to configure receive version on layerzero + /// @inheritdoc ILayerZeroUserApplicationConfig function setReceiveVersion(uint16 version_) external override onlyProtocolAdmin { lzEndpoint.setReceiveVersion(version_); } - /// @dev allows protocol admin to unblock queue of messages if needed + /// @inheritdoc ILayerZeroUserApplicationConfig function forceResumeReceive(uint16 srcChainId_, bytes calldata srcAddress_) external override onlyEmergencyAdmin { lzEndpoint.forceResumeReceive(srcChainId_, srcAddress_); } /// @dev allows protocol admin to set contract which can receive messages + /// @param srcChainId_ is the layerzero source chain id + /// @param srcAddress_ is the address to set as the trusted remote on the source chain function setTrustedRemote(uint16 srcChainId_, bytes calldata srcAddress_) external onlyProtocolAdmin { trustedRemoteLookup[srcChainId_] = srcAddress_; emit SetTrustedRemote(srcChainId_, srcAddress_); @@ -155,7 +159,13 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio // EXTERNAL VIEW FUNCTIONS // ////////////////////////////////////////////////////////////// + /// @dev checks if another contract on another chain is a trusted remote + /// @param srcChainId_ is the layerzero source chain id + /// @param srcAddress_ is the address to check function isTrustedRemote(uint16 srcChainId_, bytes calldata srcAddress_) external view returns (bool) { + if (srcChainId_ == 0) { + revert Error.INVALID_CHAIN_ID(); + } return keccak256(trustedRemoteLookup[srcChainId_]) == keccak256(srcAddress_); } @@ -195,7 +205,12 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio override onlyValidStateRegistry { - _lzSend(ambChainId[dstChainId_], message_, payable(srcSender_), address(0x0), extraData_, msg.value); + uint16 domain = ambChainId[dstChainId_]; + if (domain == 0) { + revert Error.INVALID_CHAIN_ID(); + } + + _lzSend(domain, message_, payable(srcSender_), address(0x0), extraData_, msg.value); } /// @inheritdoc IAmbImplementation @@ -242,12 +257,6 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio override onlyLzEndpoint { - if (isValid[srcChainId_][nonce_]) { - revert Error.DUPLICATE_PAYLOAD(); - } - - isValid[srcChainId_][nonce_] = true; - bytes memory trustedRemote = trustedRemoteLookup[srcChainId_]; if ( @@ -262,6 +271,10 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio _blockingLzReceive(srcChainId_, srcAddress_, nonce_, payload_); } + /// @dev receive failed messages on this Endpoint destination + /// @param srcChainId_ is the layerzero source chain id + /// @param srcAddress_ is the address on the source chain + /// @param payload_ is the payload to store function nonblockingLzReceive(uint16 srcChainId_, bytes memory srcAddress_, bytes memory payload_) public { // only internal transaction if (msg.sender != address(this)) { @@ -271,6 +284,11 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio _nonblockingLzReceive(srcChainId_, srcAddress_, payload_); } + /// @dev retry failed messages on this Endpoint destination + /// @param srcChainId_ is the layerzero source chain id + /// @param srcAddress_ is the address on the source chain + /// @param nonce_ is the sequential location of the stuck payload + /// @param payload_ is the payload to retry function retryMessage( uint16 srcChainId_, bytes memory srcAddress_, @@ -311,13 +329,19 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio IBaseStateRegistry targetRegistry = IBaseStateRegistry(superRegistry.getStateRegistry(registryId)); - targetRegistry.receivePayload(superChainId[_srcChainId], _payload); + uint64 srcChainId = superChainId[_srcChainId]; + + if (srcChainId == 0) { + revert Error.INVALID_CHAIN_ID(); + } + + targetRegistry.receivePayload(srcChainId, _payload); } function _lzSend( uint16 dstChainId_, bytes memory payload_, - address payable refundAddress_, + address payable receiverAddress_, address zroPaymentAddress_, bytes memory adapterParams_, uint256 msgValue_ @@ -326,11 +350,11 @@ contract LayerzeroImplementation is IAmbImplementation, ILayerZeroUserApplicatio { bytes memory trustedRemote = trustedRemoteLookup[dstChainId_]; if (trustedRemote.length == 0) { - revert Error.INVALID_SRC_CHAIN_ID(); + revert Error.INVALID_CHAIN_ID(); } lzEndpoint.send{ value: msgValue_ }( - dstChainId_, trustedRemote, payload_, refundAddress_, zroPaymentAddress_, adapterParams_ + dstChainId_, trustedRemote, payload_, receiverAddress_, zroPaymentAddress_, adapterParams_ ); } diff --git a/src/crosschain-data/adapters/wormhole/automatic-relayer/WormholeARImplementation.sol b/src/crosschain-data/adapters/wormhole/automatic-relayer/WormholeARImplementation.sol index 90cf1da5c..2c4d46354 100644 --- a/src/crosschain-data/adapters/wormhole/automatic-relayer/WormholeARImplementation.sol +++ b/src/crosschain-data/adapters/wormhole/automatic-relayer/WormholeARImplementation.sol @@ -1,28 +1,27 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; import { IAmbImplementation } from "src/interfaces/IAmbImplementation.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; -import { AMBMessage } from "src/types/DataTypes.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; import { Error } from "src/libraries/Error.sol"; +import { AMBMessage } from "src/types/DataTypes.sol"; import { IWormholeRelayer, VaaKey } from "src/vendor/wormhole/IWormholeRelayer.sol"; import { IWormholeReceiver } from "src/vendor/wormhole/IWormholeReceiver.sol"; -import { DataLib } from "src/libraries/DataLib.sol"; import "src/vendor/wormhole/Utils.sol"; /// @title WormholeImplementation +/// @dev Allows state registries to use Wormhole AR's for crosschain communication /// @author Zeropoint Labs -/// @notice allows state registries to use wormhole for crosschain communication -/// @dev uses automatic relayers of wormhole for 1:1 messaging contract WormholeARImplementation is IAmbImplementation, IWormholeReceiver { + using DataLib for uint256; ////////////////////////////////////////////////////////////// // CONSTANTS // ////////////////////////////////////////////////////////////// - ISuperRegistry public immutable superRegistry; ////////////////////////////////////////////////////////////// @@ -30,6 +29,7 @@ contract WormholeARImplementation is IAmbImplementation, IWormholeReceiver { ////////////////////////////////////////////////////////////// IWormholeRelayer public relayer; + uint16 public refundChainId; mapping(uint64 => uint16) public ambChainId; mapping(uint16 => uint64) public superChainId; @@ -41,7 +41,10 @@ contract WormholeARImplementation is IAmbImplementation, IWormholeReceiver { ////////////////////////////////////////////////////////////// /// @dev emitted when wormhole relayer is set - event WormholeRelayerSet(address wormholeRelayer); + event WormholeRelayerSet(address indexed wormholeRelayer); + + /// @dev emitted when refund chain id is set + event WormholeRefundChainIdSet(uint16 indexed refundChainId); ////////////////////////////////////////////////////////////// // MODIFIERS // @@ -128,7 +131,7 @@ contract WormholeARImplementation is IAmbImplementation, IWormholeReceiver { /// @inheritdoc IAmbImplementation function dispatchPayload( - address, /*srcSender_*/ + address srcSender_, uint64 dstChainId_, bytes memory message_, bytes memory extraData_ @@ -139,12 +142,16 @@ contract WormholeARImplementation is IAmbImplementation, IWormholeReceiver { override onlyValidStateRegistry { - uint16 dstChainId = ambChainId[dstChainId_]; + if (refundChainId == 0) { + revert Error.REFUND_CHAIN_ID_NOT_SET(); + } + uint16 dstChainId = ambChainId[dstChainId_]; (uint256 dstNativeAirdrop, uint256 dstGasLimit) = abi.decode(extraData_, (uint256, uint256)); + /// @dev refunds any excess on this chain back to srcSender_ relayer.sendPayloadToEvm{ value: msg.value }( - dstChainId, authorizedImpl[dstChainId], message_, dstNativeAirdrop, dstGasLimit + dstChainId, authorizedImpl[dstChainId], message_, dstNativeAirdrop, dstGasLimit, refundChainId, srcSender_ ); } @@ -158,13 +165,29 @@ contract WormholeARImplementation is IAmbImplementation, IWormholeReceiver { address newDeliveryProviderAddress ) = abi.decode(data_, (VaaKey, uint16, uint256, uint256, address)); + (uint256 fees,) = relayer.quoteEVMDeliveryPrice(targetChain, 0, newGasLimit); + + if (msg.value < fees) { + revert Error.INVALID_RETRY_FEE(); + } + if (newDeliveryProviderAddress == address(0)) { revert Error.ZERO_ADDRESS(); } - relayer.resendToEvm{ value: msg.value }( + relayer.resendToEvm{ value: fees }( deliveryVaaKey, targetChain, newReceiverValue, newGasLimit, newDeliveryProviderAddress ); + + /// refunds excess msg.value to msg.sender + uint256 excessPaid = msg.value - fees; + if (excessPaid > 0) { + (bool success,) = payable(msg.sender).call{ value: excessPaid }(""); + + if (!success) { + revert Error.FAILED_TO_SEND_NATIVE(); + } + } } /// @inheritdoc IWormholeReceiver @@ -198,7 +221,13 @@ contract WormholeARImplementation is IAmbImplementation, IWormholeReceiver { (,,, uint8 registryId,,) = decoded.txInfo.decodeTxInfo(); IBaseStateRegistry targetRegistry = IBaseStateRegistry(superRegistry.getStateRegistry(registryId)); - targetRegistry.receivePayload(superChainId[sourceChain_], payload_); + uint64 sourceChain = superChainId[sourceChain_]; + + if (sourceChain == 0) { + revert Error.INVALID_CHAIN_ID(); + } + + targetRegistry.receivePayload(sourceChain, payload_); } /// @dev allows protocol admin to add new chain ids in future @@ -228,6 +257,15 @@ contract WormholeARImplementation is IAmbImplementation, IWormholeReceiver { emit ChainAdded(superChainId_); } + /// @dev allows protocol admin to set wormhole chain id for refunds + /// @param refundChainId_ is the wormhole chain id of current chain + function setRefundChainId(uint16 refundChainId_) external onlyProtocolAdmin { + if (refundChainId_ == 0) revert Error.INVALID_CHAIN_ID(); + refundChainId = refundChainId_; + + emit WormholeRefundChainIdSet(refundChainId_); + } + /// @dev allows protocol admin to set receiver implementation on a new chain id /// @param chainId_ is the identifier of the destination chain within wormhole /// @param authorizedImpl_ is the implementation of the wormhole message bridge on the specified destination diff --git a/src/crosschain-data/adapters/wormhole/specialized-relayer/WormholeSRImplementation.sol b/src/crosschain-data/adapters/wormhole/specialized-relayer/WormholeSRImplementation.sol index b2203e884..c83927429 100644 --- a/src/crosschain-data/adapters/wormhole/specialized-relayer/WormholeSRImplementation.sol +++ b/src/crosschain-data/adapters/wormhole/specialized-relayer/WormholeSRImplementation.sol @@ -1,20 +1,20 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { IBroadcastRegistry } from "src/interfaces/IBroadcastRegistry.sol"; import { IBroadcastAmbImplementation } from "src/interfaces/IBroadcastAmbImplementation.sol"; +import { IBroadcastRegistry } from "src/interfaces/IBroadcastRegistry.sol"; import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; import { Error } from "src/libraries/Error.sol"; import { IWormhole } from "src/vendor/wormhole/IWormhole.sol"; -import { DataLib } from "src/libraries/DataLib.sol"; import "src/vendor/wormhole/Utils.sol"; /// @title WormholeImplementation +/// @dev Allows state registries to use Wormhole SR's for broadcasting /// @author Zeropoint Labs -/// @notice allows broadcast state registry contracts to send messages to multiple chains -/// @dev uses multicast of wormhole for broadcasting contract WormholeSRImplementation is IBroadcastAmbImplementation { + using DataLib for uint256; ////////////////////////////////////////////////////////////// @@ -43,11 +43,11 @@ contract WormholeSRImplementation is IBroadcastAmbImplementation { ////////////////////////////////////////////////////////////// /// @dev emitted when wormhole core is set - event WormholeCoreSet(address wormholeCore); + event WormholeCoreSet(address indexed wormholeCore); /// @dev emitted when wormhole relyaer is set - event WormholeRelayerSet(address wormholeRelayer); + event WormholeRelayerSet(address indexed wormholeRelayer); /// @dev emitted when broadcast finality is set - event BroadcastFinalitySet(uint8 finality); + event BroadcastFinalitySet(uint8 indexed finality); ////////////////////////////////////////////////////////////// // MODIFIERS // @@ -178,6 +178,8 @@ contract WormholeSRImplementation is IBroadcastAmbImplementation { } } + /// @dev function to receive broadcasted messages via Wormhole SR's + /// @param encodedMessage_ is the message to receive function receiveMessage(bytes memory encodedMessage_) public onlyWormholeVAARelayer { /// @dev 1. validate caller /// @dev 2. validate not broadcasted to emitter chain @@ -191,7 +193,7 @@ contract WormholeSRImplementation is IBroadcastAmbImplementation { } if (wormholeMessage.emitterChainId == wormhole.chainId()) { - revert Error.INVALID_SRC_CHAIN_ID(); + revert Error.INVALID_CHAIN_ID(); } if (fromWormholeFormat(wormholeMessage.emitterAddress) != authorizedImpl[wormholeMessage.emitterChainId]) { @@ -205,8 +207,14 @@ contract WormholeSRImplementation is IBroadcastAmbImplementation { processedMessages[wormholeMessage.hash] = true; /// @dev decoding payload + uint64 emitterChainId = superChainId[wormholeMessage.emitterChainId]; + + if (emitterChainId == 0) { + revert Error.INVALID_CHAIN_ID(); + } + IBroadcastRegistry(superRegistry.getStateRegistry(BROADCAST_REGISTRY_ID)).receiveBroadcastPayload( - superChainId[wormholeMessage.emitterChainId], wormholeMessage.payload + emitterChainId, wormholeMessage.payload ); } diff --git a/src/crosschain-data/extensions/CoreStateRegistry.sol b/src/crosschain-data/extensions/CoreStateRegistry.sol index 8b5e33daa..04761fcb9 100644 --- a/src/crosschain-data/extensions/CoreStateRegistry.sol +++ b/src/crosschain-data/extensions/CoreStateRegistry.sol @@ -1,23 +1,21 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; -import { BaseStateRegistry } from "../BaseStateRegistry.sol"; -import { ISuperRBAC } from "../../interfaces/ISuperRBAC.sol"; -import { ISuperPositions } from "../../interfaces/ISuperPositions.sol"; -import { ISuperRegistry } from "../../interfaces/ISuperRegistry.sol"; -import { IPaymentHelper } from "../../interfaces/IPaymentHelper.sol"; -import { IBaseForm } from "../../interfaces/IBaseForm.sol"; -import { IDstSwapper } from "../../interfaces/IDstSwapper.sol"; -import { ISuperformFactory } from "../../interfaces/ISuperformFactory.sol"; -import { ICoreStateRegistry } from "../../interfaces/ICoreStateRegistry.sol"; -import { IBridgeValidator } from "../../interfaces/IBridgeValidator.sol"; -import { DataLib } from "../../libraries/DataLib.sol"; -import { ProofLib } from "../../libraries/ProofLib.sol"; -import { ArrayCastLib } from "../../libraries/ArrayCastLib.sol"; -import { PayloadUpdaterLib } from "../../libraries/PayloadUpdaterLib.sol"; -import { Error } from "../../libraries/Error.sol"; +import { BaseStateRegistry } from "src/crosschain-data/BaseStateRegistry.sol"; +import { ICoreStateRegistry } from "src/interfaces/ICoreStateRegistry.sol"; +import { IBaseForm } from "src/interfaces/IBaseForm.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { IDstSwapper } from "src/interfaces/IDstSwapper.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; +import { ISuperPositions } from "src/interfaces/ISuperPositions.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { IPaymentHelper } from "src/interfaces/IPaymentHelper.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { ProofLib } from "src/libraries/ProofLib.sol"; +import { ArrayCastLib } from "src/libraries/ArrayCastLib.sol"; +import { PayloadUpdaterLib } from "src/libraries/PayloadUpdaterLib.sol"; +import { Error } from "src/libraries/Error.sol"; import { PayloadState, AMBMessage, @@ -28,11 +26,13 @@ import { ReturnSingleData, InitSingleVaultData, LiqRequest -} from "../../types/DataTypes.sol"; +} from "src/types/DataTypes.sol"; +import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; + /// @title CoreStateRegistry +/// @dev Enables communication between Superform core contracts deployed on all supported networks /// @author Zeropoint Labs -/// @dev enables communication between Superform Core Contracts deployed on all supported networks - contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { using SafeERC20 for IERC20; using DataLib for uint256; @@ -43,14 +43,14 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { ////////////////////////////////////////////////////////////// /// @dev just stores the superformIds that failed in a specific payload id - mapping(uint256 payloadId => FailedDeposit) internal failedDeposits; + mapping(uint256 payloadId => FailedDeposit) failedDeposits; ////////////////////////////////////////////////////////////// // MODIFIERS // ////////////////////////////////////////////////////////////// modifier onlySender() override { - if (msg.sender != superRegistry.getAddress(keccak256("SUPERFORM_ROUTER"))) revert Error.NOT_SUPERFORM_ROUTER(); + if (msg.sender != _getAddress(keccak256("SUPERFORM_ROUTER"))) revert Error.NOT_SUPERFORM_ROUTER(); _; } @@ -69,10 +69,22 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { external view override - returns (uint256[] memory superformIds, uint256[] memory amounts) + returns (uint256[] memory superformIds, uint256[] memory amounts, uint256 lastProposedTime) { - superformIds = failedDeposits[payloadId_].superformIds; - amounts = failedDeposits[payloadId_].amounts; + FailedDeposit storage failedDeposit = failedDeposits[payloadId_]; + superformIds = failedDeposit.superformIds; + amounts = failedDeposit.amounts; + lastProposedTime = failedDeposit.lastProposedTimestamp; + } + + /// @inheritdoc ICoreStateRegistry + function validateSlippage(uint256 finalAmount_, uint256 amount_, uint256 maxSlippage_) public view returns (bool) { + // only internal transaction + if (msg.sender != address(this)) { + revert Error.INVALID_INTERNAL_CALL(); + } + + return PayloadUpdaterLib.validateSlippage(finalAmount_, amount_, maxSlippage_); } ////////////////////////////////////////////////////////////// @@ -80,7 +92,15 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { ////////////////////////////////////////////////////////////// /// @inheritdoc ICoreStateRegistry - function updateDepositPayload(uint256 payloadId_, uint256[] calldata finalAmounts_) external virtual override { + function updateDepositPayload( + uint256 payloadId_, + address[] calldata finalTokens_, + uint256[] calldata finalAmounts_ + ) + external + virtual + override + { /// @dev validates the caller _onlyAllowedCaller(keccak256("CORE_STATE_REGISTRY_UPDATER_ROLE")); @@ -96,9 +116,11 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { PayloadState finalState; if (isMulti != 0) { - (prevPayloadBody, finalState) = _updateMultiDeposit(payloadId_, prevPayloadBody, finalAmounts_); + (prevPayloadBody, finalState) = + _updateMultiDeposit(payloadId_, prevPayloadBody, finalAmounts_, finalTokens_); } else { - (prevPayloadBody, finalState) = _updateSingleDeposit(payloadId_, prevPayloadBody, finalAmounts_[0]); + (prevPayloadBody, finalState) = + _updateSingleDeposit(payloadId_, prevPayloadBody, finalAmounts_[0], finalTokens_[0]); } /// @dev updates the payload proof @@ -127,7 +149,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { , uint8 isMulti, , - address srcSender, + , uint64 srcChainId ) = _getPayload(payloadId_); @@ -135,7 +157,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { PayloadUpdaterLib.validatePayloadUpdate( prevPayloadHeader, uint8(TransactionType.WITHDRAW), payloadTracking[payloadId_], isMulti ); - prevPayloadBody = _updateWithdrawPayload(prevPayloadBody, srcSender, srcChainId, txData_, isMulti); + prevPayloadBody = _updateWithdrawPayload(prevPayloadBody, srcChainId, txData_, isMulti); /// @dev updates the payload proof _updatePayload(payloadId_, prevPayloadProof, prevPayloadBody, prevPayloadHeader, PayloadState.UPDATED); @@ -175,9 +197,8 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { /// @dev mint superPositions for successful deposits or remint for failed withdraws if (callbackType == uint256(CallbackType.RETURN) || callbackType == uint256(CallbackType.FAIL)) { - isMulti == 1 - ? ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).stateMultiSync(message_) - : ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))).stateSync(message_); + ISuperPositions superPositions = ISuperPositions(_getAddress(keccak256("SUPER_POSITIONS"))); + isMulti == 1 ? superPositions.stateMultiSync(message_) : superPositions.stateSync(message_); } else if (callbackType == uint8(CallbackType.INIT)) { /// @dev for initial payload processing bytes memory returnMessage; @@ -214,10 +235,8 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { FailedDeposit storage failedDeposits_ = failedDeposits[payloadId_]; - if ( - failedDeposits_.superformIds.length == 0 || proposedAmounts_.length == 0 - || failedDeposits_.superformIds.length != proposedAmounts_.length - ) { + if (failedDeposits_.superformIds.length == 0 || failedDeposits_.superformIds.length != proposedAmounts_.length) + { revert Error.INVALID_RESCUE_DATA(); } @@ -225,20 +244,20 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { revert Error.RESCUE_ALREADY_PROPOSED(); } - /// @dev note: should set this value to dstSwapper.failedSwap().amount for interim rescue + /// @dev should set this value to dstSwapper.failedSwap().amount for interim rescue failedDeposits[payloadId_].amounts = proposedAmounts_; failedDeposits[payloadId_].lastProposedTimestamp = block.timestamp; (,, uint8 multi,,,) = DataLib.decodeTxInfo(payloadHeader[payloadId_]); - address refundAddress; + address receiverAddress; if (multi == 1) { - refundAddress = abi.decode(payloadBody[payloadId_], (InitMultiVaultData)).receiverAddress; + receiverAddress = abi.decode(payloadBody[payloadId_], (InitMultiVaultData)).receiverAddress; } else { - refundAddress = abi.decode(payloadBody[payloadId_], (InitSingleVaultData)).receiverAddress; + receiverAddress = abi.decode(payloadBody[payloadId_], (InitSingleVaultData)).receiverAddress; } - failedDeposits[payloadId_].refundAddress = refundAddress; + failedDeposits[payloadId_].receiverAddress = receiverAddress; emit RescueProposed(payloadId_, failedDeposits_.superformIds, proposedAmounts_, block.timestamp); } @@ -252,7 +271,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { /// @dev the msg sender should be the refund address (or) the disputer if ( !( - msg.sender == failedDeposits_.refundAddress + msg.sender == failedDeposits_.receiverAddress || _hasRole(keccak256("CORE_STATE_REGISTRY_DISPUTER_ROLE"), msg.sender) ) ) { @@ -285,25 +304,24 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { /// @dev the timelock is elapsed if ( failedDeposits_.lastProposedTimestamp == 0 - || block.timestamp < failedDeposits_.lastProposedTimestamp + _getDelay() + || block.timestamp <= failedDeposits_.lastProposedTimestamp + _getDelay() ) { revert Error.RESCUE_LOCKED(); } /// @dev set to zero to prevent re-entrancy failedDeposits_.lastProposedTimestamp = 0; - IDstSwapper dstSwapper = IDstSwapper(_getAddress(keccak256("DST_SWAPPER"))); uint256 len = failedDeposits_.amounts.length; for (uint256 i; i < len; ++i) { /// @dev refunds the amount to user specified refund address if (failedDeposits_.settleFromDstSwapper[i]) { - dstSwapper.processFailedTx( - failedDeposits_.refundAddress, failedDeposits_.settlementToken[i], failedDeposits_.amounts[i] + IDstSwapper(_getAddress(keccak256("DST_SWAPPER"))).processFailedTx( + failedDeposits_.receiverAddress, failedDeposits_.settlementToken[i], failedDeposits_.amounts[i] ); } else { IERC20(failedDeposits_.settlementToken[i]).safeTransfer( - failedDeposits_.refundAddress, failedDeposits_.amounts[i] + failedDeposits_.receiverAddress, failedDeposits_.amounts[i] ); } } @@ -316,6 +334,16 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { // INTERNAL FUNCTIONS // ////////////////////////////////////////////////////////////// + /// @dev returns vault asset from superform + function _getVaultAsset(address superform_) internal view returns (address) { + return IBaseForm(superform_).getVaultAsset(); + } + + /// @dev returns if superform is valid + function _isSuperform(uint256 superformId_) internal view returns (bool) { + return ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))).isSuperform(superformId_); + } + /// @dev returns a superformAddress function _getSuperform(uint256 superformId_) internal pure returns (address superform) { (superform,,) = superformId_.getSuperform(); @@ -386,16 +414,15 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { function _updateMultiDeposit( uint256 payloadId_, bytes memory prevPayloadBody_, - uint256[] calldata finalAmounts_ + uint256[] calldata finalAmounts_, + address[] calldata finalToken_ ) internal returns (bytes memory newPayloadBody_, PayloadState finalState_) { InitMultiVaultData memory multiVaultData = abi.decode(prevPayloadBody_, (InitMultiVaultData)); - IDstSwapper dstSwapper = IDstSwapper(_getAddress(keccak256("DST_SWAPPER"))); uint256 arrLen = finalAmounts_.length; - /// @dev compare number of vaults to update with provided finalAmounts length if (multiVaultData.amounts.length != arrLen) { revert Error.DIFFERENT_PAYLOAD_UPDATE_AMOUNTS_LENGTH(); @@ -407,9 +434,13 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { revert Error.ZERO_AMOUNT(); } + if (_getVaultAsset(_getSuperform(multiVaultData.superformIds[i])) != finalToken_[i]) { + revert Error.INVALID_UPDATE_FINAL_TOKEN(); + } + /// @dev observe not consuming the second return value (multiVaultData.amounts[i],, validLen) = _updateAmount( - dstSwapper, + IDstSwapper(_getAddress(keccak256("DST_SWAPPER"))), multiVaultData.hasDstSwaps[i], payloadId_, i, @@ -429,24 +460,31 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { if (validLen != 0) { uint256[] memory finalSuperformIds = new uint256[](validLen); uint256[] memory finalAmounts = new uint256[](validLen); + uint256[] memory outputAmounts = new uint256[](validLen); uint256[] memory maxSlippage = new uint256[](validLen); bool[] memory hasDstSwaps = new bool[](validLen); + bool[] memory retain4626s = new bool[](validLen); uint256 currLen; for (uint256 i; i < arrLen; ++i) { if (multiVaultData.amounts[i] != 0) { finalSuperformIds[currLen] = multiVaultData.superformIds[i]; finalAmounts[currLen] = multiVaultData.amounts[i]; + outputAmounts[currLen] = multiVaultData.outputAmounts[i]; maxSlippage[currLen] = multiVaultData.maxSlippages[i]; hasDstSwaps[currLen] = multiVaultData.hasDstSwaps[i]; + retain4626s[currLen] = multiVaultData.retain4626s[i]; + ++currLen; } } - multiVaultData.amounts = finalAmounts; multiVaultData.superformIds = finalSuperformIds; + multiVaultData.amounts = finalAmounts; + multiVaultData.outputAmounts = outputAmounts; multiVaultData.maxSlippages = maxSlippage; multiVaultData.hasDstSwaps = hasDstSwaps; + multiVaultData.retain4626s = retain4626s; finalState_ = PayloadState.UPDATED; } else { finalState_ = PayloadState.PROCESSED; @@ -458,21 +496,25 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { function _updateSingleDeposit( uint256 payloadId_, bytes memory prevPayloadBody_, - uint256 finalAmount_ + uint256 finalAmount_, + address finalToken_ ) internal returns (bytes memory newPayloadBody_, PayloadState finalState_) { InitSingleVaultData memory singleVaultData = abi.decode(prevPayloadBody_, (InitSingleVaultData)); - IDstSwapper dstSwapper = IDstSwapper(_getAddress(keccak256("DST_SWAPPER"))); if (finalAmount_ == 0) { revert Error.ZERO_AMOUNT(); } + if (_getVaultAsset(_getSuperform(singleVaultData.superformId)) != finalToken_) { + revert Error.INVALID_UPDATE_FINAL_TOKEN(); + } + /// @dev observe not consuming the third return value (singleVaultData.amount, finalState_,) = _updateAmount( - dstSwapper, + IDstSwapper(_getAddress(keccak256("DST_SWAPPER"))), singleVaultData.hasDstSwap, payloadId_, 0, @@ -487,15 +529,6 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { newPayloadBody_ = abi.encode(singleVaultData); } - function validateSlippage(uint256 finalAmount_, uint256 amount_, uint256 maxSlippage_) public view returns (bool) { - // only internal transaction - if (msg.sender != address(this)) { - revert Error.INVALID_INTERNAL_CALL(); - } - - return PayloadUpdaterLib.validateSlippage(finalAmount_, amount_, maxSlippage_); - } - function _updateAmount( IDstSwapper dstSwapper, bool hasDstSwap_, @@ -556,12 +589,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { finalState_ = PayloadState.UPDATED; } - if ( - !( - ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))).isSuperform(superformId_) - && finalState_ == PayloadState.UPDATED - ) - ) { + if (!(_isSuperform(superformId_) && finalState_ == PayloadState.UPDATED)) { failedDeposits[payloadId_].superformIds.push(superformId_); address asset; @@ -573,6 +601,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { /// @dev if superform is invalid, try catch will fail and asset pushed is address (0) /// @notice this means that if a user tries to game the protocol with an invalid superformId, the funds /// bridged over that failed will be stuck here + /// @notice assets can still be spoofed with any vault.asset(), hence this is done via permissioned role failedDeposits[payloadId_].settlementToken.push(asset); failedDeposits[payloadId_].settleFromDstSwapper.push(false); @@ -591,7 +620,6 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { /// @dev helper function to update multi vault withdraw payload function _updateWithdrawPayload( bytes memory prevPayloadBody_, - address srcSender_, uint64 srcChainId_, bytes[] calldata txData_, uint8 multi @@ -613,10 +641,10 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { revert Error.DIFFERENT_PAYLOAD_UPDATE_TX_DATA_LENGTH(); } - multiVaultData = _updateTxData(txData_, multiVaultData, srcSender_, srcChainId_, CHAIN_ID); + multiVaultData = _updateTxData(txData_, multiVaultData, srcChainId_, CHAIN_ID); if (multi == 0) { - singleVaultData.liqData.txData = txData_[0]; + singleVaultData.liqData.txData = multiVaultData.liqData[0].txData; return abi.encode(singleVaultData); } @@ -627,7 +655,6 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { function _updateTxData( bytes[] calldata txData_, InitMultiVaultData memory multiVaultData_, - address srcSender_, uint64 srcChainId_, uint64 dstChainId_ ) @@ -636,17 +663,18 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { returns (InitMultiVaultData memory) { uint256 len = multiVaultData_.liqData.length; + IBaseForm superform; for (uint256 i; i < len; ++i) { if (txData_[i].length != 0 && multiVaultData_.liqData[i].txData.length == 0) { - (address superform,,) = multiVaultData_.superformIds[i].getSuperform(); + superform = IBaseForm(_getSuperform(multiVaultData_.superformIds[i])); /// @dev for withdrawals the payload update can happen on core state registry (for normal forms) /// and also can happen in timelock state registry (for timelock form) /// @notice this check validates if the state registry is eligible to update tx data for the /// corresponding superform - if (IBaseForm(superform).getStateRegistryId() == _getStateRegistryId(address(this))) { + if (superform.getStateRegistryId() == _getStateRegistryId(address(this))) { PayloadUpdaterLib.validateLiqReq(multiVaultData_.liqData[i]); IBridgeValidator bridgeValidator = @@ -659,9 +687,9 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { srcChainId_, multiVaultData_.liqData[i].liqDstChainId, false, - superform, - srcSender_, - multiVaultData_.liqData[i].token, + address(superform), + multiVaultData_.receiverAddress, + _getVaultAsset(address(superform)), address(0) ) ); @@ -669,7 +697,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { if ( !PayloadUpdaterLib.validateSlippage( bridgeValidator.decodeAmountIn(txData_[i], false), - IBaseForm(superform).previewRedeemFrom(multiVaultData_.amounts[i]), + superform.previewRedeemFrom(multiVaultData_.amounts[i]), multiVaultData_.maxSlippages[i] ) ) { @@ -697,7 +725,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { bool errors; uint256 len = multiVaultData.superformIds.length; - address superformFactory = superRegistry.getAddress(keccak256("SUPERFORM_FACTORY")); + address superformFactory = _getAddress(keccak256("SUPERFORM_FACTORY")); for (uint256 i; i < len; ++i) { // @dev validates if superformId exists on factory @@ -706,13 +734,12 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { } /// @dev Store destination payloadId_ & index in extraFormData (tbd: 1-step flow doesnt need this) - (address superform_,,) = multiVaultData.superformIds[i].getSuperform(); - - try IBaseForm(superform_).xChainWithdrawFromVault( + try IBaseForm(_getSuperform(multiVaultData.superformIds[i])).xChainWithdrawFromVault( InitSingleVaultData({ payloadId: multiVaultData.payloadId, superformId: multiVaultData.superformIds[i], amount: multiVaultData.amounts[i], + outputAmount: multiVaultData.outputAmounts[i], maxSlippage: multiVaultData.maxSlippages[i], liqData: multiVaultData.liqData[i], hasDstSwap: false, @@ -769,7 +796,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { /// @dev this means that this amount was already added to the failedDeposits state variable and should not /// be re-added (or processed here) if (multiVaultData.amounts[i] != 0) { - underlying = IERC20(IBaseForm(superforms[i]).getVaultAsset()); + underlying = IERC20(_getVaultAsset(superforms[i])); if (underlying.balanceOf(address(this)) >= multiVaultData.amounts[i]) { underlying.safeIncreaseAllowance(superforms[i], multiVaultData.amounts[i]); @@ -781,6 +808,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { payloadId: multiVaultData.payloadId, superformId: multiVaultData.superformIds[i], amount: multiVaultData.amounts[i], + outputAmount: multiVaultData.outputAmounts[i], maxSlippage: multiVaultData.maxSlippages[i], liqData: emptyRequest, hasDstSwap: false, @@ -790,11 +818,11 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { }), srcSender_, srcChainId_ - ) returns (uint256 dstAmount) { - if (dstAmount != 0 && !multiVaultData.retain4626s[i]) { + ) returns (uint256 shares) { + if (shares != 0 && !multiVaultData.retain4626s[i]) { fulfilment = true; /// @dev marks the indexes that require a callback mint of shares (successful) - multiVaultData.amounts[i] = dstAmount; + multiVaultData.amounts[i] = shares; } else { multiVaultData.amounts[i] = 0; } @@ -811,7 +839,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { /// @dev clearing multiVaultData.amounts so that in case that fulfillment is true these amounts /// are not minted multiVaultData.amounts[i] = 0; - failedDeposits[payloadId_].settlementToken.push(IBaseForm(superforms[i]).getVaultAsset()); + failedDeposits[payloadId_].settlementToken.push(_getVaultAsset(superforms[i])); failedDeposits[payloadId_].settleFromDstSwapper.push(false); } } else { @@ -820,6 +848,10 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { } } + if (errors) { + emit FailedXChainDeposits(payloadId_); + } + /// @dev issue superPositions if at least one vault deposit passed if (fulfilment) { return _multiReturnData( @@ -832,10 +864,6 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { ); } - if (errors) { - emit FailedXChainDeposits(payloadId_); - } - return ""; } @@ -851,13 +879,14 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { InitSingleVaultData memory singleVaultData = abi.decode(payload_, (InitSingleVaultData)); singleVaultData.extraFormData = abi.encode(payloadId_, 0); - if (!ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))).isSuperform(singleVaultData.superformId)) { + if (!_isSuperform(singleVaultData.superformId)) { revert Error.SUPERFORM_ID_NONEXISTENT(); } - (address superform_,,) = singleVaultData.superformId.getSuperform(); /// @dev Withdraw from superform - try IBaseForm(superform_).xChainWithdrawFromVault(singleVaultData, srcSender_, srcChainId_) { + try IBaseForm(_getSuperform(singleVaultData.superformId)).xChainWithdrawFromVault( + singleVaultData, srcSender_, srcChainId_ + ) { // Handle the case when the external call succeeds } catch { // Handle the case when the external call reverts for whatever reason @@ -886,24 +915,25 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { { InitSingleVaultData memory singleVaultData = abi.decode(payload_, (InitSingleVaultData)); - (address superform_,,) = singleVaultData.superformId.getSuperform(); - IERC20 underlying = IERC20(IBaseForm(superform_).getVaultAsset()); + address superform_ = _getSuperform(singleVaultData.superformId); + address vaultAsset = _getVaultAsset(superform_); + IERC20 underlying = IERC20(vaultAsset); if (underlying.balanceOf(address(this)) >= singleVaultData.amount) { underlying.safeIncreaseAllowance(superform_, singleVaultData.amount); /// @dev deposit to superform try IBaseForm(superform_).xChainDepositIntoVault(singleVaultData, srcSender_, srcChainId_) returns ( - uint256 dstAmount + uint256 shares ) { - if (dstAmount != 0 && !singleVaultData.retain4626) { + if (shares != 0 && !singleVaultData.retain4626) { return _singleReturnData( srcSender_, singleVaultData.payloadId, TransactionType.DEPOSIT, CallbackType.RETURN, singleVaultData.superformId, - dstAmount + shares ); } } catch { @@ -912,7 +942,7 @@ contract CoreStateRegistry is BaseStateRegistry, ICoreStateRegistry { /// @dev if any deposit fails, add it to failedDepositSuperformIds mapping for future rescuing failedDeposits[payloadId_].superformIds.push(singleVaultData.superformId); - failedDeposits[payloadId_].settlementToken.push(IBaseForm(superform_).getVaultAsset()); + failedDeposits[payloadId_].settlementToken.push(vaultAsset); failedDeposits[payloadId_].settleFromDstSwapper.push(false); emit FailedXChainDeposits(payloadId_); diff --git a/src/crosschain-data/extensions/TimelockStateRegistry.sol b/src/crosschain-data/extensions/TimelockStateRegistry.sol index ab1caf0af..7bdafea9a 100644 --- a/src/crosschain-data/extensions/TimelockStateRegistry.sol +++ b/src/crosschain-data/extensions/TimelockStateRegistry.sol @@ -1,29 +1,37 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { BaseStateRegistry } from "src/crosschain-data/BaseStateRegistry.sol"; +import { IBaseForm } from "src/interfaces/IBaseForm.sol"; +import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { IQuorumManager } from "src/interfaces/IQuorumManager.sol"; +import { ISuperPositions } from "src/interfaces/ISuperPositions.sol"; +import { ITimelockStateRegistry } from "src/interfaces/ITimelockStateRegistry.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { IPaymentHelper } from "src/interfaces/IPaymentHelper.sol"; +import { IERC4626TimelockForm } from "src/forms/interfaces/IERC4626TimelockForm.sol"; +import { Error } from "src/libraries/Error.sol"; +import { ProofLib } from "src/libraries/ProofLib.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { PayloadUpdaterLib } from "src/libraries/PayloadUpdaterLib.sol"; +import { + InitSingleVaultData, + AMBMessage, + TimelockPayload, + CallbackType, + TransactionType, + PayloadState, + TimelockStatus, + ReturnSingleData +} from "src/types/DataTypes.sol"; import { ReentrancyGuard } from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; -import { IBaseForm } from "../../interfaces/IBaseForm.sol"; -import { ISuperformFactory } from "../../interfaces/ISuperformFactory.sol"; -import { ISuperRegistry } from "../../interfaces/ISuperRegistry.sol"; -import { IBridgeValidator } from "../../interfaces/IBridgeValidator.sol"; -import { IQuorumManager } from "../../interfaces/IQuorumManager.sol"; -import { ISuperPositions } from "../../interfaces/ISuperPositions.sol"; -import { IERC4626TimelockForm } from "../../forms/interfaces/IERC4626TimelockForm.sol"; -import { ITimelockStateRegistry } from "../../interfaces/ITimelockStateRegistry.sol"; -import { IBaseStateRegistry } from "../../interfaces/IBaseStateRegistry.sol"; -import { ISuperRBAC } from "../../interfaces/ISuperRBAC.sol"; -import { IPaymentHelper } from "../../interfaces/IPaymentHelper.sol"; -import { Error } from "../../libraries/Error.sol"; -import { BaseStateRegistry } from "../BaseStateRegistry.sol"; -import { ProofLib } from "../../libraries/ProofLib.sol"; -import { DataLib } from "../../libraries/DataLib.sol"; -import { PayloadUpdaterLib } from "../../libraries/PayloadUpdaterLib.sol"; -import "../../types/DataTypes.sol"; /// @title TimelockStateRegistry +/// @dev Handles communication in timelocked forms /// @author Zeropoint Labs -/// @notice handles communication in timelocked forms - contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, ReentrancyGuard { using DataLib for uint256; using ProofLib for AMBMessage; @@ -56,12 +64,13 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree _; } - /// @dev allows only form to write to the receive payload - modifier onlyTimelockSuperform(uint256 superformId) { - if (!ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))).isSuperform(superformId)) { + /// @dev ensures only the timelock form can write to a timelock superform + /// @param superformId_ is the superformId of the superform to check + modifier onlyTimelockSuperform(uint256 superformId_) { + if (!ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))).isSuperform(superformId_)) { revert Error.SUPERFORM_ID_NONEXISTENT(); } - (address superform,,) = superformId.getSuperform(); + (address superform,,) = superformId_.getSuperform(); if (msg.sender != superform) revert Error.NOT_SUPERFORM(); if (IBaseForm(superform).getStateRegistryId() != superRegistry.getStateRegistryId(address(this))) { @@ -71,6 +80,8 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree _; } + /// @dev ensures only valid payloads are processed + /// @param payloadId_ is the payloadId to check modifier isValidPayloadId(uint256 payloadId_) { if (payloadId_ > payloadsCount) { revert Error.INVALID_PAYLOAD_ID(); @@ -100,7 +111,6 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree /// @inheritdoc ITimelockStateRegistry function receivePayload( uint8 type_, - address srcSender_, uint64 srcChainId_, uint256 lockedTill_, InitSingleVaultData memory data_ @@ -109,10 +119,12 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree override onlyTimelockSuperform(data_.superformId) { + if (data_.receiverAddress == address(0)) revert Error.RECEIVER_ADDRESS_NOT_SET(); + ++timelockPayloadCounter; timelockPayload[timelockPayloadCounter] = - TimelockPayload(type_, srcSender_, srcChainId_, lockedTill_, data_, TimelockStatus.PENDING); + TimelockPayload(type_, srcChainId_, lockedTill_, data_, TimelockStatus.PENDING); } /// @inheritdoc ITimelockStateRegistry @@ -139,9 +151,9 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree /// @dev set status here to prevent re-entrancy p.status = TimelockStatus.PROCESSED; - (address superform,,) = p.data.superformId.getSuperform(); + (address superformAddress,,) = p.data.superformId.getSuperform(); - IERC4626TimelockForm form = IERC4626TimelockForm(superform); + IERC4626TimelockForm superform = IERC4626TimelockForm(superformAddress); /// @dev this step is used to re-feed txData to avoid using old txData that would have expired by now if (txData_.length != 0) { @@ -156,9 +168,9 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree p.srcChainId, p.data.liqData.liqDstChainId, false, - superform, + superformAddress, p.data.receiverAddress, - p.data.liqData.token, + superform.getVaultAsset(), address(0) ) ); @@ -166,7 +178,7 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree finalAmount = bridgeValidator.decodeAmountIn(txData_, false); if ( !PayloadUpdaterLib.validateSlippage( - finalAmount, form.previewRedeemFrom(p.data.amount), p.data.maxSlippage + finalAmount, superform.previewRedeemFrom(p.data.amount), p.data.maxSlippage ) ) { revert Error.SLIPPAGE_OUT_OF_BOUNDS(); @@ -175,21 +187,21 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree p.data.liqData.txData = txData_; } - try form.withdrawAfterCoolDown(p) { } + try superform.withdrawAfterCoolDown(p) { } catch { /// @dev dispatch acknowledgement to mint superPositions back because of failure if (p.isXChain == 1) { (uint256 payloadId,) = abi.decode(p.data.extraFormData, (uint256, uint256)); _dispatchAcknowledgement( - p.srcChainId, _getDeliveryAMB(payloadId), _constructSingleReturnData(p.srcSender, p.data) + p.srcChainId, _getDeliveryAMB(payloadId), _constructSingleReturnData(p.data.receiverAddress, p.data) ); } /// @dev for direct chain, superPositions are minted directly if (p.isXChain == 0) { ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).mintSingle( - p.srcSender, p.data.superformId, p.data.amount + p.data.receiverAddress, p.data.superformId, p.data.amount ); } } @@ -255,7 +267,7 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree /// xChainWithdraw succeeds. /// @dev Constructs return message in case of a FAILURE to perform redemption of already unlocked assets function _constructSingleReturnData( - address srcSender_, + address receiverAddress_, InitSingleVaultData memory singleVaultData_ ) internal @@ -270,7 +282,7 @@ contract TimelockStateRegistry is BaseStateRegistry, ITimelockStateRegistry, Ree uint8(CallbackType.FAIL), 0, superRegistry.getStateRegistryId(address(this)), - srcSender_, + receiverAddress_, CHAIN_ID ), abi.encode( diff --git a/src/crosschain-data/utils/PayloadHelper.sol b/src/crosschain-data/utils/PayloadHelper.sol index e233c1d20..4bd33c155 100644 --- a/src/crosschain-data/utils/PayloadHelper.sol +++ b/src/crosschain-data/utils/PayloadHelper.sol @@ -1,13 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { ISuperRegistry } from "../../interfaces/ISuperRegistry.sol"; -import { ISuperPositions } from "../../interfaces/ISuperPositions.sol"; -import { IBaseStateRegistry } from "../../interfaces/IBaseStateRegistry.sol"; -import { ITimelockStateRegistry } from "../../interfaces/ITimelockStateRegistry.sol"; -import { IPayloadHelper } from "../../interfaces/IPayloadHelper.sol"; -import { IBridgeValidator } from "../../interfaces/IBridgeValidator.sol"; -import { Error } from "../../libraries/Error.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; +import { ITimelockStateRegistry } from "src/interfaces/ITimelockStateRegistry.sol"; +import { IPayloadHelper } from "src/interfaces/IPayloadHelper.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { ISuperPositions } from "src/interfaces/ISuperPositions.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { ProofLib } from "src/libraries/ProofLib.sol"; +import { Error } from "src/libraries/Error.sol"; import { CallbackType, ReturnMultiData, @@ -15,17 +17,14 @@ import { InitMultiVaultData, InitSingleVaultData, TimelockPayload, - LiqRequest, AMBMessage -} from "../../types/DataTypes.sol"; -import { DataLib } from "../../libraries/DataLib.sol"; -import { ProofLib } from "../../libraries/ProofLib.sol"; +} from "src/types/DataTypes.sol"; /// @title PayloadHelper +/// @dev Helps decode payload data for off-chain purposes /// @author ZeroPoint Labs -/// @dev helps decode payload data more easily. Used for off-chain purposes - contract PayloadHelper is IPayloadHelper { + using DataLib for uint256; ////////////////////////////////////////////////////////////// @@ -38,25 +37,6 @@ contract PayloadHelper is IPayloadHelper { // STRUCTS // ////////////////////////////////////////////////////////////// - struct DecodeDstPayloadInternalVars { - uint8 txType; - uint8 callbackType; - address srcSender; - uint64 srcChainId; - uint256[] amounts; - uint256[] slippages; - uint256[] superformIds; - bool[] hasDstSwaps; - address receiverAddress; - uint256 srcPayloadId; - bytes extraFormData; - uint8 multi; - ReturnMultiData rd; - ReturnSingleData rsd; - InitMultiVaultData imvd; - InitSingleVaultData isvd; - } - struct DecodeDstPayloadLiqDataInternalVars { uint8 callbackType; uint8 multi; @@ -76,6 +56,9 @@ contract PayloadHelper is IPayloadHelper { ////////////////////////////////////////////////////////////// constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } superRegistry = ISuperRegistry(superRegistry_); } @@ -88,49 +71,29 @@ contract PayloadHelper is IPayloadHelper { external view override - returns ( - uint8 txType, - uint8 callbackType, - address srcSender, - uint64 srcChainId, - uint256[] memory amounts, - uint256[] memory slippages, - uint256[] memory superformIds, - bool[] memory hasDstSwaps, - bytes memory extraFormData, - address receiverAddress, - uint256 srcPayloadId - ) + returns (DecodedDstPayload memory v) { - IBaseStateRegistry coreStateRegistry = _getCoreStateRegistry(); - _isValidPayloadId(dstPayloadId_, coreStateRegistry); + _isValidPayloadId(dstPayloadId_, _getCoreStateRegistry()); - DecodeDstPayloadInternalVars memory v; (v.txType, v.callbackType, v.multi, v.srcSender, v.srcChainId) = - _decodePayloadHeader(dstPayloadId_, coreStateRegistry); + _decodePayloadHeader(dstPayloadId_, _getCoreStateRegistry()); if (v.callbackType == uint256(CallbackType.RETURN) || v.callbackType == uint256(CallbackType.FAIL)) { - (v.amounts, v.srcPayloadId) = _decodeReturnData(dstPayloadId_, v.multi, coreStateRegistry); + (v.amounts, v.srcPayloadId) = _decodeReturnData(dstPayloadId_, v.multi, _getCoreStateRegistry()); } else if (v.callbackType == uint256(CallbackType.INIT)) { - (v.amounts, v.slippages, v.superformIds, v.hasDstSwaps, v.extraFormData, v.receiverAddress, v.srcPayloadId) - = _decodeInitData(dstPayloadId_, v.multi, coreStateRegistry); + ( + v.amounts, + v.outputAmounts, + v.slippages, + v.superformIds, + v.hasDstSwaps, + v.extraFormData, + v.receiverAddress, + v.srcPayloadId + ) = _decodeInitData(dstPayloadId_, v.multi, _getCoreStateRegistry()); } else { revert Error.INVALID_PAYLOAD(); } - - return ( - v.txType, - v.callbackType, - v.srcSender, - v.srcChainId, - v.amounts, - v.slippages, - v.superformIds, - v.hasDstSwaps, - v.extraFormData, - v.receiverAddress, - v.srcPayloadId - ); } /// @inheritdoc IPayloadHelper @@ -139,9 +102,10 @@ contract PayloadHelper is IPayloadHelper { view override returns ( - uint8[] memory bridgeIds, bytes[] memory txDatas, address[] memory tokens, + address[] memory interimTokens, + uint8[] memory bridgeIds, uint64[] memory liqDstChainIds, uint256[] memory amountsIn, uint256[] memory nativeAmounts @@ -165,9 +129,17 @@ contract PayloadHelper is IPayloadHelper { external view override - returns (uint8 txType, uint8 callbackType, uint8 multi, address srcSender, uint64 srcChainId) + returns ( + uint8 txType, + uint8 callbackType, + uint8 multi, + address srcSender, + address receiverAddressSP, + uint64 srcChainId + ) { - uint256 txInfo = + uint256 txInfo; + (txInfo, receiverAddressSP) = ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).txHistory(srcPayloadId_); if (txInfo == 0) { @@ -182,7 +154,7 @@ contract PayloadHelper is IPayloadHelper { external view override - returns (address srcSender, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount) + returns (address receiverAddress, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount) { ITimelockStateRegistry timelockStateRegistry = ITimelockStateRegistry(superRegistry.getAddress(keccak256("TIMELOCK_STATE_REGISTRY"))); @@ -194,7 +166,11 @@ contract PayloadHelper is IPayloadHelper { TimelockPayload memory payload = timelockStateRegistry.getTimelockPayload(timelockPayloadId_); return ( - payload.srcSender, payload.srcChainId, payload.data.payloadId, payload.data.superformId, payload.data.amount + payload.data.receiverAddress, + payload.srcChainId, + payload.data.payloadId, + payload.data.superformId, + payload.data.amount ); } @@ -294,6 +270,7 @@ contract PayloadHelper is IPayloadHelper { view returns ( uint256[] memory amounts, + uint256[] memory outputAmounts, uint256[] memory slippages, uint256[] memory superformIds, bool[] memory hasDstSwaps, @@ -308,6 +285,7 @@ contract PayloadHelper is IPayloadHelper { return ( imvd.amounts, + imvd.outputAmounts, imvd.maxSlippages, imvd.superformIds, imvd.hasDstSwaps, @@ -322,6 +300,9 @@ contract PayloadHelper is IPayloadHelper { amounts = new uint256[](1); amounts[0] = isvd.amount; + outputAmounts = new uint256[](1); + outputAmounts[0] = isvd.outputAmount; + slippages = new uint256[](1); slippages[0] = isvd.maxSlippage; @@ -332,7 +313,14 @@ contract PayloadHelper is IPayloadHelper { receiverAddress = isvd.receiverAddress; return ( - amounts, slippages, superformIds, hasDstSwaps, isvd.extraFormData, isvd.receiverAddress, isvd.payloadId + amounts, + outputAmounts, + slippages, + superformIds, + hasDstSwaps, + isvd.extraFormData, + isvd.receiverAddress, + isvd.payloadId ); } } @@ -344,9 +332,10 @@ contract PayloadHelper is IPayloadHelper { internal view returns ( - uint8[] memory bridgeIds, bytes[] memory txDatas, address[] memory tokens, + address[] memory interimTokens, + uint8[] memory bridgeIds, uint64[] memory liqDstChainIds, uint256[] memory amountsIn, uint256[] memory nativeAmounts @@ -357,6 +346,7 @@ contract PayloadHelper is IPayloadHelper { bridgeIds = new uint8[](imvd.liqData.length); txDatas = new bytes[](imvd.liqData.length); tokens = new address[](imvd.liqData.length); + interimTokens = new address[](imvd.liqData.length); liqDstChainIds = new uint64[](imvd.liqData.length); amountsIn = new uint256[](imvd.liqData.length); nativeAmounts = new uint256[](imvd.liqData.length); @@ -367,6 +357,7 @@ contract PayloadHelper is IPayloadHelper { bridgeIds[i] = imvd.liqData[i].bridgeId; txDatas[i] = imvd.liqData[i].txData; tokens[i] = imvd.liqData[i].token; + interimTokens[i] = imvd.liqData[i].interimToken; liqDstChainIds[i] = imvd.liqData[i].liqDstChainId; /// @dev decodes amount from txdata only if its present @@ -377,8 +368,6 @@ contract PayloadHelper is IPayloadHelper { nativeAmounts[i] = imvd.liqData[i].nativeAmount; } - - return (bridgeIds, txDatas, tokens, liqDstChainIds, amountsIn, nativeAmounts); } function _decodeSingleLiqData( @@ -388,9 +377,10 @@ contract PayloadHelper is IPayloadHelper { internal view returns ( - uint8[] memory bridgeIds, bytes[] memory txDatas, address[] memory tokens, + address[] memory interimTokens, + uint8[] memory bridgeIds, uint64[] memory liqDstChainIds, uint256[] memory amountsIn, uint256[] memory nativeAmounts @@ -408,6 +398,9 @@ contract PayloadHelper is IPayloadHelper { tokens = new address[](1); tokens[0] = isvd.liqData.token; + interimTokens = new address[](1); + interimTokens[0] = isvd.liqData.interimToken; + liqDstChainIds = new uint64[](1); liqDstChainIds[0] = isvd.liqData.liqDstChainId; @@ -421,7 +414,5 @@ contract PayloadHelper is IPayloadHelper { nativeAmounts = new uint256[](1); nativeAmounts[0] = isvd.liqData.nativeAmount; - - return (bridgeIds, txDatas, tokens, liqDstChainIds, amountsIn, nativeAmounts); } } diff --git a/src/crosschain-data/utils/QuorumManager.sol b/src/crosschain-data/utils/QuorumManager.sol index f7fe1c397..42c596e15 100644 --- a/src/crosschain-data/utils/QuorumManager.sol +++ b/src/crosschain-data/utils/QuorumManager.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { IQuorumManager } from "../../interfaces/IQuorumManager.sol"; -import { Error } from "../../libraries/Error.sol"; +import { IQuorumManager } from "src/interfaces/IQuorumManager.sol"; +import { Error } from "src/libraries/Error.sol"; /// @title QuorumManager +/// @dev Quorum thresholds using in sending proofs from chain to chain /// @author ZeroPoint Labs -/// @dev separates quorum management concerns into an abstract contract. Can be re-used (currently used by -/// superRegistry) to set different quorums per amb in different areas of the protocol abstract contract QuorumManager is IQuorumManager { + ////////////////////////////////////////////////////////////// // STATE VARIABLES // ////////////////////////////////////////////////////////////// diff --git a/src/crosschain-liquidity/BridgeValidator.sol b/src/crosschain-liquidity/BridgeValidator.sol index 00dac7efe..fd9207d0e 100644 --- a/src/crosschain-liquidity/BridgeValidator.sol +++ b/src/crosschain-liquidity/BridgeValidator.sol @@ -1,16 +1,19 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { ISuperRegistry } from "../interfaces/ISuperRegistry.sol"; -import { IBridgeValidator } from "../interfaces/IBridgeValidator.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { Error } from "src/libraries/Error.sol"; /// @title BridgeValidator +/// @dev Inherited by specific bridge handlers to verify the calldata being sent /// @author Zeropoint Labs -/// @dev To be inherited by specific bridge handlers to verify the calldata being sent abstract contract BridgeValidator is IBridgeValidator { + ////////////////////////////////////////////////////////////// - // CONSTANTS // + // CONSTANTS // ////////////////////////////////////////////////////////////// + ISuperRegistry public immutable superRegistry; address constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; @@ -19,6 +22,9 @@ abstract contract BridgeValidator is IBridgeValidator { ////////////////////////////////////////////////////////////// constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } superRegistry = ISuperRegistry(superRegistry_); } @@ -51,7 +57,7 @@ abstract contract BridgeValidator is IBridgeValidator { bool genericSwapDisallowed_ ) external - view + pure virtual override returns (uint256 amount_); @@ -67,7 +73,7 @@ abstract contract BridgeValidator is IBridgeValidator { /// @inheritdoc IBridgeValidator function decodeSwapOutputToken(bytes calldata txData_) external - view + pure virtual override returns (address outputToken_); diff --git a/src/crosschain-liquidity/DstSwapper.sol b/src/crosschain-liquidity/DstSwapper.sol index 6050b1464..2fdf94fb0 100644 --- a/src/crosschain-liquidity/DstSwapper.sol +++ b/src/crosschain-liquidity/DstSwapper.sol @@ -1,23 +1,23 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { LiquidityHandler } from "src/crosschain-liquidity/LiquidityHandler.sol"; +import { IDstSwapper } from "src/interfaces/IDstSwapper.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { IERC4626Form } from "src/forms/interfaces/IERC4626Form.sol"; +import { Error } from "src/libraries/Error.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { InitSingleVaultData, InitMultiVaultData, PayloadState } from "src/types/DataTypes.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import { ReentrancyGuard } from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; -import { IDstSwapper } from "../interfaces/IDstSwapper.sol"; -import { ISuperRegistry } from "../interfaces/ISuperRegistry.sol"; -import { IBaseStateRegistry } from "../interfaces/IBaseStateRegistry.sol"; -import { IBridgeValidator } from "../interfaces/IBridgeValidator.sol"; -import { LiquidityHandler } from "../crosschain-liquidity/LiquidityHandler.sol"; -import { ISuperRBAC } from "../interfaces/ISuperRBAC.sol"; -import { IERC4626Form } from "../forms/interfaces/IERC4626Form.sol"; -import { Error } from "../libraries/Error.sol"; -import { DataLib } from "../libraries/DataLib.sol"; -import "../types/DataTypes.sol"; /// @title DstSwapper -/// @author Zeropoint Labs. -/// @dev handles all destination chain swaps. +/// @dev Handles all destination chain swaps +/// @author Zeropoint Labs contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { using SafeERC20 for IERC20; @@ -27,6 +27,7 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { ISuperRegistry public immutable superRegistry; uint64 public immutable CHAIN_ID; + uint256 internal constant ENTIRE_SLIPPAGE = 10_000; ////////////////////////////////////////////////////////////// // STATE VARIABLES // @@ -79,6 +80,10 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { /// @param superRegistry_ superform registry contract constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + if (block.chainid > type(uint64).max) { revert Error.BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); } @@ -130,14 +135,13 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { /// @inheritdoc IDstSwapper function processTx( uint256 payloadId_, - uint256 index_, uint8 bridgeId_, bytes calldata txData_ ) external override - onlySwapper nonReentrant + onlySwapper { IBaseStateRegistry coreStateRegistry = _getCoreStateRegistry(); @@ -149,7 +153,8 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { _processTx( payloadId_, - index_, + 0, + /// index is always 0 for single vault payload bridgeId_, txData_, abi.decode(coreStateRegistry.payloadBody(payloadId_), (InitSingleVaultData)).liqData.interimToken, @@ -160,17 +165,20 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { /// @inheritdoc IDstSwapper function batchProcessTx( uint256 payloadId_, - uint256[] calldata indices, + uint256[] calldata indices_, uint8[] calldata bridgeIds_, bytes[] calldata txData_ ) external override - onlySwapper nonReentrant + onlySwapper { - IBaseStateRegistry coreStateRegistry = _getCoreStateRegistry(); + uint256 len = txData_.length; + if (len == 0) revert Error.ZERO_INPUT_VALUE(); + if (len != indices_.length && len != bridgeIds_.length) revert Error.ARRAY_LENGTH_MISMATCH(); + IBaseStateRegistry coreStateRegistry = _getCoreStateRegistry(); _isValidPayloadId(payloadId_, coreStateRegistry); (,, uint8 multi,,,) = DataLib.decodeTxInfo(coreStateRegistry.payloadHeader(payloadId_)); @@ -178,25 +186,25 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { InitMultiVaultData memory data = abi.decode(coreStateRegistry.payloadBody(payloadId_), (InitMultiVaultData)); - uint256 len = txData_.length; + uint256 maxIndex = data.liqData.length; + uint256 index; + for (uint256 i; i < len; ++i) { + index = indices_[i]; + + if (index >= maxIndex) revert Error.INDEX_OUT_OF_BOUNDS(); + if (i > 0 && index <= indices_[i - 1]) { + revert Error.DUPLICATE_INDEX(); + } + _processTx( - payloadId_, indices[i], bridgeIds_[i], txData_[i], data.liqData[i].interimToken, coreStateRegistry + payloadId_, index, bridgeIds_[i], txData_[i], data.liqData[index].interimToken, coreStateRegistry ); } } /// @inheritdoc IDstSwapper - function updateFailedTx( - uint256 payloadId_, - uint256 index_, - address interimToken_, - uint256 amount_ - ) - external - override - onlySwapper - { + function updateFailedTx(uint256 payloadId_, address interimToken_, uint256 amount_) external override onlySwapper { IBaseStateRegistry coreStateRegistry = _getCoreStateRegistry(); _isValidPayloadId(payloadId_, coreStateRegistry); @@ -207,7 +215,8 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { _updateFailedTx( payloadId_, - index_, + 0, + /// index is always zero for single vault payload interimToken_, abi.decode(coreStateRegistry.payloadBody(payloadId_), (InitSingleVaultData)).liqData.interimToken, amount_, @@ -228,6 +237,10 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { { uint256 len = indices_.length; + if (len != interimTokens_.length || len != amounts_.length) { + revert Error.ARRAY_LENGTH_MISMATCH(); + } + IBaseStateRegistry coreStateRegistry = _getCoreStateRegistry(); _isValidPayloadId(payloadId_, coreStateRegistry); @@ -237,9 +250,23 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { InitMultiVaultData memory data = abi.decode(coreStateRegistry.payloadBody(payloadId_), (InitMultiVaultData)); + uint256 maxIndex = data.liqData.length; + uint256 index; + for (uint256 i; i < len; ++i) { + index = indices_[i]; + if (index >= maxIndex) revert Error.INDEX_OUT_OF_BOUNDS(); + if (i > 0 && index <= indices_[i - 1]) { + revert Error.DUPLICATE_INDEX(); + } + _updateFailedTx( - payloadId_, indices_[i], interimTokens_[i], data.liqData[i].interimToken, amounts_[i], coreStateRegistry + payloadId_, + indices_[index], + interimTokens_[i], + data.liqData[index].interimToken, + amounts_[i], + coreStateRegistry ); } } @@ -300,7 +327,15 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { if (userSuppliedInterimToken_ != v.approvalToken) { revert Error.INVALID_INTERIM_TOKEN(); } - + if (userSuppliedInterimToken_ == NATIVE) { + if (address(this).balance < v.amount) { + revert Error.INSUFFICIENT_BALANCE(); + } + } else { + if (IERC20(userSuppliedInterimToken_).balanceOf(address(this)) < v.amount) { + revert Error.INSUFFICIENT_BALANCE(); + } + } v.finalDst = address(coreStateRegistry_); /// @dev validates the bridge data @@ -323,7 +358,6 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { (v.underlying, v.expAmount, v.maxSlippage) = _getFormUnderlyingFrom(coreStateRegistry_, payloadId_, index_); v.balanceBefore = IERC20(v.underlying).balanceOf(v.finalDst); - _dispatchTokens( superRegistry.getBridgeAddress(bridgeId_), txData_, @@ -340,21 +374,17 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { v.balanceDiff = v.balanceAfter - v.balanceBefore; - /// @dev if actual underlying is less than expAmount adjusted - /// with maxSlippage, invariant breaks - /// @notice that unlike in CoreStateRegistry slippage check inside updateDeposit, in here we don't check for - /// negative slippage - /// @notice this essentially allows any amount to be swapped, (the invariant will still break if the amount is - /// too low) - /// @notice this doesn't mean that the keeper or the user can swap any amount, because of the 2nd slippage check - /// in CoreStateRegistry - /// @notice in this check, we check if there is negative slippage, for which case, the user is capped to receive - /// the v.expAmount of tokens (originally defined) - if (v.balanceDiff < ((v.expAmount * (10_000 - v.maxSlippage)) / 10_000)) { + /// @dev if actual underlying is less than expAmount adjusted with maxSlippage, invariant breaks + if (v.balanceDiff * ENTIRE_SLIPPAGE < v.expAmount * (ENTIRE_SLIPPAGE - v.maxSlippage)) { revert Error.SLIPPAGE_OUT_OF_BOUNDS(); } - /// @dev updates swapped amount + /// @dev updates swapped amount adjusting for + /// @notice in this check, we check if there is negative slippage, for which case, the user is capped to receive + /// the v.expAmount of tokens (originally defined) + if (v.balanceDiff > v.expAmount) { + v.balanceDiff = v.expAmount; + } swappedAmount[payloadId_][index_] = v.balanceDiff; /// @dev emits final event diff --git a/src/crosschain-liquidity/LiquidityHandler.sol b/src/crosschain-liquidity/LiquidityHandler.sol index 9b8cc3d6f..cb5b8a045 100644 --- a/src/crosschain-liquidity/LiquidityHandler.sol +++ b/src/crosschain-liquidity/LiquidityHandler.sol @@ -1,21 +1,19 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { Error } from "src/libraries/Error.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; -import { Error } from "../libraries/Error.sol"; - -/** - * @title LiquidityHandler - * @author Zeropoint Labs. - * @dev Executes an action with tokens to either bridge from Chain A -> Chain B or swap on same chain. - * @dev To be inherited by contracts that move liquidity - */ + +/// @title LiquidityHandler +/// @dev Executes an action with tokens to either bridge from Chain A -> Chain B or swap on same chain +/// @dev To be inherited by contracts that move liquidity +/// @author ZeroPoint Labs abstract contract LiquidityHandler { using SafeERC20 for IERC20; ////////////////////////////////////////////////////////////// - // CONSTANTS // + // CONSTANTS // ////////////////////////////////////////////////////////////// address constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; @@ -40,18 +38,27 @@ abstract contract LiquidityHandler { internal virtual { + if (amount_ == 0) { + revert Error.ZERO_AMOUNT(); + } + if (bridge_ == address(0)) { revert Error.ZERO_ADDRESS(); } if (token_ != NATIVE) { - IERC20 token = IERC20(token_); - token.safeIncreaseAllowance(bridge_, amount_); + IERC20(token_).safeIncreaseAllowance(bridge_, amount_); } else { if (nativeAmount_ < amount_) revert Error.INSUFFICIENT_NATIVE_AMOUNT(); + if (nativeAmount_ > address(this).balance) revert Error.INSUFFICIENT_BALANCE(); } (bool success,) = payable(bridge_).call{ value: nativeAmount_ }(txData_); if (!success) revert Error.FAILED_TO_EXECUTE_TXDATA(token_); + + if (token_ != NATIVE) { + IERC20 token = IERC20(token_); + if (token.allowance(address(this), bridge_) > 0) token.forceApprove(bridge_, 0); + } } } diff --git a/src/crosschain-liquidity/lifi/LiFiValidator.sol b/src/crosschain-liquidity/lifi/LiFiValidator.sol index a306a4f74..3b9aeda24 100644 --- a/src/crosschain-liquidity/lifi/LiFiValidator.sol +++ b/src/crosschain-liquidity/lifi/LiFiValidator.sol @@ -7,11 +7,13 @@ import { LiFiTxDataExtractor } from "src/vendor/lifi/LiFiTxDataExtractor.sol"; import { LibSwap } from "src/vendor/lifi/LibSwap.sol"; import { ILiFi } from "src/vendor/lifi/ILiFi.sol"; import { StandardizedCallFacet } from "src/vendor/lifi/StandardizedCallFacet.sol"; +import { GenericSwapFacet } from "src/vendor/lifi/GenericSwapFacet.sol"; /// @title LiFiValidator +/// @dev Asserts LiFi input txData is valid /// @author Zeropoint Labs -/// @dev To assert input txData is valid contract LiFiValidator is BridgeValidator, LiFiTxDataExtractor { + ////////////////////////////////////////////////////////////// // CONSTRUCTOR // ////////////////////////////////////////////////////////////// @@ -24,97 +26,40 @@ contract LiFiValidator is BridgeValidator, LiFiTxDataExtractor { /// @inheritdoc BridgeValidator function validateReceiver(bytes calldata txData_, address receiver_) external pure override returns (bool valid_) { - return _extractBridgeData(txData_).receiver == receiver_; + (, address receiver) = _extractBridgeData(txData_); + return receiver == receiver_; } /// @inheritdoc BridgeValidator function validateTxData(ValidateTxDataArgs calldata args_) external view override returns (bool hasDstSwap) { - /// @dev xchain actions can have bridgeData or bridgeData + swapData - /// @dev direct actions with deposit, cannot have bridge data - goes into catch block - /// @dev withdraw actions may have bridge data after withdrawing - goes into try block - /// @dev withdraw actions without bridge data (just swap) - goes into catch block + bytes4 selector = _extractSelector(args_.txData); - try this.extractMainParameters(args_.txData) returns ( - string memory, /*bridge*/ - address sendingAssetId, - address receiver, - uint256, /*amount*/ - uint256, /*minAmount*/ - uint256 destinationChainId, - bool, /*hasSourceSwaps*/ - bool hasDestinationCall - ) { - /// @dev 0. Destination call validation - if (hasDestinationCall) revert Error.INVALID_TXDATA_NO_DESTINATIONCALL_ALLOWED(); + address sendingAssetId; + address receiver; + bool hasDestinationCall; + uint256 destinationChainId; - /// @dev 1. chainId validation - /// @dev for deposits, liqDstChainId/toChainId will be the normal destination (where the target superform - /// is) - /// @dev for withdraws, liqDstChainId/toChainId will be the desired chain to where the underlying must be - /// sent - /// @dev to after vault redemption - - if (uint256(args_.liqDstChainId) != destinationChainId) revert Error.INVALID_TXDATA_CHAIN_ID(); + /// @dev 1 - check if it is a swapTokensGeneric call (match via selector) + if (selector == GenericSwapFacet.swapTokensGeneric.selector) { + /// @dev GenericSwapFacet - /// @dev 2. receiver address validation - if (args_.deposit) { - if (args_.srcChainId == args_.dstChainId) { - revert Error.INVALID_ACTION(); - } else { - hasDstSwap = - receiver == superRegistry.getAddressByChainId(keccak256("DST_SWAPPER"), args_.dstChainId); - /// @dev if cross chain deposits, then receiver address must be CoreStateRegistry (or) Dst Swapper - if ( - !( - receiver - == superRegistry.getAddressByChainId(keccak256("CORE_STATE_REGISTRY"), args_.dstChainId) - || hasDstSwap - ) - ) { - revert Error.INVALID_TXDATA_RECEIVER(); - } - - /// @dev forbid xChain deposits with destination swaps without interim token set (for user - /// protection) - if (hasDstSwap && args_.liqDataInterimToken == address(0)) { - revert Error.INVALID_INTERIM_TOKEN(); - } - } - } else { - /// @dev if withdraws, then receiver address must be the srcSender - if (receiver != args_.srcSender) revert Error.INVALID_TXDATA_RECEIVER(); - } + (sendingAssetId,, receiver,,) = extractGenericSwapParameters(args_.txData); + _validateGenericParameters(args_, receiver, sendingAssetId); + /// @dev if valid return here + return false; + } - /// @dev remap of address 0 to NATIVE because of how LiFi produces txData - if (sendingAssetId == address(0)) { - sendingAssetId = NATIVE; - } - /// @dev 3. token validations - if (args_.liqDataToken != sendingAssetId) revert Error.INVALID_TXDATA_TOKEN(); - } catch { - (address sendingAssetId,, address receiver,,) = extractGenericSwapParameters(args_.txData); + /// @dev 2 - check if it is any other blacklisted selector - /// @dev 1. chainId validation + if (!_validateSelector(selector)) revert Error.BLACKLISTED_SELECTOR(); - if (args_.srcChainId != args_.dstChainId) revert Error.INVALID_ACTION(); + /// @dev 3 - proceed with normal extraction + (, sendingAssetId, receiver,,, destinationChainId,, hasDestinationCall) = extractMainParameters(args_.txData); - /// @dev 2. receiver address validation - if (args_.deposit) { - if (args_.dstChainId != args_.liqDstChainId) revert Error.INVALID_DEPOSIT_LIQ_DST_CHAIN_ID(); - /// @dev If same chain deposits then receiver address must be the superform - if (receiver != args_.superform) revert Error.INVALID_TXDATA_RECEIVER(); - } else { - /// @dev if withdraws, then receiver address must be the srcSender - if (receiver != args_.srcSender) revert Error.INVALID_TXDATA_RECEIVER(); - } + hasDstSwap = + _validateMainParameters(args_, hasDestinationCall, hasDstSwap, receiver, sendingAssetId, destinationChainId); - /// @dev remap of address 0 to NATIVE because of how LiFi produces txData - if (sendingAssetId == address(0)) { - sendingAssetId = NATIVE; - } - /// @dev 3. token validations - if (args_.liqDataToken != sendingAssetId) revert Error.INVALID_TXDATA_TOKEN(); - } + return hasDstSwap; } /// @inheritdoc BridgeValidator @@ -123,53 +68,51 @@ contract LiFiValidator is BridgeValidator, LiFiTxDataExtractor { bool genericSwapDisallowed_ ) external - view + pure override returns (uint256 amount_) { - try this.extractMainParameters(txData_) returns ( - string memory, /*bridge*/ - address, /*sendingAssetId*/ - address, /*receiver*/ - uint256 amount, - uint256, /*minAmount*/ - uint256, /*destinationChainId*/ - bool, /*hasSourceSwaps*/ - bool /*hasDestinationCall*/ - ) { - /// @dev if there isn't a source swap, amountIn is minAmountOut from bridge data? - - amount_ = amount; - } catch { - if (genericSwapDisallowed_) revert Error.INVALID_ACTION(); - /// @dev in the case of a generic swap, amountIn is the from amount + bytes4 selector = _extractSelector(txData_); + /// @dev 1 - check if it is a swapTokensGeneric call (match via selector) + if (selector == GenericSwapFacet.swapTokensGeneric.selector) { + if (genericSwapDisallowed_) { + revert Error.INVALID_ACTION(); + } (, amount_,,,) = extractGenericSwapParameters(txData_); + return amount_; } + + /// @dev 2 - check if it is any other blacklisted selector + if (!_validateSelector(selector)) revert Error.BLACKLISTED_SELECTOR(); + + /// @dev 3 - proceed with normal extraction + (, /*bridgeId*/,, amount_, /*amount*/, /*minAmount*/,, /*hasSourceSwaps*/ ) = extractMainParameters(txData_); + /// @dev if there isn't a source swap, amount_ is minAmountOut from bridge data + + return amount_; } /// @inheritdoc BridgeValidator function decodeDstSwap(bytes calldata txData_) external pure override returns (address token_, uint256 amount_) { - (token_, amount_,,,) = extractGenericSwapParameters(txData_); + bytes4 selector = _extractSelector(txData_); + if (selector == GenericSwapFacet.swapTokensGeneric.selector) { + (token_, amount_,,,) = extractGenericSwapParameters(txData_); + return (token_, amount_); + } else { + revert Error.INVALID_ACTION(); + } } - function decodeSwapOutputToken(bytes calldata txData_) external view override returns (address token_) { - try this.extractMainParameters(txData_) returns ( - string memory, /*bridge*/ - address, /*sendingAssetId*/ - address, /*receiver*/ - uint256, /*amount*/ - uint256, /*minAmount*/ - uint256, /*destinationChainId*/ - bool, /*hasSourceSwaps*/ - bool /*hasDestinationCall*/ - ) { - /// @dev if there isn't a source swap, amountIn is minAmountOut from bridge data? + /// @inheritdoc BridgeValidator + function decodeSwapOutputToken(bytes calldata txData_) external pure override returns (address token_) { + bytes4 selector = _extractSelector(txData_); + if (selector == GenericSwapFacet.swapTokensGeneric.selector) { + (,,, token_,) = extractGenericSwapParameters(txData_); + return token_; + } else { revert Error.CANNOT_DECODE_FINAL_SWAP_OUTPUT_TOKEN(); - } catch { - (,,, address receivingAssetId,) = extractGenericSwapParameters(txData_); - token_ = receivingAssetId; } } @@ -197,7 +140,8 @@ contract LiFiValidator is BridgeValidator, LiFiTxDataExtractor { bool hasDestinationCall ) { - ILiFi.BridgeData memory bridgeData = _extractBridgeData(data_); + ILiFi.BridgeData memory bridgeData; + (bridgeData, receiver) = _extractBridgeData(data_); if (bridgeData.hasSourceSwaps) { LibSwap.SwapData[] memory swapData = _extractSwapData(data_); @@ -211,7 +155,7 @@ contract LiFiValidator is BridgeValidator, LiFiTxDataExtractor { return ( bridgeData.bridge, sendingAssetId, - bridgeData.receiver, + receiver, amount, minAmount, bridgeData.destinationChainId, @@ -252,6 +196,101 @@ contract LiFiValidator is BridgeValidator, LiFiTxDataExtractor { sendingAssetId = swapData[0].sendingAssetId; amount = swapData[0].fromAmount; receivingAssetId = swapData[swapData.length - 1].receivingAssetId; - return (sendingAssetId, amount, receiver, receivingAssetId, receivingAmount); + } + + function _validateMainParameters( + ValidateTxDataArgs calldata args_, + bool hasDestinationCall, + bool hasDstSwap, + address receiver, + address sendingAssetId, + uint256 destinationChainId + ) + internal + view + returns (bool) + { + /// @notice xchain actions can have bridgeData or swapData + bridgeData + + /// @dev 0. Destination call validation + if (hasDestinationCall) revert Error.INVALID_TXDATA_NO_DESTINATIONCALL_ALLOWED(); + + /// @dev 1. chainId validation + /// @dev for deposits, liqDstChainId/toChainId will be the normal destination (where the target superform + /// is) + /// @dev for withdraws, liqDstChainId will be the desired chain to where the underlying must be + /// sent (post any bridge/swap). To ChainId is where the target superform is + /// @dev to after vault redemption + + if (uint256(args_.liqDstChainId) != destinationChainId) revert Error.INVALID_TXDATA_CHAIN_ID(); + + /// @dev 2. receiver address validation + if (args_.deposit) { + if (args_.srcChainId == args_.dstChainId) { + revert Error.INVALID_ACTION(); + } else { + hasDstSwap = receiver == superRegistry.getAddressByChainId(keccak256("DST_SWAPPER"), args_.dstChainId); + /// @dev if cross chain deposits, then receiver address must be CoreStateRegistry (or) Dst Swapper + if ( + !( + receiver + == superRegistry.getAddressByChainId(keccak256("CORE_STATE_REGISTRY"), args_.dstChainId) + || hasDstSwap + ) + ) { + revert Error.INVALID_TXDATA_RECEIVER(); + } + + /// @dev forbid xChain deposits with destination swaps without interim token set (for user + /// protection) + if (hasDstSwap && args_.liqDataInterimToken == address(0)) { + revert Error.INVALID_INTERIM_TOKEN(); + } + } + } else { + /// @dev if withdraws, then receiver address must be the receiverAddress + if (receiver != args_.receiverAddress) revert Error.INVALID_TXDATA_RECEIVER(); + } + + /// @dev remap of address 0 to NATIVE because of how LiFi produces txData + if (sendingAssetId == address(0)) { + sendingAssetId = NATIVE; + } + /// @dev 3. token validations + if (args_.liqDataToken != sendingAssetId) revert Error.INVALID_TXDATA_TOKEN(); + + return hasDstSwap; + } + + function _validateGenericParameters( + ValidateTxDataArgs calldata args_, + address receiver, + address sendingAssetId + ) + internal + pure + { + /// @notice direct actions with deposit, cannot have bridge data + /// @notice withdraw actions without bridge data (just swap) also fall in GenericSwap + if (args_.deposit) { + /// @dev 1. chainId validation + if (args_.srcChainId != args_.dstChainId) revert Error.INVALID_TXDATA_CHAIN_ID(); + if (args_.dstChainId != args_.liqDstChainId) revert Error.INVALID_DEPOSIT_LIQ_DST_CHAIN_ID(); + + /// @dev 2. receiver address validation + /// @dev If same chain deposits then receiver address must be the superform + if (receiver != args_.superform) revert Error.INVALID_TXDATA_RECEIVER(); + } else { + /// @dev 2. receiver address validation + /// @dev if withdraws, then receiver address must be the receiverAddress + if (receiver != args_.receiverAddress) revert Error.INVALID_TXDATA_RECEIVER(); + } + + /// @dev remap of address 0 to NATIVE because of how LiFi produces txData + if (sendingAssetId == address(0)) { + sendingAssetId = NATIVE; + } + /// @dev 3. token validations + if (args_.liqDataToken != sendingAssetId) revert Error.INVALID_TXDATA_TOKEN(); } } diff --git a/src/crosschain-liquidity/socket/SocketOneInchValidator.sol b/src/crosschain-liquidity/socket/SocketOneInchValidator.sol index 787824ddb..e4b55a2d6 100644 --- a/src/crosschain-liquidity/socket/SocketOneInchValidator.sol +++ b/src/crosschain-liquidity/socket/SocketOneInchValidator.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { Error } from "src/libraries/Error.sol"; import { BridgeValidator } from "src/crosschain-liquidity/BridgeValidator.sol"; +import { Error } from "src/libraries/Error.sol"; import { ISocketOneInchImpl } from "src/vendor/socket/ISocketOneInchImpl.sol"; /// @title SocketOneInchValidator +/// @dev Asserts Socket same-chain txData is valid /// @author Zeropoint Labs -/// @dev to assert input txData is valid contract SocketOneInchValidator is BridgeValidator { + ////////////////////////////////////////////////////////////// // CONSTRUCTOR // ////////////////////////////////////////////////////////////// @@ -28,21 +29,21 @@ contract SocketOneInchValidator is BridgeValidator { function validateTxData(ValidateTxDataArgs calldata args_) external pure override returns (bool) { ISocketOneInchImpl.SwapInput memory decodedReq = _decodeTxData(args_.txData); - /// @dev 1. chain id validation (only allow samechain with this) - if (args_.dstChainId != args_.srcChainId) revert Error.INVALID_ACTION(); - - /// @dev 2. receiver address validation if (args_.deposit) { + /// @dev 1. chain id validation (only allow samechain with this) + if (args_.dstChainId != args_.srcChainId) revert Error.INVALID_TXDATA_CHAIN_ID(); if (args_.dstChainId != args_.liqDstChainId) revert Error.INVALID_DEPOSIT_LIQ_DST_CHAIN_ID(); + /// @dev 2. receiver address validation /// @dev If same chain deposits then receiver address must be the superform if (decodedReq.receiver != args_.superform) revert Error.INVALID_TXDATA_RECEIVER(); } else { - /// @dev if withdraws, then receiver address must be the srcSender - if (decodedReq.receiver != args_.srcSender) revert Error.INVALID_TXDATA_RECEIVER(); + /// @dev 2. receiver address validation + /// @dev if withdraws, then receiver address must be the receiverAddress + if (decodedReq.receiver != args_.receiverAddress) revert Error.INVALID_TXDATA_RECEIVER(); } - /// @dev FIXME: add 3. token validations + /// @dev 3. token validations if (args_.liqDataToken != decodedReq.fromToken) revert Error.INVALID_TXDATA_TOKEN(); return false; @@ -91,7 +92,7 @@ contract SocketOneInchValidator is BridgeValidator { } /// @dev helps parsing socket calldata and return the socket request - function _parseCallData(bytes calldata callData) internal pure returns (bytes memory) { + function _parseCallData(bytes calldata callData) internal pure returns (bytes calldata) { return callData[4:]; } } diff --git a/src/crosschain-liquidity/socket/SocketValidator.sol b/src/crosschain-liquidity/socket/SocketValidator.sol index d27ae5cc8..7097d05e8 100644 --- a/src/crosschain-liquidity/socket/SocketValidator.sol +++ b/src/crosschain-liquidity/socket/SocketValidator.sol @@ -1,17 +1,19 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { Error } from "src/libraries/Error.sol"; import { BridgeValidator } from "src/crosschain-liquidity/BridgeValidator.sol"; +import { Error } from "src/libraries/Error.sol"; import { ISocketRegistry } from "src/vendor/socket/ISocketRegistry.sol"; /// @title SocketValidator +/// @dev Asserts Socket x-chain input txData is valid /// @author Zeropoint Labs -/// @dev to assert input txData is valid contract SocketValidator is BridgeValidator { + ////////////////////////////////////////////////////////////// // CONSTRUCTOR // ////////////////////////////////////////////////////////////// + constructor(address superRegistry_) BridgeValidator(superRegistry_) { } ////////////////////////////////////////////////////////////// @@ -60,11 +62,11 @@ contract SocketValidator is BridgeValidator { } } } else { - /// @dev if withdraws, then receiver address must be the srcSender - if (decodedReq.receiverAddress != args_.srcSender) revert Error.INVALID_TXDATA_RECEIVER(); + /// @dev if withdraws, then receiver address must be the receiverAddress + if (decodedReq.receiverAddress != args_.receiverAddress) revert Error.INVALID_TXDATA_RECEIVER(); } - /// @dev FIXME: add 3. token validations + /// @dev token validations if ( (decodedReq.middlewareRequest.id == 0 && args_.liqDataToken != decodedReq.bridgeRequest.inputToken) || (decodedReq.middlewareRequest.id != 0 && args_.liqDataToken != decodedReq.middlewareRequest.inputToken) @@ -91,10 +93,13 @@ contract SocketValidator is BridgeValidator { override returns (address, /*token_*/ uint256 /*amount_*/ ) { - revert(); + /// @dev SocketValidator cannot be used for just swaps, see SocketOneinchValidator + revert Error.CANNOT_DECODE_FINAL_SWAP_OUTPUT_TOKEN(); } + /// @inheritdoc BridgeValidator function decodeSwapOutputToken(bytes calldata /*txData_*/ ) external pure override returns (address /*token_*/ ) { + /// @dev SocketValidator cannot be used for just swaps, see SocketOneinchValidator revert Error.CANNOT_DECODE_FINAL_SWAP_OUTPUT_TOKEN(); } @@ -113,7 +118,7 @@ contract SocketValidator is BridgeValidator { } /// @dev helps parsing socket calldata and return the socket request - function _parseCallData(bytes calldata callData) internal pure returns (bytes memory) { + function _parseCallData(bytes calldata callData) internal pure returns (bytes calldata) { return callData[4:]; } } diff --git a/src/forms/ERC4626Form.sol b/src/forms/ERC4626Form.sol index 1b62df457..a9c537b2f 100644 --- a/src/forms/ERC4626Form.sol +++ b/src/forms/ERC4626Form.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { InitSingleVaultData } from "../types/DataTypes.sol"; -import { ERC4626FormImplementation } from "./ERC4626FormImplementation.sol"; -import { BaseForm } from "../BaseForm.sol"; +import { ERC4626FormImplementation } from "src/forms/ERC4626FormImplementation.sol"; +import { BaseForm } from "src/BaseForm.sol"; +import { InitSingleVaultData } from "src/types/DataTypes.sol"; /// @title ERC4626Form -/// @notice The Form implementation for IERC4626 vaults +/// @dev The Form implementation for normal ERC4626 vaults +/// @author Zeropoint Labs contract ERC4626Form is ERC4626FormImplementation { ////////////////////////////////////////////////////////////// // CONSTANTS // @@ -31,56 +32,56 @@ contract ERC4626Form is ERC4626FormImplementation { ) internal override - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processDirectDeposit(singleVaultData_); + shares = _processDirectDeposit(singleVaultData_); } /// @inheritdoc BaseForm function _xChainDepositIntoVault( InitSingleVaultData memory singleVaultData_, - address, + address, /*srcSender_*/ uint64 srcChainId_ ) internal override - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processXChainDeposit(singleVaultData_, srcChainId_); + shares = _processXChainDeposit(singleVaultData_, srcChainId_); } /// @inheritdoc BaseForm function _directWithdrawFromVault( InitSingleVaultData memory singleVaultData_, - address srcSender_ + address /*srcSender_*/ ) internal override - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _processDirectWithdraw(singleVaultData_, srcSender_); + assets = _processDirectWithdraw(singleVaultData_); } /// @inheritdoc BaseForm function _xChainWithdrawFromVault( InitSingleVaultData memory singleVaultData_, - address srcSender_, + address, /*srcSender_*/ uint64 srcChainId_ ) internal override - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _processXChainWithdraw(singleVaultData_, srcSender_, srcChainId_); + assets = _processXChainWithdraw(singleVaultData_, srcChainId_); } /// @inheritdoc BaseForm - function _emergencyWithdraw(address, /*srcSender_*/ address refundAddress_, uint256 amount_) internal override { - _processEmergencyWithdraw(refundAddress_, amount_); + function _emergencyWithdraw(address receiverAddress_, uint256 amount_) internal override { + _processEmergencyWithdraw(receiverAddress_, amount_); } /// @inheritdoc BaseForm - function _forwardDustToPaymaster() internal override { - _processForwardDustToPaymaster(); + function _forwardDustToPaymaster(address token_) internal override { + _processForwardDustToPaymaster(token_); } } diff --git a/src/forms/ERC4626FormImplementation.sol b/src/forms/ERC4626FormImplementation.sol index e183ac5aa..ee752acf5 100644 --- a/src/forms/ERC4626FormImplementation.sol +++ b/src/forms/ERC4626FormImplementation.sol @@ -1,20 +1,22 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { BaseForm } from "src/BaseForm.sol"; +import { LiquidityHandler } from "src/crosschain-liquidity/LiquidityHandler.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { Error } from "src/libraries/Error.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { InitSingleVaultData } from "src/types/DataTypes.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import { IERC20Metadata } from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC4626 } from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; -import { LiquidityHandler } from "../crosschain-liquidity/LiquidityHandler.sol"; -import { InitSingleVaultData } from "../types/DataTypes.sol"; -import { BaseForm } from "../BaseForm.sol"; -import { IBridgeValidator } from "../interfaces/IBridgeValidator.sol"; -import { Error } from "../libraries/Error.sol"; -import { DataLib } from "../libraries/DataLib.sol"; /// @title ERC4626FormImplementation -/// @notice Has common internal functions that can be re-used by actual form implementations +/// @dev Has common ERC4626 internal functions that can be re-used by implementations +/// @author Zeropoint Labs abstract contract ERC4626FormImplementation is BaseForm, LiquidityHandler { + using SafeERC20 for IERC20; using SafeERC20 for IERC4626; using DataLib for uint256; @@ -24,16 +26,17 @@ abstract contract ERC4626FormImplementation is BaseForm, LiquidityHandler { ////////////////////////////////////////////////////////////// uint8 internal immutable STATE_REGISTRY_ID; + uint256 internal constant ENTIRE_SLIPPAGE = 10_000; ////////////////////////////////////////////////////////////// // STRUCTS // ////////////////////////////////////////////////////////////// - struct directDepositLocalVars { + struct DirectDepositLocalVars { uint64 chainId; address asset; address bridgeValidator; - uint256 dstAmount; + uint256 shares; uint256 balanceBefore; uint256 assetDifference; uint256 nonce; @@ -42,19 +45,15 @@ abstract contract ERC4626FormImplementation is BaseForm, LiquidityHandler { bytes signature; } - struct directWithdrawLocalVars { + struct DirectWithdrawLocalVars { uint64 chainId; address asset; - address receiver; address bridgeValidator; - uint256 len1; uint256 amount; - IERC4626 v; } - struct xChainWithdrawLocalVars { + struct XChainWithdrawLocalVars { uint64 dstChainId; - address receiver; address asset; address bridgeValidator; uint256 balanceBefore; @@ -153,8 +152,8 @@ abstract contract ERC4626FormImplementation is BaseForm, LiquidityHandler { // INTERNAL FUNCTIONS // ////////////////////////////////////////////////////////////// - function _processDirectDeposit(InitSingleVaultData memory singleVaultData_) internal returns (uint256 dstAmount) { - directDepositLocalVars memory vars; + function _processDirectDeposit(InitSingleVaultData memory singleVaultData_) internal returns (uint256 shares) { + DirectDepositLocalVars memory vars; IERC4626 v = IERC4626(vault); vars.asset = address(asset); @@ -167,7 +166,7 @@ abstract contract ERC4626FormImplementation is BaseForm, LiquidityHandler { /// @dev handles the asset token transfers. if (token.allowance(msg.sender, address(this)) < singleVaultData_.amount) { - revert Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE(); + revert Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT(); } /// @dev transfers input token, which is the same as vault asset, to the form @@ -187,7 +186,7 @@ abstract contract ERC4626FormImplementation is BaseForm, LiquidityHandler { if (address(token) != NATIVE) { /// @dev checks the allowance before transfer from router if (token.allowance(msg.sender, address(this)) < vars.inputAmount) { - revert Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE(); + revert Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT(); } /// @dev transfers input token, which is different from the vault asset, to the form @@ -228,19 +227,19 @@ abstract contract ERC4626FormImplementation is BaseForm, LiquidityHandler { /// @dev the difference in vault tokens, ready to be deposited, is compared with the amount inscribed in the /// superform data - if (vars.assetDifference < singleVaultData_.amount) { - revert Error.DIRECT_DEPOSIT_INVALID_DATA(); + if ( + vars.assetDifference * ENTIRE_SLIPPAGE + < singleVaultData_.amount * (ENTIRE_SLIPPAGE - singleVaultData_.maxSlippage) + ) { + revert Error.DIRECT_DEPOSIT_SWAP_FAILED(); } /// @dev notice that vars.assetDifference is deposited regardless if txData exists or not /// @dev this presumes no dust is left in the superform IERC20(vars.asset).safeIncreaseAllowance(vault, vars.assetDifference); - if (singleVaultData_.retain4626) { - dstAmount = v.deposit(vars.assetDifference, singleVaultData_.receiverAddress); - } else { - dstAmount = v.deposit(vars.assetDifference, address(this)); - } + /// @dev deposit assets for shares and add extra validation check to ensure intended ERC4626 behavior + shares = _depositAndValidate(singleVaultData_, v, vars.assetDifference); } function _processXChainDeposit( @@ -248,174 +247,249 @@ abstract contract ERC4626FormImplementation is BaseForm, LiquidityHandler { uint64 srcChainId_ ) internal - returns (uint256 dstAmount) + returns (uint256 shares) { (,, uint64 dstChainId) = singleVaultData_.superformId.getSuperform(); address vaultLoc = vault; IERC4626 v = IERC4626(vaultLoc); + if (IERC20(asset).allowance(msg.sender, address(this)) < singleVaultData_.amount) { + revert Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT(); + } + /// @dev pulling from sender, to auto-send tokens back in case of failed deposits / reverts IERC20(asset).safeTransferFrom(msg.sender, address(this), singleVaultData_.amount); /// @dev allowance is modified inside of the IERC20.transferFrom() call IERC20(asset).safeIncreaseAllowance(vaultLoc, singleVaultData_.amount); - /// @dev Deposit into vault - if (singleVaultData_.retain4626) { - dstAmount = v.deposit(singleVaultData_.amount, singleVaultData_.receiverAddress); - } else { - /// This makes ERC4626Form (address(this)) owner of v.shares - dstAmount = v.deposit(singleVaultData_.amount, address(this)); - } + /// @dev deposit assets for shares and add extra validation check to ensure intended ERC4626 behavior + shares = _depositAndValidate(singleVaultData_, v, singleVaultData_.amount); emit Processed(srcChainId_, dstChainId, singleVaultData_.payloadId, singleVaultData_.amount, vaultLoc); } - function _processDirectWithdraw( - InitSingleVaultData memory singleVaultData_, - address srcSender_ - ) - internal - returns (uint256 dstAmount) - { - directWithdrawLocalVars memory v; - v.len1 = singleVaultData_.liqData.txData.length; + function _processDirectWithdraw(InitSingleVaultData memory singleVaultData_) internal returns (uint256 assets) { + + DirectWithdrawLocalVars memory vars; - /// @dev if there is no txData, on withdraws the receiver is the original beneficiary (srcSender_), otherwise it + /// @dev if there is no txData, on withdraws the receiver is receiverAddress, otherwise it /// is this contract (before swap) - v.receiver = v.len1 == 0 ? srcSender_ : address(this); - - v.v = IERC4626(vault); - v.asset = address(asset); - /// @dev redeem the underlying - dstAmount = v.v.redeem(singleVaultData_.amount, v.receiver, address(this)); + IERC4626 v = IERC4626(vault); + IERC20 a = IERC20(asset); - if (v.len1 != 0) { - /// @dev the token we are swapping from to our desired output token (if there is txData), must be the same - /// as the vault asset - if (singleVaultData_.liqData.token != v.asset) revert Error.DIRECT_WITHDRAW_INVALID_TOKEN(); + if (!singleVaultData_.retain4626) { + vars.asset = address(asset); - v.bridgeValidator = superRegistry.getBridgeValidator(singleVaultData_.liqData.bridgeId); - v.amount = IBridgeValidator(v.bridgeValidator).decodeAmountIn(singleVaultData_.liqData.txData, false); + /// @dev redeem shares for assets and add extra validation check to ensure intended ERC4626 behavior + assets = _withdrawAndValidate(singleVaultData_, v, a); - /// @dev the amount inscribed in liqData must be less or equal than the amount redeemed from the vault - if (v.amount > dstAmount) revert Error.DIRECT_WITHDRAW_INVALID_LIQ_REQUEST(); + if (singleVaultData_.liqData.txData.length != 0) { + vars.bridgeValidator = superRegistry.getBridgeValidator(singleVaultData_.liqData.bridgeId); + vars.amount = + IBridgeValidator(vars.bridgeValidator).decodeAmountIn(singleVaultData_.liqData.txData, false); - v.chainId = CHAIN_ID; + /// @dev the amount inscribed in liqData must be less or equal than the amount redeemed from the vault + /// @dev if less it should be within the slippage limit specified by the user + /// @dev important to maintain so that the keeper cannot update with malicious data after successful + /// withdraw + if (_isWithdrawTxDataAmountInvalid(vars.amount, assets, singleVaultData_.maxSlippage)) { + revert Error.DIRECT_WITHDRAW_INVALID_LIQ_REQUEST(); + } - /// @dev validate and perform the swap to desired output token and send to beneficiary - IBridgeValidator(v.bridgeValidator).validateTxData( - IBridgeValidator.ValidateTxDataArgs( + vars.chainId = CHAIN_ID; + + /// @dev validate and perform the swap to desired output token and send to beneficiary + IBridgeValidator(vars.bridgeValidator).validateTxData( + IBridgeValidator.ValidateTxDataArgs( + singleVaultData_.liqData.txData, + vars.chainId, + vars.chainId, + singleVaultData_.liqData.liqDstChainId, + false, + address(this), + singleVaultData_.receiverAddress, + vars.asset, + address(0) + ) + ); + + _dispatchTokens( + superRegistry.getBridgeAddress(singleVaultData_.liqData.bridgeId), singleVaultData_.liqData.txData, - v.chainId, - v.chainId, - singleVaultData_.liqData.liqDstChainId, - false, - address(this), - srcSender_, - singleVaultData_.liqData.token, - address(0) - ) - ); - - _dispatchTokens( - superRegistry.getBridgeAddress(singleVaultData_.liqData.bridgeId), - singleVaultData_.liqData.txData, - singleVaultData_.liqData.token, - v.amount, - singleVaultData_.liqData.nativeAmount - ); + vars.asset, + vars.amount, + singleVaultData_.liqData.nativeAmount + ); + } + } else { + /// @dev transfer shares to user and do not redeem shares for assets + v.safeTransfer(singleVaultData_.receiverAddress, singleVaultData_.amount); + return 0; } } function _processXChainWithdraw( InitSingleVaultData memory singleVaultData_, - address, /*srcSender_*/ uint64 srcChainId_ ) internal - returns (uint256 dstAmount) + returns (uint256 assets) { - uint256 len = singleVaultData_.liqData.txData.length; + XChainWithdrawLocalVars memory vars; + uint256 len = singleVaultData_.liqData.txData.length; /// @dev a case where the withdraw req liqData has a valid token and tx data is not updated by the keeper if (singleVaultData_.liqData.token != address(0) && len == 0) { revert Error.WITHDRAW_TX_DATA_NOT_UPDATED(); + } else if (singleVaultData_.liqData.token == address(0) && len != 0) { + revert Error.WITHDRAW_TOKEN_NOT_UPDATED(); } - xChainWithdrawLocalVars memory vars; (,, vars.dstChainId) = singleVaultData_.superformId.getSuperform(); - /// @dev receiverAddress is checked for existence on source - /// @dev user will either provide an address equal to msg.sender (if EOA) - /// @dev or user will specify an address on the target chain for the collateral extraction (if Smart Contract - /// Wallet) - vars.receiver = len == 0 ? singleVaultData_.receiverAddress : address(this); - IERC4626 v = IERC4626(vault); - vars.asset = asset; - - /// @dev redeem vault positions (we operate only on positions, not assets) - dstAmount = v.redeem(singleVaultData_.amount, vars.receiver, address(this)); - - if (len != 0) { - /// @dev the token we are swapping from to our desired output token (if there is txData), must be the same - /// as the vault asset - if (vars.asset != singleVaultData_.liqData.token) revert Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); + IERC20 a = IERC20(asset); + if (!singleVaultData_.retain4626) { + vars.asset = address(asset); + + /// @dev redeem shares for assets and add extra validation check to ensure intended ERC4626 behavior + assets = _withdrawAndValidate(singleVaultData_, v, a); + + if (len != 0) { + vars.bridgeValidator = superRegistry.getBridgeValidator(singleVaultData_.liqData.bridgeId); + vars.amount = + IBridgeValidator(vars.bridgeValidator).decodeAmountIn(singleVaultData_.liqData.txData, false); + + /// @dev the amount inscribed in liqData must be less or equal than the amount redeemed from the vault + /// @dev if less it should be within the slippage limit specified by the user + /// @dev important to maintain so that the keeper cannot update with malicious data after successful + /// withdraw + if (_isWithdrawTxDataAmountInvalid(vars.amount, assets, singleVaultData_.maxSlippage)) { + revert Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); + } - vars.bridgeValidator = superRegistry.getBridgeValidator(singleVaultData_.liqData.bridgeId); - vars.amount = IBridgeValidator(vars.bridgeValidator).decodeAmountIn(singleVaultData_.liqData.txData, false); + /// @dev validate and perform the swap to desired output token and send to beneficiary + IBridgeValidator(vars.bridgeValidator).validateTxData( + IBridgeValidator.ValidateTxDataArgs( + singleVaultData_.liqData.txData, + vars.dstChainId, + srcChainId_, + singleVaultData_.liqData.liqDstChainId, + false, + address(this), + singleVaultData_.receiverAddress, + vars.asset, + address(0) + ) + ); + + _dispatchTokens( + superRegistry.getBridgeAddress(singleVaultData_.liqData.bridgeId), + singleVaultData_.liqData.txData, + vars.asset, + vars.amount, + singleVaultData_.liqData.nativeAmount + ); + } + } else { + /// @dev transfer shares to user and do not redeem shares for assets + v.safeTransfer(singleVaultData_.receiverAddress, singleVaultData_.amount); + return 0; + } - /// @dev the amount inscribed in liqData must be less or equal than the amount redeemed from the vault - if (vars.amount > dstAmount) revert Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); + emit Processed(srcChainId_, vars.dstChainId, singleVaultData_.payloadId, singleVaultData_.amount, vault); + } - /// @dev validate and perform the swap to desired output token and send to beneficiary - IBridgeValidator(vars.bridgeValidator).validateTxData( - IBridgeValidator.ValidateTxDataArgs( - singleVaultData_.liqData.txData, - vars.dstChainId, - srcChainId_, - singleVaultData_.liqData.liqDstChainId, - false, - address(this), - singleVaultData_.receiverAddress, - singleVaultData_.liqData.token, - address(0) + function _depositAndValidate( + InitSingleVaultData memory singleVaultData_, + IERC4626 v, + uint256 assetDifference + ) + internal + returns (uint256 shares) + { + address sharesReceiver = singleVaultData_.retain4626 ? singleVaultData_.receiverAddress : address(this); + uint256 sharesBalanceBefore = v.balanceOf(sharesReceiver); + shares = v.deposit(assetDifference, sharesReceiver); + uint256 sharesBalanceAfter = v.balanceOf(sharesReceiver); + if ( + (sharesBalanceAfter - sharesBalanceBefore != shares) + || ( + ENTIRE_SLIPPAGE * shares + < ((singleVaultData_.outputAmount * (ENTIRE_SLIPPAGE - singleVaultData_.maxSlippage))) ) - ); + ) { + revert Error.VAULT_IMPLEMENTATION_FAILED(); + } + } - _dispatchTokens( - superRegistry.getBridgeAddress(singleVaultData_.liqData.bridgeId), - singleVaultData_.liqData.txData, - singleVaultData_.liqData.token, - vars.amount, - singleVaultData_.liqData.nativeAmount - ); + function _withdrawAndValidate( + InitSingleVaultData memory singleVaultData_, + IERC4626 v, + IERC20 a + ) + internal + returns (uint256 assets) + { + address assetsReceiver = + singleVaultData_.liqData.txData.length == 0 ? singleVaultData_.receiverAddress : address(this); + uint256 assetsBalanceBefore = a.balanceOf(assetsReceiver); + assets = v.redeem(singleVaultData_.amount, assetsReceiver, address(this)); + uint256 assetsBalanceAfter = a.balanceOf(assetsReceiver); + if ( + (assetsBalanceAfter - assetsBalanceBefore != assets) + || ( + ENTIRE_SLIPPAGE * assets + < ((singleVaultData_.outputAmount * (ENTIRE_SLIPPAGE - singleVaultData_.maxSlippage))) + ) + ) { + revert Error.VAULT_IMPLEMENTATION_FAILED(); } - emit Processed(srcChainId_, vars.dstChainId, singleVaultData_.payloadId, singleVaultData_.amount, vault); + if (assets == 0) revert Error.WITHDRAW_ZERO_COLLATERAL(); } - function _processEmergencyWithdraw(address refundAddress_, uint256 amount_) internal { - IERC4626 vaultContract = IERC4626(vault); + function _isWithdrawTxDataAmountInvalid( + uint256 bridgeDecodedAmount_, + uint256 redeemedAmount_, + uint256 slippage_ + ) + internal + pure + returns (bool isInvalid) + { + if ( + bridgeDecodedAmount_ > redeemedAmount_ + || ((bridgeDecodedAmount_ * ENTIRE_SLIPPAGE) < (redeemedAmount_ * (ENTIRE_SLIPPAGE - slippage_))) + ) return true; + } - if (vaultContract.balanceOf(address(this)) < amount_) { + function _processEmergencyWithdraw(address receiverAddress_, uint256 amount_) internal { + IERC4626 v = IERC4626(vault); + if (receiverAddress_ == address(0)) revert Error.ZERO_ADDRESS(); + + if (v.balanceOf(address(this)) < amount_) { revert Error.INSUFFICIENT_BALANCE(); } - vaultContract.safeTransfer(refundAddress_, amount_); - emit EmergencyWithdrawalProcessed(refundAddress_, amount_); + v.safeTransfer(receiverAddress_, amount_); + + emit EmergencyWithdrawalProcessed(receiverAddress_, amount_); } - function _processForwardDustToPaymaster() internal { + function _processForwardDustToPaymaster(address token_) internal { + if (token_ == address(0)) revert Error.ZERO_ADDRESS(); + address paymaster = superRegistry.getAddress(keccak256("PAYMASTER")); - IERC20 token = IERC20(getVaultAsset()); + IERC20 token = IERC20(token_); uint256 dust = token.balanceOf(address(this)); if (dust != 0) { token.safeTransfer(paymaster, dust); + emit FormDustForwardedToPaymaster(token_, dust); } } } diff --git a/src/forms/ERC4626KYCDaoForm.sol b/src/forms/ERC4626KYCDaoForm.sol index b02c999e1..1c60e4850 100644 --- a/src/forms/ERC4626KYCDaoForm.sol +++ b/src/forms/ERC4626KYCDaoForm.sol @@ -1,16 +1,19 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { ERC4626FormImplementation } from "src/forms/ERC4626FormImplementation.sol"; +import { BaseForm } from "src/BaseForm.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { IKycdaoNTNFT } from "src/vendor/kycDAO/IKycDAONTNFT.sol"; +import { Error } from "src/libraries/Error.sol"; +import { InitSingleVaultData } from "src/types/DataTypes.sol"; import { kycDAO4626 } from "super-vaults/kycdao-4626/kycdao4626.sol"; -import { InitSingleVaultData } from "../types/DataTypes.sol"; -import { ERC4626FormImplementation } from "./ERC4626FormImplementation.sol"; -import { BaseForm } from "../BaseForm.sol"; -import { Error } from "../libraries/Error.sol"; +import { ERC721Holder } from "openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol"; /// @title ERC4626KYCDaoForm -/// @notice The Form implementation for IERC4626 vaults with kycDAO NFT checks -/// @notice This form must hold a kycDAO NFT to operate -contract ERC4626KYCDaoForm is ERC4626FormImplementation { +/// @dev The Form implementation for kycDAO-gated ERC4626 vaults, must hold kycDAO NFT +/// @author Zeropoint Labs +contract ERC4626KYCDaoForm is ERC4626FormImplementation, ERC721Holder { ////////////////////////////////////////////////////////////// // CONSTANTS // ////////////////////////////////////////////////////////////// @@ -36,6 +39,20 @@ contract ERC4626KYCDaoForm is ERC4626FormImplementation { _; } + modifier onlyProtocolAdmin() { + if (!ISuperRBAC(superRegistry.getAddress(keccak256("SUPER_RBAC"))).hasProtocolAdminRole(msg.sender)) { + revert Error.NOT_PROTOCOL_ADMIN(); + } + _; + } + ////////////////////////////////////////////////////////////// + // EXTERNAL ADMIN FUNCTIONS // + ////////////////////////////////////////////////////////////// + + function mintKYC(uint32 authCode_) external onlyProtocolAdmin { + IKycdaoNTNFT(address(kycDAO4626(vault).kycValidity())).mintWithCode(authCode_); + } + ////////////////////////////////////////////////////////////// // INTERNAL FUNCTIONS // ////////////////////////////////////////////////////////////// @@ -48,9 +65,9 @@ contract ERC4626KYCDaoForm is ERC4626FormImplementation { internal override onlyKYC(srcSender_) - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processDirectDeposit(singleVaultData_); + shares = _processDirectDeposit(singleVaultData_); } function _xChainDepositIntoVault( @@ -61,7 +78,7 @@ contract ERC4626KYCDaoForm is ERC4626FormImplementation { internal pure override - returns (uint256 /*dstAmount*/ ) + returns (uint256 /*shares*/ ) { revert Error.NOT_IMPLEMENTED(); } @@ -74,9 +91,9 @@ contract ERC4626KYCDaoForm is ERC4626FormImplementation { internal override onlyKYC(srcSender_) - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _processDirectWithdraw(singleVaultData_, srcSender_); + assets = _processDirectWithdraw(singleVaultData_); } /// @inheritdoc BaseForm @@ -88,26 +105,18 @@ contract ERC4626KYCDaoForm is ERC4626FormImplementation { internal pure override - returns (uint256 /*dstAmount*/ ) + returns (uint256 /*assets*/ ) { revert Error.NOT_IMPLEMENTED(); } /// @inheritdoc BaseForm - function _emergencyWithdraw( - address srcSender_, - address refundAddress_, - uint256 amount_ - ) - internal - override - onlyKYC(srcSender_) - { - _processEmergencyWithdraw(refundAddress_, amount_); + function _emergencyWithdraw(address receiverAddress_, uint256 amount_) internal override { + _processEmergencyWithdraw(receiverAddress_, amount_); } /// @inheritdoc BaseForm - function _forwardDustToPaymaster() internal override { - _processForwardDustToPaymaster(); + function _forwardDustToPaymaster(address token_) internal override { + _processForwardDustToPaymaster(token_); } } diff --git a/src/forms/ERC4626TimelockForm.sol b/src/forms/ERC4626TimelockForm.sol index 6f1e3e852..e40f51b8e 100644 --- a/src/forms/ERC4626TimelockForm.sol +++ b/src/forms/ERC4626TimelockForm.sol @@ -1,22 +1,24 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { ERC4626FormImplementation } from "src/forms/ERC4626FormImplementation.sol"; +import { BaseForm } from "src/BaseForm.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { ITimelockStateRegistry } from "src/interfaces/ITimelockStateRegistry.sol"; +import { IEmergencyQueue } from "src/interfaces/IEmergencyQueue.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { Error } from "src/libraries/Error.sol"; +import { InitSingleVaultData, TimelockPayload, LiqRequest } from "src/types/DataTypes.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC4626TimelockVault } from "super-vaults/interfaces/IERC4626TimelockVault.sol"; -import { InitSingleVaultData, TimelockPayload, LiqRequest } from "../types/DataTypes.sol"; -import { ERC4626FormImplementation } from "./ERC4626FormImplementation.sol"; -import { BaseForm } from "../BaseForm.sol"; -import { IBridgeValidator } from "../interfaces/IBridgeValidator.sol"; -import { ITimelockStateRegistry } from "../interfaces/ITimelockStateRegistry.sol"; -import { IEmergencyQueue } from "../interfaces/IEmergencyQueue.sol"; -import { DataLib } from "../libraries/DataLib.sol"; -import { Error } from "../libraries/Error.sol"; /// @title ERC4626TimelockForm -/// @notice Form implementation to handle timelock extension for ERC4626 vaults +/// @dev Form implementation to handle timelock extension for ERC4626 vaults +/// @author Zeropoint Labs contract ERC4626TimelockForm is ERC4626FormImplementation { using SafeERC20 for IERC20; + using SafeERC20 for IERC4626TimelockVault; using DataLib for uint256; ////////////////////////////////////////////////////////////// @@ -29,11 +31,12 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { // STRUCTS // ////////////////////////////////////////////////////////////// - struct withdrawAfterCoolDownLocalVars { + struct WithdrawAfterCoolDownLocalVars { uint256 len1; address bridgeValidator; uint64 chainId; address receiver; + address asset; uint256 amount; LiqRequest liqData; } @@ -60,19 +63,23 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { ////////////////////////////////////////////////////////////// /// @dev this function is called when the timelock deposit is ready to be withdrawn after being unlocked + /// @dev retain4626 flag is not added in this implementation unlike in ERC4626Implementation.sol because + /// @dev if a vault fails to redeem at this stage, superPositions are minted back to the user and he can + /// @dev try again with retain4626 flag set and take their shares directly /// @param p_ the payload data function withdrawAfterCoolDown(TimelockPayload memory p_) external onlyTimelockStateRegistry - returns (uint256 dstAmount) + returns (uint256 assets) { + if (p_.data.receiverAddress == address(0)) revert Error.RECEIVER_ADDRESS_NOT_SET(); + if (_isPaused(p_.data.superformId)) { - IEmergencyQueue(superRegistry.getAddress(keccak256("EMERGENCY_QUEUE"))).queueWithdrawal( - p_.data, p_.srcSender - ); + IEmergencyQueue(superRegistry.getAddress(keccak256("EMERGENCY_QUEUE"))).queueWithdrawal(p_.data); + return 0; } - withdrawAfterCoolDownLocalVars memory vars; + WithdrawAfterCoolDownLocalVars memory vars; IERC4626TimelockVault v = IERC4626TimelockVault(vault); @@ -89,14 +96,34 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { /// @dev if the txData is empty, the tokens are sent directly to the sender, otherwise sent first to this form vars.receiver = vars.len1 == 0 ? p_.data.receiverAddress : address(this); - dstAmount = v.redeem(p_.data.amount, vars.receiver, address(this)); + /// @dev redeem from vault + vars.asset = asset; + IERC20 assetERC = IERC20(vars.asset); + + uint256 assetsBalanceBefore = assetERC.balanceOf(vars.receiver); + + assets = v.redeem(p_.data.amount, vars.receiver, address(this)); + uint256 assetsBalanceAfter = assetERC.balanceOf(vars.receiver); + + if ( + (assetsBalanceAfter - assetsBalanceBefore != assets) + || (assets * ENTIRE_SLIPPAGE < p_.data.outputAmount * (ENTIRE_SLIPPAGE - p_.data.maxSlippage)) + ) { + revert Error.VAULT_IMPLEMENTATION_FAILED(); + } + + if (assets == 0) revert Error.WITHDRAW_ZERO_COLLATERAL(); + /// @dev validate and dispatches the tokens if (vars.len1 != 0) { vars.bridgeValidator = superRegistry.getBridgeValidator(vars.liqData.bridgeId); vars.amount = IBridgeValidator(vars.bridgeValidator).decodeAmountIn(vars.liqData.txData, false); /// @dev the amount inscribed in liqData must be less or equal than the amount redeemed from the vault - if (vars.amount > dstAmount) revert Error.DIRECT_WITHDRAW_INVALID_LIQ_REQUEST(); + if (_isWithdrawTxDataAmountInvalid(vars.amount, assets, p_.data.maxSlippage)) { + if (p_.isXChain == 1) revert Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); + revert Error.DIRECT_WITHDRAW_INVALID_LIQ_REQUEST(); + } vars.chainId = CHAIN_ID; @@ -110,7 +137,7 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { false, address(this), p_.data.receiverAddress, - vars.liqData.token, + vars.asset, address(0) ) ); @@ -118,7 +145,7 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { _dispatchTokens( superRegistry.getBridgeAddress(vars.liqData.bridgeId), vars.liqData.txData, - vars.liqData.token, + vars.asset, vars.amount, vars.liqData.nativeAmount ); @@ -137,52 +164,57 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { internal virtual override - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processDirectDeposit(singleVaultData_); + shares = _processDirectDeposit(singleVaultData_); } /// @inheritdoc BaseForm function _xChainDepositIntoVault( InitSingleVaultData memory singleVaultData_, - address, + address, /*srcSender_*/ uint64 srcChainId_ ) internal virtual override - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processXChainDeposit(singleVaultData_, srcChainId_); + shares = _processXChainDeposit(singleVaultData_, srcChainId_); } /// @inheritdoc BaseForm /// @dev this is the step-1 for timelock form withdrawal, direct case - /// @dev will mandatorily process unlock - /// @return dstAmount is always 0 + /// @dev will mandatorily process unlock unless the retain4626 flag is set + /// @return shares is always 0 function _directWithdrawFromVault( InitSingleVaultData memory singleVaultData_, - address srcSender_ + address /*srcSender_*/ ) internal virtual override returns (uint256) { - /// @dev after requesting the unlock, the information with the time of full unlock is saved and sent to timelock - /// @dev state registry for re-processing at a later date - _storePayload(0, srcSender_, CHAIN_ID, _requestUnlock(singleVaultData_.amount), singleVaultData_); - + if (!singleVaultData_.retain4626) { + /// @dev after requesting the unlock, the information with the time of full unlock is saved and sent to + /// timelock + /// @dev state registry for re-processing at a later date + _storePayload(0, CHAIN_ID, _requestUnlock(singleVaultData_.amount), singleVaultData_); + } else { + /// @dev transfer shares to user and do not redeem shares for assets + IERC4626TimelockVault(vault).safeTransfer(singleVaultData_.receiverAddress, singleVaultData_.amount); + } return 0; } /// @inheritdoc BaseForm /// @dev this is the step-1 for timelock form withdrawal, xchain case - /// @dev will mandatorily process unlock - /// @return dstAmount is always 0 + /// @dev will mandatorily process unlock unless the retain4626 flag is set + /// @return shares is always 0 function _xChainWithdrawFromVault( InitSingleVaultData memory singleVaultData_, - address srcSender_, + address, /*srcSender_*/ uint64 srcChainId_ ) internal @@ -190,21 +222,27 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { override returns (uint256) { - /// @dev after requesting the unlock, the information with the time of full unlock is saved and sent to timelock - /// @dev state registry for re-processing at a later date - _storePayload(1, srcSender_, srcChainId_, _requestUnlock(singleVaultData_.amount), singleVaultData_); + if (!singleVaultData_.retain4626) { + /// @dev after requesting the unlock, the information with the time of full unlock is saved and sent to + /// timelock + /// @dev state registry for re-processing at a later date + _storePayload(1, srcChainId_, _requestUnlock(singleVaultData_.amount), singleVaultData_); + } else { + /// @dev transfer shares to user and do not redeem shares for assets + IERC4626TimelockVault(vault).safeTransfer(singleVaultData_.receiverAddress, singleVaultData_.amount); + } return 0; } /// @inheritdoc BaseForm - function _emergencyWithdraw(address, /*srcSender_*/ address refundAddress_, uint256 amount_) internal override { - _processEmergencyWithdraw(refundAddress_, amount_); + function _emergencyWithdraw(address receiverAddress_, uint256 amount_) internal override { + _processEmergencyWithdraw(receiverAddress_, amount_); } /// @inheritdoc BaseForm - function _forwardDustToPaymaster() internal override { - _processForwardDustToPaymaster(); + function _forwardDustToPaymaster(address token_) internal override { + _processForwardDustToPaymaster(token_); } /// @dev calls the vault to request unlock @@ -219,7 +257,6 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { /// @dev stores the withdrawal payload function _storePayload( uint8 type_, - address srcSender_, uint64 srcChainId_, uint256 lockedTill_, InitSingleVaultData memory data_ @@ -227,7 +264,7 @@ contract ERC4626TimelockForm is ERC4626FormImplementation { internal { ITimelockStateRegistry(superRegistry.getAddress(keccak256("TIMELOCK_STATE_REGISTRY"))).receivePayload( - type_, srcSender_, srcChainId_, lockedTill_, data_ + type_, srcChainId_, lockedTill_, data_ ); } } diff --git a/src/forms/interfaces/IERC4626Form.sol b/src/forms/interfaces/IERC4626Form.sol index 01c100e62..14cb791f1 100644 --- a/src/forms/interfaces/IERC4626Form.sol +++ b/src/forms/interfaces/IERC4626Form.sol @@ -4,8 +4,10 @@ pragma solidity ^0.8.23; import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; /// @title IERC4626Form +/// @dev Interface for ERC4626Form /// @author Zeropoint Labs interface IERC4626Form is IERC20 { + ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // ////////////////////////////////////////////////////////////// diff --git a/src/forms/interfaces/IERC4626TimelockForm.sol b/src/forms/interfaces/IERC4626TimelockForm.sol index 0920f87b1..951d1ee55 100644 --- a/src/forms/interfaces/IERC4626TimelockForm.sol +++ b/src/forms/interfaces/IERC4626TimelockForm.sol @@ -5,9 +5,8 @@ import { IERC4626Form } from "./IERC4626Form.sol"; import { InitSingleVaultData, TimelockPayload } from "../../types/DataTypes.sol"; /// @title IERC4626TimelockForm +/// @dev Interface used by ERC4626TimelockForm. Required by TimelockStateRegistry to call processUnlock() /// @author Zeropoint Labs -/// @notice Interface used by ERC4626TimelockForm. Required by TimelockStateRegistry to call processUnlock() -/// function interface IERC4626TimelockForm is IERC4626Form { ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // diff --git a/src/interfaces/IAmbImplementation.sol b/src/interfaces/IAmbImplementation.sol index 006bbe015..29e6ccf81 100644 --- a/src/interfaces/IAmbImplementation.sol +++ b/src/interfaces/IAmbImplementation.sol @@ -2,15 +2,16 @@ pragma solidity ^0.8.23; /// @title IAmbImplementation +/// @dev Interface for arbitrary message bridge (AMB) implementations /// @author ZeroPoint Labs -/// @dev interface for arbitrary message bridge implementation interface IAmbImplementation { + ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// - event ChainAdded(uint64 superChainId); - event AuthorizedImplAdded(uint64 superChainId, address authImpl); + event ChainAdded(uint64 indexed superChainId); + event AuthorizedImplAdded(uint64 indexed superChainId, address indexed authImpl); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // diff --git a/src/interfaces/IBaseForm.sol b/src/interfaces/IBaseForm.sol index ee34f726e..4a298a947 100644 --- a/src/interfaces/IBaseForm.sol +++ b/src/interfaces/IBaseForm.sol @@ -1,26 +1,36 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { InitSingleVaultData } from "src/types/DataTypes.sol"; import { IERC165 } from "openzeppelin-contracts/contracts/utils/introspection/IERC165.sol"; -import { InitSingleVaultData } from "../types/DataTypes.sol"; import { IERC4626 } from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; /// @title IBaseForm +/// @dev Interface for BaseForm /// @author ZeroPoint Labs -/// @notice Interface for Base Form interface IBaseForm is IERC165 { + ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// /// @dev is emitted when a new vault is added by the admin. - event VaultAdded(uint256 id, IERC4626 vault); + event VaultAdded(uint256 indexed id, IERC4626 indexed vault); /// @dev is emitted when a payload is processed by the destination contract. - event Processed(uint64 srcChainID, uint64 dstChainId, uint256 srcPayloadId, uint256 amount, address vault); + event Processed( + uint64 indexed srcChainID, + uint64 indexed dstChainId, + uint256 indexed srcPayloadId, + uint256 amount, + address vault + ); /// @dev is emitted when an emergency withdrawal is processed - event EmergencyWithdrawalProcessed(address refundAddress, uint256 amount); + event EmergencyWithdrawalProcessed(address indexed refundAddress, uint256 indexed amount); + + /// @dev is emitted when dust is forwarded to the paymaster + event FormDustForwardedToPaymaster(address indexed token, uint256 indexed amount); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // @@ -92,59 +102,59 @@ interface IBaseForm is IERC165 { /// @dev process same chain id deposits /// @param singleVaultData_ A bytes representation containing all the data required to make a form action /// @param srcSender_ The address of the sender of the transaction - /// @return dstAmount The amount of tokens deposited in same chain action + /// @return shares The amount of vault shares received function directDepositIntoVault( InitSingleVaultData memory singleVaultData_, address srcSender_ ) external payable - returns (uint256 dstAmount); + returns (uint256 shares); /// @dev process same chain id deposits /// @param singleVaultData_ A bytes representation containing all the data required to make a form action /// @param srcSender_ The address of the sender of the transaction /// @param srcChainId_ The chain id of the source chain - /// @return dstAmount The amount of tokens deposited in same chain action - /// @dev is dstAmoutn is `0` then no further action/acknowledgement needs to be sent + /// @return shares The amount of vault shares received + /// @dev is shares is `0` then no further action/acknowledgement needs to be sent function xChainDepositIntoVault( InitSingleVaultData memory singleVaultData_, address srcSender_, uint64 srcChainId_ ) external - returns (uint256 dstAmount); + returns (uint256 shares); /// @dev process withdrawal of asset from a vault /// @param singleVaultData_ A bytes representation containing all the data required to make a form action /// @param srcSender_ The address of the sender of the transaction - /// @return dstAmount The amount of tokens withdrawn in same chain action + /// @return assets The amount of assets received function directWithdrawFromVault( InitSingleVaultData memory singleVaultData_, address srcSender_ ) external - returns (uint256 dstAmount); + returns (uint256 assets); /// @dev process withdrawal of asset from a vault /// @param singleVaultData_ A bytes representation containing all the data required to make a form action /// @param srcSender_ The address of the sender of the transaction /// @param srcChainId_ The chain id of the source chain - /// @return dstAmount The amount of tokens withdrawn + /// @return assets The amount of assets received function xChainWithdrawFromVault( InitSingleVaultData memory singleVaultData_, address srcSender_, uint64 srcChainId_ ) external - returns (uint256 dstAmount); + returns (uint256 assets); /// @dev process withdrawal of shares if form is paused - /// @param srcSender_ The address of the sender of the transaction - /// @param refundAddress_ The address to refund the shares to + /// @param receiverAddress_ The address to refund the shares to /// @param amount_ The amount of vault shares to refund - function emergencyWithdraw(address srcSender_, address refundAddress_, uint256 amount_) external; + function emergencyWithdraw(address receiverAddress_, uint256 amount_) external; /// @dev moves all dust in the contract to Paymaster contract - function forwardDustToPaymaster() external; + /// @param token_ The address of the token to forward + function forwardDustToPaymaster(address token_) external; } diff --git a/src/interfaces/IBaseRouter.sol b/src/interfaces/IBaseRouter.sol index fca0f1c00..7679081b7 100644 --- a/src/interfaces/IBaseRouter.sol +++ b/src/interfaces/IBaseRouter.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import "../types/DataTypes.sol"; +import "src/types/DataTypes.sol"; /// @title IBaseRouter -/// @author Zeropoint Labs. -/// @dev interface for abstract Router +/// @dev Interface for abstract BaseRouter +/// @author Zeropoint Labs interface IBaseRouter { + ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// @@ -58,4 +59,8 @@ interface IBaseRouter { /// @dev Performs multi destination x multi vault withdraws /// @param req_ is the request object containing all the necessary data for the action function multiDstMultiVaultWithdraw(MultiDstMultiVaultStateReq calldata req_) external payable; + + /// @dev Forwards dust to Paymaster + /// @param token_ the token to forward + function forwardDustToPaymaster(address token_) external; } diff --git a/src/interfaces/IBaseRouterImplementation.sol b/src/interfaces/IBaseRouterImplementation.sol index c6fddef06..e0ca9fadd 100644 --- a/src/interfaces/IBaseRouterImplementation.sol +++ b/src/interfaces/IBaseRouterImplementation.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { IBaseRouter } from "./IBaseRouter.sol"; - -import "../types/DataTypes.sol"; +import { IBaseRouter } from "src/interfaces/IBaseRouter.sol"; +import { AMBMessage, LiqRequest, TransactionType } from "src/types/DataTypes.sol"; /// @title IBaseRouterImplementation -/// @author Zeropoint Labs. -/// @dev interface for BaseRouterImplementation +/// @dev Interface for BaseRouterImplementation +/// @author Zeropoint Labs interface IBaseRouterImplementation is IBaseRouter { ////////////////////////////////////////////////////////////// // STRUCTS // @@ -70,4 +69,7 @@ interface IBaseRouterImplementation is IBaseRouter { /// @dev is emitted when a direct chain action is complete event Completed(); + + /// @dev is emitted when dust is forwarded to paymaster + event RouterDustForwardedToPaymaster(address indexed token, uint256 indexed amount); } diff --git a/src/interfaces/IBaseStateRegistry.sol b/src/interfaces/IBaseStateRegistry.sol index 34b73c572..4b43277ed 100644 --- a/src/interfaces/IBaseStateRegistry.sol +++ b/src/interfaces/IBaseStateRegistry.sol @@ -1,28 +1,28 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import "../types/DataTypes.sol"; +import { PayloadState } from "src/types/DataTypes.sol"; /// @title IBaseStateRegistry +/// @dev Interface for BaseStateRegistry /// @author ZeroPoint Labs -/// @dev Is the crosschain interaction point. Send, store & process crosschain messages interface IBaseStateRegistry { ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// /// @dev is emitted when a cross-chain payload is received in the state registry - event PayloadReceived(uint64 srcChainId, uint64 dstChainId, uint256 payloadId); + event PayloadReceived(uint64 indexed srcChainId, uint64 indexed dstChainId, uint256 indexed payloadId); /// @dev is emitted when a cross-chain proof is received in the state registry /// NOTE: comes handy if quorum required is more than 0 - event ProofReceived(bytes proof); + event ProofReceived(bytes indexed proof); /// @dev is emitted when a payload id gets updated - event PayloadUpdated(uint256 payloadId); + event PayloadUpdated(uint256 indexed payloadId); /// @dev is emitted when a payload id gets processed - event PayloadProcessed(uint256 payloadId); + event PayloadProcessed(uint256 indexed payloadId); /// @dev is emitted when the super registry address is updated event SuperRegistryUpdated(address indexed superRegistry); diff --git a/src/interfaces/IBridgeValidator.sol b/src/interfaces/IBridgeValidator.sol index 88c749fb1..8b5ffc426 100644 --- a/src/interfaces/IBridgeValidator.sol +++ b/src/interfaces/IBridgeValidator.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -/// @title Bridge Handler Interface +/// @title Bridge Validator Interface +/// @dev Interface all Bridge Validators must follow /// @author Zeropoint Labs interface IBridgeValidator { + ////////////////////////////////////////////////////////////// // STRUCTS // ////////////////////////////////////////////////////////////// @@ -15,7 +17,7 @@ interface IBridgeValidator { uint64 liqDstChainId; bool deposit; address superform; - address srcSender; + address receiverAddress; address liqDataToken; address liqDataInterimToken; } @@ -44,7 +46,7 @@ interface IBridgeValidator { bool genericSwapDisallowed_ ) external - view + pure returns (uint256 amount_); /// @dev decodes neccesary information for processing swaps on the destination chain @@ -56,5 +58,5 @@ interface IBridgeValidator { /// @dev decodes the final output token address (for only direct chain actions!) /// @param txData_ is the txData to be decoded /// @return token_ the address of the token - function decodeSwapOutputToken(bytes calldata txData_) external view returns (address token_); + function decodeSwapOutputToken(bytes calldata txData_) external pure returns (address token_); } diff --git a/src/interfaces/IBroadcastAmbImplementation.sol b/src/interfaces/IBroadcastAmbImplementation.sol index 72eb463cf..545c045bf 100644 --- a/src/interfaces/IBroadcastAmbImplementation.sol +++ b/src/interfaces/IBroadcastAmbImplementation.sol @@ -2,15 +2,16 @@ pragma solidity ^0.8.23; /// @title IBroadcastAmbImplementation +/// @dev Interface for AMBs used in Broadcasting /// @author ZeroPoint Labs -/// @dev interface for arbitrary message bridge implementation the supports broadcasting interface IBroadcastAmbImplementation { + ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// - event ChainAdded(uint64 superChainId); - event AuthorizedImplAdded(uint64 superChainId, address authImpl); + event ChainAdded(uint64 indexed superChainId); + event AuthorizedImplAdded(uint64 indexed superChainId, address indexed authImpl); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // diff --git a/src/interfaces/IBroadcastRegistry.sol b/src/interfaces/IBroadcastRegistry.sol index 4c53e37cf..d875fe3e8 100644 --- a/src/interfaces/IBroadcastRegistry.sol +++ b/src/interfaces/IBroadcastRegistry.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.23; /// @title IBroadcastRegistry +/// @dev Interface for BroadcastRegistry /// @author ZeroPoint Labs -/// @dev is an helper for base state registry with broadcasting abilities. interface IBroadcastRegistry { + ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// @@ -12,11 +13,13 @@ interface IBroadcastRegistry { /// @dev allows core contracts to send payload to all configured destination chain. /// @param srcSender_ is the caller of the function (used for gas refunds). /// @param ambId_ is the identifier of the arbitrary message bridge to be used + /// @param gasFee_ is the gas fee to be used for broadcasting /// @param message_ is the crosschain payload to be broadcasted /// @param extraData_ defines all the message bridge related overrides function broadcastPayload( address srcSender_, uint8 ambId_, + uint256 gasFee_, bytes memory message_, bytes memory extraData_ ) diff --git a/src/interfaces/ICoreStateRegistry.sol b/src/interfaces/ICoreStateRegistry.sol index 7a9fb3781..d9614a9d4 100644 --- a/src/interfaces/ICoreStateRegistry.sol +++ b/src/interfaces/ICoreStateRegistry.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.23; /// @title ICoreStateRegistry +/// @dev Interface for CoreStateRegistry /// @author ZeroPoint Labs -/// @notice Interface for Core State Registry interface ICoreStateRegistry { + ////////////////////////////////////////////////////////////// // STRUCTS // ////////////////////////////////////////////////////////////// @@ -13,14 +14,14 @@ interface ICoreStateRegistry { /// @param superformIds is an array of failing superform ids /// @param settlementToken is an array of tokens to be refunded for the failing superform /// @param amounts is an array of amounts of settlementToken to be refunded - /// @param refundAddress is the users refund address + /// @param receiverAddress is the users refund address /// @param lastProposedTime indicates the rescue proposal timestamp struct FailedDeposit { uint256[] superformIds; address[] settlementToken; uint256[] amounts; bool[] settleFromDstSwapper; - address refundAddress; + address receiverAddress; uint256 lastProposedTimestamp; } @@ -33,7 +34,10 @@ interface ICoreStateRegistry { /// @dev is emitted when a rescue is proposed for failed deposits in a payload event RescueProposed( - uint256 indexed payloadId, uint256[] superformIds, uint256[] proposedAmount, uint256 proposedTime + uint256 indexed payloadId, + uint256[] indexed superformIds, + uint256[] indexed proposedAmount, + uint256 proposedTime ); /// @dev is emitted when an user disputed his refund amounts @@ -48,11 +52,22 @@ interface ICoreStateRegistry { /// @dev allows users to read the superformIds that failed in a specific payloadId_ /// @param payloadId_ is the identifier of the cross-chain payload. - /// @return superformIds_ is the identifiers of superforms in the payloadId that got failed. + /// @return superformIds is the identifiers of superforms in the payloadId that got failed. + /// @return amounts is the amounts of refund tokens issues + /// @return lastProposedTime is the refund proposed time function getFailedDeposits(uint256 payloadId_) external view - returns (uint256[] memory superformIds_, uint256[] memory amounts); + returns (uint256[] memory superformIds, uint256[] memory amounts, uint256 lastProposedTime); + + /// @dev used internally for try/catching + /// @param finalAmount_ is the final amount of tokens received + /// @param amount_ is the indicated amount of tokens to be received + /// @param maxSlippage_ is the amount of acceptable slippage for the transaction + function validateSlippage(uint256 finalAmount_, uint256 amount_, uint256 maxSlippage_) + external + view + returns (bool); ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // @@ -60,9 +75,15 @@ interface ICoreStateRegistry { /// @dev allows accounts with {CORE_STATE_REGISTRY_UPDATER_ROLE} to modify a received cross-chain deposit payload. /// @param payloadId_ is the identifier of the cross-chain payload to be updated. + /// @param finalTokens_ is the token received by the core state registry. /// @param finalAmounts_ is the amount to be updated. /// NOTE: amounts cannot be updated beyond user specified safe slippage limit. - function updateDepositPayload(uint256 payloadId_, uint256[] calldata finalAmounts_) external; + function updateDepositPayload( + uint256 payloadId_, + address[] calldata finalTokens_, + uint256[] calldata finalAmounts_ + ) + external; /// @dev allows accounts with {CORE_STATE_REGISTRY_UPDATER_ROLE} to modify a received cross-chain withdraw payload. /// @param payloadId_ is the identifier of the cross-chain payload to be updated. diff --git a/src/interfaces/IDstSwapper.sol b/src/interfaces/IDstSwapper.sol index faba85946..62bad9b90 100644 --- a/src/interfaces/IDstSwapper.sol +++ b/src/interfaces/IDstSwapper.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.23; /// @title IDstSwapper +/// @dev Interface for DstSwapper /// @author Zeropoint Labs -/// @dev handles all destination chain swaps. -/// @notice all write functions can only be accessed by superform keepers. interface IDstSwapper { + ////////////////////////////////////////////////////////////// // STRUCTS // ////////////////////////////////////////////////////////////// @@ -23,10 +23,14 @@ interface IDstSwapper { event SuperRegistryUpdated(address indexed superRegistry); /// @dev is emitted when a dst swap transaction is processed - event SwapProcessed(uint256 payloadId, uint256 index, uint256 bridgeId, uint256 finalAmount); + event SwapProcessed( + uint256 indexed payloadId, uint256 indexed index, uint256 indexed bridgeId, uint256 finalAmount + ); /// @dev is emitted when a dst swap fails and intermediary tokens are sent to CoreStateRegistry for rescue - event SwapFailed(uint256 payloadId, uint256 index, address intermediaryToken, uint256 amount); + event SwapFailed( + uint256 indexed payloadId, uint256 indexed index, address indexed intermediaryToken, uint256 amount + ); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // @@ -57,17 +61,28 @@ interface IDstSwapper { /// @notice will process dst swap through a liquidity bridge /// @param payloadId_ represents the id of the payload - /// @param index_ represents the index of the superformid in the payload /// @param bridgeId_ represents the id of liquidity bridge used /// @param txData_ represents the transaction data generated by liquidity bridge API. - function processTx(uint256 payloadId_, uint256 index_, uint8 bridgeId_, bytes calldata txData_) external; + function processTx(uint256 payloadId_, uint8 bridgeId_, bytes calldata txData_) external; + + /// @notice will process dst swaps in batch through a liquidity bridge + /// @param payloadId_ represents the array of payload ids used + /// @param indices_ represents the index of the superformid in the payload + /// @param bridgeIds_ represents the array of ids of liquidity bridges used + /// @param txDatas_ represents the array of transaction data generated by liquidity bridge API + function batchProcessTx( + uint256 payloadId_, + uint256[] calldata indices_, + uint8[] calldata bridgeIds_, + bytes[] calldata txDatas_ + ) + external; /// @notice updates the amounts of intermediary tokens stuck because of failing dst swap /// @param payloadId_ represents the id of the payload - /// @param index_ represents the failing index in the payload /// @param interimToken_ is the intermediary token that cannot be swapped to the vault underlying /// @param amount_ is the amount of the intermediary token - function updateFailedTx(uint256 payloadId_, uint256 index_, address interimToken_, uint256 amount_) external; + function updateFailedTx(uint256 payloadId_, address interimToken_, uint256 amount_) external; /// @notice updates the amounts of intermediary tokens stuck because of failing dst swap in batch /// @param payloadId_ represents the id of the payload @@ -82,19 +97,6 @@ interface IDstSwapper { ) external; - /// @notice will process dst swaps in batch through a liquidity bridge - /// @param payloadId_ represents the array of payload ids used - /// @param indices_ represents the index of the superformid in the payload - /// @param bridgeIds_ represents the array of ids of liquidity bridges used - /// @param txData_ represents the array of transaction data generated by liquidity bridge API - function batchProcessTx( - uint256 payloadId_, - uint256[] calldata indices_, - uint8[] calldata bridgeIds_, - bytes[] calldata txData_ - ) - external; - /// @notice is a privileged function that allows Core State Registry to process refunds /// @param user_ is the final refund receiver of the interimToken_ /// @param interimToken_ is the refund token diff --git a/src/interfaces/IEmergencyQueue.sol b/src/interfaces/IEmergencyQueue.sol index 6892ae626..5d2daa982 100644 --- a/src/interfaces/IEmergencyQueue.sol +++ b/src/interfaces/IEmergencyQueue.sol @@ -1,23 +1,28 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { InitSingleVaultData } from "../types/DataTypes.sol"; +import { InitSingleVaultData } from "src/types/DataTypes.sol"; +/// @title IEmergencyQueue +/// @dev Interface for EmergencyQueue +/// @author ZeroPoint Labs interface IEmergencyQueue { + ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// event WithdrawalQueued( - address indexed srcAddress, - address indexed refundAddress, + address indexed receiverAddress, uint256 indexed id, - uint256 superformId, + uint256 indexed superformId, uint256 amount, uint256 srcPayloadId ); - event WithdrawalProcessed(address indexed refundAddress, uint256 indexed id, uint256 superformId, uint256 amount); + event WithdrawalProcessed( + address indexed refundAddress, uint256 indexed id, uint256 indexed superformId, uint256 amount + ); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // @@ -34,7 +39,7 @@ interface IEmergencyQueue { /// @dev called by paused forms to queue up withdrawals for exit /// @param data_ is the single vault data passed by the user - function queueWithdrawal(InitSingleVaultData memory data_, address srcSender_) external; + function queueWithdrawal(InitSingleVaultData memory data_) external; /// @dev called by emergency admin to processed queued withdrawal /// @param id_ is the identifier of the queued action diff --git a/src/interfaces/IPayMaster.sol b/src/interfaces/IPayMaster.sol index 743229ce7..50448e658 100644 --- a/src/interfaces/IPayMaster.sol +++ b/src/interfaces/IPayMaster.sol @@ -1,30 +1,40 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { LiqRequest } from "../types/DataTypes.sol"; +import { LiqRequest } from "src/types/DataTypes.sol"; /// @title IPayMaster +/// @dev Interface for PayMaster /// @author ZeroPoint Labs -/// @dev contract for destination transaction costs payment interface IPayMaster { + ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// /// @dev is emitted when a new payment is made - event Payment(address indexed user, uint256 amount); + event Payment(address indexed user, uint256 indexed amount); - /// @dev is emitted when payments are moved out of collector - event PaymentWithdrawn(address indexed receiver, uint256 amount); + /// @dev is emitted when tokens are moved out of paymaster + event TokenWithdrawn(address indexed receiver, address indexed token, uint256 indexed amount); + + /// @dev is emitted when native tokens are moved out of paymaster + event NativeWithdrawn(address indexed receiver, uint256 indexed amount); ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// - /// @dev withdraws funds from pay master to target id from superRegistry + /// @dev withdraws token funds from pay master to target id from superRegistry + /// @param superRegistryId_ is the id of the target address in superRegistry + /// @param token_ is the token to withdraw from pay master + /// @param amount_ is the amount to withdraw from pay master + function withdrawTo(bytes32 superRegistryId_, address token_, uint256 amount_) external; + + /// @dev withdraws native funds from pay master to target id from superRegistry /// @param superRegistryId_ is the id of the target address in superRegistry /// @param nativeAmount_ is the amount to withdraw from pay master - function withdrawTo(bytes32 superRegistryId_, uint256 nativeAmount_) external; + function withdrawNativeTo(bytes32 superRegistryId_, uint256 nativeAmount_) external; /// @dev withdraws fund from pay master to target id from superRegistry /// @param superRegistryId_ is the id of the target address in superRegistry diff --git a/src/interfaces/IPayloadHelper.sol b/src/interfaces/IPayloadHelper.sol index 0f99b4a83..1fb98d496 100644 --- a/src/interfaces/IPayloadHelper.sol +++ b/src/interfaces/IPayloadHelper.sol @@ -2,48 +2,60 @@ pragma solidity ^0.8.23; /// @title IPayloadHelper +/// @dev Interface for PayloadHelper /// @author ZeroPoint Labs -/// @dev helps decoding the bytes payload and returns meaningful information interface IPayloadHelper { + + ////////////////////////////////////////////////////////////// + // STRUCTS // + ////////////////////////////////////////////////////////////// + + /// @notice txType is the type of transaction. check {TransactionType} enum in DataTypes.sol + /// @notice callbackType is the type of payload. check {CallbackType} enum in DataTypes.sol + /// @notice srcSender is the user who initiated the transaction on the srcChain + /// @notice srcChainId is the unique identifier of the srcChain + /// @notice amounts are the amounts to deposit/withdraw + /// @notice outputAmounts are the expected outputAmounts specified by user + /// @notice slippages are the max slippages configured by the user (only for deposits) + /// @notice superformIds are the unique identifiers of the superforms + /// @notice hasDstSwaps are the array of flags indicating if the original liqData has a dstSwaps + /// @notice extraFormData is the extra form data (optional: passed for forms with special needs) + /// @notice receiverAddress is the address to be used for refunds + /// @notice srcPayloadId is the identifier of the corresponding payload on srcChain + struct DecodedDstPayload { + uint8 txType; + uint8 callbackType; + address srcSender; + uint64 srcChainId; + uint256[] amounts; + uint256[] outputAmounts; + uint256[] slippages; + uint256[] superformIds; + bool[] hasDstSwaps; + address receiverAddress; + uint256 srcPayloadId; + bytes extraFormData; + uint8 multi; + } + ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // ////////////////////////////////////////////////////////////// /// @dev reads the payload from the core state registry and decodes it in a more detailed manner. /// @param dstPayloadId_ is the unique identifier of the payload received in dst core state registry - /// @return txType is the type of transaction. check {TransactionType} enum in DataTypes.sol - /// @return callbackType is the type of payload. check {CallbackType} enum in DataTypes.sol - /// @return srcSender is the user who initiated the transaction on the srcChain - /// @return srcChainId is the unique identifier of the srcChain - /// @return amounts are the amounts to deposit/withdraw - /// @return slippages are the max slippages configured by the user (only for deposits) - /// @return superformIds are the unique identifiers of the superforms - /// @return hasDstSwaps are the array of flags indicating if the original liqData has a dstSwaps - /// @return extraFormData is the extra form data (optional: passed for forms with special needs) - /// @return receiverAddress is the address to be used for refunds - /// @return srcPayloadId is the identifier of the corresponding payload on srcChain + /// @return decodedDstPayload is the details of the payload, refer DecodedDstPayload struct for info function decodeCoreStateRegistryPayload(uint256 dstPayloadId_) external view - returns ( - uint8 txType, - uint8 callbackType, - address srcSender, - uint64 srcChainId, - uint256[] memory amounts, - uint256[] memory slippages, - uint256[] memory superformIds, - bool[] memory hasDstSwaps, - bytes memory extraFormData, - address receiverAddress, - uint256 srcPayloadId - ); + returns (DecodedDstPayload memory decodedDstPayload); /// @dev reads the payload from the core state registry and decodes liqData for it (to be used in withdraw cases) /// @param dstPayloadId_ is the unique identifier of the payload received in dst core state registry - /// @return bridgeIds are the ids of the bridges to be used /// @return txDatas are the array of txData to be sent to the bridges /// @return tokens are the tokens to be used in the liqData + /// @return interimTokens are the interim tokens to be used in the liqData + /// @return bridgeIds are the ids of the bridges to be used /// @return liqDstChainIds are the final destination chain id for the underlying token (can be arbitrary on /// withdraws) /// @return amountsIn are the from amounts to the liquidity bridge @@ -52,9 +64,10 @@ interface IPayloadHelper { external view returns ( - uint8[] memory bridgeIds, bytes[] memory txDatas, address[] memory tokens, + address[] memory interimTokens, + uint8[] memory bridgeIds, uint64[] memory liqDstChainIds, uint256[] memory amountsIn, uint256[] memory nativeAmounts @@ -66,18 +79,26 @@ interface IPayloadHelper { /// @return callbackType is the type of payload. check {CallbackType} enum in DataTypes.sol /// @return isMulti indicates if the transaction involves operations to multiple vaults /// @return srcSender is the user who initiated the transaction on the srcChain + /// @return receiverAddressSP is the address to be used for receiving Super Positions /// @return srcChainId is the unique identifier of the srcChain function decodePayloadHistory(uint256 srcPayloadId_) external view - returns (uint8 txType, uint8 callbackType, uint8 isMulti, address srcSender, uint64 srcChainId); + returns ( + uint8 txType, + uint8 callbackType, + uint8 isMulti, + address srcSender, + address receiverAddressSP, + uint64 srcChainId + ); /// @dev returns decoded timelock form payloads /// @param timelockPayloadId_ is the unique identifier of payload in timelock state registry function decodeTimeLockPayload(uint256 timelockPayloadId_) external view - returns (address srcSender, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount); + returns (address receiverAddress, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount); /// @dev returns decoded failed timelock form payloads /// @param timelockPayloadId_ is the unique identifier of payload in timelock state registry diff --git a/src/interfaces/IPaymentHelper.sol b/src/interfaces/IPaymentHelper.sol index 29fcda798..c84019d6d 100644 --- a/src/interfaces/IPaymentHelper.sol +++ b/src/interfaces/IPaymentHelper.sol @@ -8,12 +8,13 @@ import { SingleXChainSingleVaultStateReq, SingleDirectSingleVaultStateReq, SingleDirectMultiVaultStateReq -} from "../types/DataTypes.sol"; +} from "src/types/DataTypes.sol"; /// @title IPaymentHelper +/// @dev Interface for PaymentHelper /// @author ZeroPoint Labs -/// @dev helps decoding the bytes payload and returns meaningful information interface IPaymentHelper { + ////////////////////////////////////////////////////////////// // STRUCTS // ////////////////////////////////////////////////////////////// @@ -29,6 +30,7 @@ interface IPaymentHelper { /// @param dstGasPerByte is the gas per size of data on the specified chain /// @param ackGasCost is the gas cost for processing acknowledgements on src chain /// @param timelockCost is the extra cost for processing timelocked payloads + /// @param emergencyCost is the extra cost for processing emergency payloads struct PaymentHelperConfig { address nativeFeedOracle; address gasPriceOracle; @@ -41,13 +43,15 @@ interface IPaymentHelper { uint256 dstGasPerByte; uint256 ackGasCost; uint256 timelockCost; + uint256 emergencyCost; } ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// - event ChainConfigUpdated(uint64 chainId_, uint256 configType_, bytes config_); + event ChainConfigUpdated(uint64 indexed chainId_, uint256 indexed configType_, bytes indexed config_); + event ChainConfigAdded(uint64 chainId_, PaymentHelperConfig config_); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // @@ -67,9 +71,8 @@ interface IPaymentHelper { returns (uint256 totalFees, bytes memory extraData); /// @dev returns the amb overrides & gas to be used - /// @return totalFees the msg.value to be sent along the transaction /// @return extraData the amb specific override information - function getRegisterTransmuterAMBData() external view returns (uint256 totalFees, bytes memory extraData); + function getRegisterTransmuterAMBData() external view returns (bytes memory extraData); /// @dev returns the gas fees estimation in native tokens if we send message through a combination of AMBs /// @param ambIds_ is the identifier of different AMBs @@ -191,7 +194,6 @@ interface IPaymentHelper { function updateRemoteChain(uint64 chainId_, uint256 configType_, bytes memory config_) external; /// @dev admin updates config for register transmuter amb params - /// @param totalTransmuterFees_ is the native value fees for registering transmuter on all supported chains /// @param extraDataForTransmuter_ is the broadcast extra data - function updateRegisterAERC20Params(uint256 totalTransmuterFees_, bytes memory extraDataForTransmuter_) external; + function updateRegisterAERC20Params(bytes memory extraDataForTransmuter_) external; } diff --git a/src/interfaces/IQuorumManager.sol b/src/interfaces/IQuorumManager.sol index aa5e0d6db..813d1d0e2 100644 --- a/src/interfaces/IQuorumManager.sol +++ b/src/interfaces/IQuorumManager.sol @@ -1,7 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +/// @title IQuorumManager +/// @dev Interface for QuorumManager +/// @author ZeroPoint Labs interface IQuorumManager { + ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// @@ -9,7 +13,7 @@ interface IQuorumManager { /// @dev emitted when a new quorum is set for a specific chain /// @param srcChainId the chain id from which the message (payload) is sent /// @param quorum the minimum number of message bridges required for processing - event QuorumSet(uint64 indexed srcChainId, uint256 quorum); + event QuorumSet(uint64 indexed srcChainId, uint256 indexed quorum); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // diff --git a/src/interfaces/ISuperPositions.sol b/src/interfaces/ISuperPositions.sol index d7855a453..1f1dd069a 100644 --- a/src/interfaces/ISuperPositions.sol +++ b/src/interfaces/ISuperPositions.sol @@ -1,33 +1,48 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { AMBMessage } from "../types/DataTypes.sol"; import { IERC1155A } from "ERC1155A/interfaces/IERC1155A.sol"; +import { AMBMessage } from "../types/DataTypes.sol"; /// @title ISuperPositions -/// @author Zeropoint Labs. -/// @dev interface for Super Positions +/// @dev Interface for SuperPositions +/// @author Zeropoint Labs interface ISuperPositions is IERC1155A { + + ////////////////////////////////////////////////////////////// + // STRUCTS // + ////////////////////////////////////////////////////////////// + + struct TxHistory { + uint256 txInfo; + address receiverAddressSP; + } + ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// /// @dev is emitted when a dynamic uri is updated - event DynamicURIUpdated(string oldURI, string newURI, bool frozen); + event DynamicURIUpdated(string indexed oldURI, string indexed newURI, bool indexed frozen); /// @dev is emitted when a cross-chain transaction is completed. - event Completed(uint256 txId); + event Completed(uint256 indexed txId); /// @dev is emitted when a aErc20 token is registered event AERC20TokenRegistered(uint256 indexed tokenId, address indexed tokenAddress); + /// @dev is emitted when a tx info is saved + event TxHistorySet(uint256 indexed payloadId, uint256 txInfo, address indexed receiverAddress); + ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // ////////////////////////////////////////////////////////////// - /// @dev returns the payload header for a tx id on the source chain + /// @dev returns the payload header and the receiver address for a tx id on the source chain /// @param txId_ is the identifier of the transaction issued by superform router - function txHistory(uint256 txId_) external view returns (uint256); + /// @return txInfo is the header of the payload + /// @return receiverAddressSP is the address of the receiver of superPositions + function txHistory(uint256 txId_) external view returns (uint256 txInfo, address receiverAddressSP); ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // @@ -35,20 +50,21 @@ interface ISuperPositions is IERC1155A { /// @dev saves the message being sent together with the associated id formulated in a router /// @param payloadId_ is the id of the message being saved - /// @param txInfo_ is the relevant information of the transaction being saved - function updateTxHistory(uint256 payloadId_, uint256 txInfo_) external; + /// @param txInfo_ is the header of the AMBMessage of the transaction being saved + /// @param receiverAddressSP_ is the address of the receiver of superPositions + function updateTxHistory(uint256 payloadId_, uint256 txInfo_, address receiverAddressSP_) external; /// @dev allows minter to mint shares on source - /// @param srcSender_ is the beneficiary of shares + /// @param receiverAddress_ is the beneficiary of shares /// @param id_ is the id of the shares /// @param amount_ is the amount of shares to mint - function mintSingle(address srcSender_, uint256 id_, uint256 amount_) external; + function mintSingle(address receiverAddress_, uint256 id_, uint256 amount_) external; /// @dev allows minter to mint shares on source in batch - /// @param srcSender_ is the beneficiary of shares + /// @param receiverAddress_ is the beneficiary of shares /// @param ids_ are the ids of the shares /// @param amounts_ are the amounts of shares to mint - function mintBatch(address srcSender_, uint256[] memory ids_, uint256[] memory amounts_) external; + function mintBatch(address receiverAddress_, uint256[] memory ids_, uint256[] memory amounts_) external; /// @dev allows superformRouter to burn shares on source /// @notice burn is done optimistically by the router in the beginning of the withdraw transactions diff --git a/src/interfaces/ISuperRBAC.sol b/src/interfaces/ISuperRBAC.sol index c14785dff..3b9a6abb1 100644 --- a/src/interfaces/ISuperRBAC.sol +++ b/src/interfaces/ISuperRBAC.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.23; import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol"; /// @title ISuperRBAC -/// @author Zeropoint Labs. -/// @dev interface for Super RBAC +/// @dev Interface for SuperRBAC +/// @author Zeropoint Labs interface ISuperRBAC is IAccessControl { + ////////////////////////////////////////////////////////////// // STRUCTS // ////////////////////////////////////////////////////////////// @@ -25,6 +26,16 @@ interface ISuperRBAC is IAccessControl { address csrDisputer; } + ////////////////////////////////////////////////////////////// + // EVENTS // + ////////////////////////////////////////////////////////////// + + /// @dev is emitted when superRegistry is set + event SuperRegistrySet(address indexed superRegistry); + + /// @dev is emitted when an admin is set for a role + event RoleAdminSet(bytes32 role, bytes32 adminRole); + ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // ////////////////////////////////////////////////////////////// diff --git a/src/interfaces/ISuperRegistry.sol b/src/interfaces/ISuperRegistry.sol index 41c277d41..e5e31511b 100644 --- a/src/interfaces/ISuperRegistry.sol +++ b/src/interfaces/ISuperRegistry.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.23; /// @title ISuperRegistry -/// @author Zeropoint Labs. -/// @dev interface for Super Registry +/// @dev Interface for SuperRegistry +/// @author Zeropoint Labs interface ISuperRegistry { + ////////////////////////////////////////////////////////////// // EVENTS // ////////////////////////////////////////////////////////////// @@ -24,16 +25,16 @@ interface ISuperRegistry { event SetBridgeValidator(uint256 indexed bridgeId, address indexed bridgeValidator); /// @dev is emitted when a new amb is configured. - event SetAmbAddress(uint8 ambId_, address ambAddress_, bool isBroadcastAMB_); + event SetAmbAddress(uint8 indexed ambId_, address indexed ambAddress_, bool indexed isBroadcastAMB_); /// @dev is emitted when a new state registry is configured. - event SetStateRegistryAddress(uint8 registryId_, address registryAddress_); + event SetStateRegistryAddress(uint8 indexed registryId_, address indexed registryAddress_); /// @dev is emitted when a new delay is configured. - event SetDelay(uint256 oldDelay_, uint256 newDelay_); + event SetDelay(uint256 indexed oldDelay_, uint256 indexed newDelay_); /// @dev is emitted when a new vault limit is configured - event SetVaultLimitPerTx(uint64 chainId_, uint256 vaultLimit_); + event SetVaultLimitPerDestination(uint64 indexed chainId_, uint256 indexed vaultLimit_); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // @@ -84,6 +85,9 @@ interface ISuperRegistry { /// @dev returns the id of the emergency queue function EMERGENCY_QUEUE() external view returns (bytes32); + /// @dev returns the id of the superform receiver + function SUPERFORM_RECEIVER() external view returns (bytes32); + /// @dev returns the id of the payment admin keeper function PAYMENT_ADMIN() external view returns (bytes32); @@ -150,9 +154,9 @@ interface ISuperRegistry { /// @dev gets the safe vault limit /// @param chainId_ is the id of the remote chain - /// @return vaultLimitPerTx_ is the safe number of vaults to deposit + /// @return vaultLimitPerDestination_ is the safe number of vaults to deposit /// without hitting out of gas error - function getVaultLimitPerTx(uint64 chainId_) external view returns (uint256 vaultLimitPerTx_); + function getVaultLimitPerDestination(uint64 chainId_) external view returns (uint256 vaultLimitPerDestination_); /// @dev helps validate if an address is a valid state registry /// @param registryAddress_ is the address of the state registry @@ -184,7 +188,7 @@ interface ISuperRegistry { /// @dev sets the safe vault limit /// @param chainId_ is the remote chain identifier /// @param vaultLimit_ is the max limit of vaults per transaction - function setVaultLimitPerTx(uint64 chainId_, uint256 vaultLimit_) external; + function setVaultLimitPerDestination(uint64 chainId_, uint256 vaultLimit_) external; /// @dev sets a new address on a specific chain. /// @param id_ the identifier of the address on that chain diff --git a/src/interfaces/ISuperformFactory.sol b/src/interfaces/ISuperformFactory.sol index aa0479c9f..dc1705e80 100644 --- a/src/interfaces/ISuperformFactory.sol +++ b/src/interfaces/ISuperformFactory.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.23; /// @title ISuperformFactory +/// @dev Interface for SuperformFactory /// @author ZeroPoint Labs -/// @notice Interface for Superform Factory interface ISuperformFactory { + ////////////////////////////////////////////////////////////// // CONSTANTS // ////////////////////////////////////////////////////////////// @@ -21,7 +22,10 @@ interface ISuperformFactory { /// @dev emitted when a new formImplementation is entered into the factory /// @param formImplementation is the address of the new form implementation /// @param formImplementationId is the id of the formImplementation - event FormImplementationAdded(address indexed formImplementation, uint256 indexed formImplementationId); + /// @param formStateRegistryId is any additional state registry id of the formImplementation + event FormImplementationAdded( + address indexed formImplementation, uint256 indexed formImplementationId, uint8 indexed formStateRegistryId + ); /// @dev emitted when a new Superform is created /// @param formImplementationId is the id of the form implementation @@ -39,7 +43,7 @@ interface ISuperformFactory { /// @dev emitted when a form implementation is paused /// @param formImplementationId is the id of the form implementation /// @param paused is the new paused status - event FormImplementationPaused(uint256 indexed formImplementationId, PauseStatus paused); + event FormImplementationPaused(uint256 indexed formImplementationId, PauseStatus indexed paused); ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // @@ -58,6 +62,11 @@ interface ISuperformFactory { /// @return formImplementation_ is the address of the form implementation function getFormImplementation(uint32 formImplementationId_) external view returns (address formImplementation_); + /// @dev returns the form state registry id of a form implementation + /// @param formImplementationId_ is the id of the form implementation + /// @return stateRegistryId_ is the additional state registry id of the form + function getFormStateRegistryId(uint32 formImplementationId_) external view returns (uint8 stateRegistryId_); + /// @dev returns the paused status of form implementation /// @param formImplementationId_ is the id of the form implementation /// @return paused_ is the current paused status of the form formImplementationId_ @@ -87,11 +96,6 @@ interface ISuperformFactory { view returns (uint256[] memory superformIds_, address[] memory superforms_); - /// @dev Returns all Superforms - /// @return superformIds_ is the id of the superform - /// @return vaults_ is the address of the vault - function getAllSuperforms() external view returns (uint256[] memory superformIds_, address[] memory vaults_); - ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// @@ -99,7 +103,15 @@ interface ISuperformFactory { /// @dev allows an admin to add a Form implementation to the factory /// @param formImplementation_ is the address of a form implementation /// @param formImplementationId_ is the id of the form implementation (generated off-chain and equal in all chains) - function addFormImplementation(address formImplementation_, uint32 formImplementationId_) external; + /// @param formStateRegistryId_ is the id of any additional state registry for that form + /// @dev formStateRegistryId_ 1 is default for all form implementations, pass in formStateRegistryId_ only if an + /// additional state registry is required + function addFormImplementation( + address formImplementation_, + uint32 formImplementationId_, + uint8 formStateRegistryId_ + ) + external; /// @dev To add new vaults to Form implementations, fusing them together into Superforms /// @param formImplementationId_ is the form implementation we want to attach the vault to diff --git a/src/interfaces/ITimelockStateRegistry.sol b/src/interfaces/ITimelockStateRegistry.sol index a9c9aac2f..760ef4e4a 100644 --- a/src/interfaces/ITimelockStateRegistry.sol +++ b/src/interfaces/ITimelockStateRegistry.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { InitSingleVaultData, TimelockPayload } from "../types/DataTypes.sol"; +import { InitSingleVaultData, TimelockPayload } from "src/types/DataTypes.sol"; /// @title ITimelockStateRegistry +/// @dev Interface for TimelockStateRegistry /// @author ZeroPoint Labs -/// @notice Interface for Timelock Form State Registry interface ITimelockStateRegistry { + ////////////////////////////////////////////////////////////// // EXTERNAL VIEW FUNCTIONS // ////////////////////////////////////////////////////////////// @@ -25,13 +26,11 @@ interface ITimelockStateRegistry { /// @notice Receives request (payload) from timelock form to process later /// @param type_ is the nature of transaction (xChain: 1 or same chain: 0) - /// @param srcSender_ is the address of the source chain caller /// @param srcChainId_ is the chainId of the source chain /// @param lockedTill_ is the deadline for timelock (after which we can call `finalizePayload`) /// @param data_ is the basic information of superformId, amount to withdraw of type InitSingleVaultData function receivePayload( uint8 type_, - address srcSender_, uint64 srcChainId_, uint256 lockedTill_, InitSingleVaultData memory data_ diff --git a/src/libraries/ArrayCastLib.sol b/src/libraries/ArrayCastLib.sol index bdcf9c118..ab91f189c 100644 --- a/src/libraries/ArrayCastLib.sol +++ b/src/libraries/ArrayCastLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { InitSingleVaultData, InitMultiVaultData, LiqRequest } from "../types/DataTypes.sol"; +import { InitSingleVaultData, InitMultiVaultData, LiqRequest } from "src/types/DataTypes.sol"; /// @dev library to cast single values into array for streamlining helper functions /// @notice not gas optimized, suggested for usage only in view/pure functions @@ -29,6 +29,9 @@ library ArrayCastLib { uint256[] memory amounts = new uint256[](1); amounts[0] = data_.amount; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = data_.outputAmount; + uint256[] memory maxSlippage = new uint256[](1); maxSlippage[0] = data_.maxSlippage; @@ -39,10 +42,11 @@ library ArrayCastLib { data_.payloadId, superformIds, amounts, + outputAmounts, maxSlippage, liqData, - new bool[](superformIds.length), - new bool[](superformIds.length), + castBoolToArray(data_.hasDstSwap), + castBoolToArray(data_.retain4626), data_.receiverAddress, data_.extraFormData ); diff --git a/src/libraries/DataLib.sol b/src/libraries/DataLib.sol index bfaab9dce..d654a358b 100644 --- a/src/libraries/DataLib.sol +++ b/src/libraries/DataLib.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { Error } from "../libraries/Error.sol"; +import { Error } from "src/libraries/Error.sol"; -/// @dev rationale for "memory-safe" assembly: https://docs.soliditylang.org/en/v0.8.20/assembly.html#memory-safety library DataLib { function packTxInfo( uint8 txType_, diff --git a/src/libraries/Error.sol b/src/libraries/Error.sol index 30a01c097..049e4da59 100644 --- a/src/libraries/Error.sol +++ b/src/libraries/Error.sol @@ -2,14 +2,12 @@ pragma solidity ^0.8.23; library Error { + ////////////////////////////////////////////////////////////// // CONFIGURATION ERRORS // ////////////////////////////////////////////////////////////// ///@notice errors thrown in protocol setup - /// @dev thrown if there is an array length mismatch - error ARRAY_LENGTH_MISMATCH(); - /// @dev thrown if chain id exceeds max(uint64) error BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); @@ -25,15 +23,26 @@ library Error { /// @dev thrown if rescue delay is not yet set for a chain error DELAY_NOT_SET(); + /// @dev thrown if get native token price estimate in paymentHelper is 0 + error INVALID_NATIVE_TOKEN_PRICE(); + + /// @dev thrown if wormhole refund chain id is not set + error REFUND_CHAIN_ID_NOT_SET(); + /// @dev thrown if wormhole relayer is not set error RELAYER_NOT_SET(); + /// @dev thrown if a role to be revoked is not assigned + error ROLE_NOT_ASSIGNED(); + ////////////////////////////////////////////////////////////// // AUTHORIZATION ERRORS // ////////////////////////////////////////////////////////////// ///@notice errors thrown if functions cannot be called /// COMMON AUTHORIZATION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if caller is not address(this), internal call error INVALID_INTERNAL_CALL(); @@ -101,6 +110,8 @@ library Error { error NOT_PRIVILEGED_CALLER(bytes32 role); /// STATE REGISTRY AUTHORIZATION ERRORS + /// --------------------------------------------------------- + /// @dev layerzero adapter specific error, thrown if caller not layerzero endpoint error CALLER_NOT_ENDPOINT(); @@ -119,9 +130,17 @@ library Error { ///@notice errors thrown if input variables are not valid /// COMMON INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if there is an array length mismatch + error ARRAY_LENGTH_MISMATCH(); + /// @dev thrown if payload id does not exist error INVALID_PAYLOAD_ID(); + /// @dev error thrown when msg value should be zero in certain payable functions + error MSG_VALUE_NOT_ZERO(); + /// @dev thrown if amb ids length is 0 error ZERO_AMB_ID_LENGTH(); @@ -135,6 +154,8 @@ library Error { error ZERO_INPUT_VALUE(); /// SUPERFORM ROUTER INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if the vaults data is invalid error INVALID_SUPERFORMS_DATA(); @@ -142,13 +163,18 @@ library Error { error RECEIVER_ADDRESS_NOT_SET(); /// SUPERFORM FACTORY INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if a form is not ERC165 compatible error ERC165_UNSUPPORTED(); /// @dev thrown if a form is not form interface compatible error FORM_INTERFACE_UNSUPPORTED(); - /// @dev error thrown if beacon id already exists + /// @dev error thrown if form implementation address already exists + error FORM_IMPLEMENTATION_ALREADY_EXISTS(); + + /// @dev error thrown if form implementation id already exists error FORM_IMPLEMENTATION_ID_ALREADY_EXISTS(); /// @dev thrown if a form does not exist @@ -160,20 +186,16 @@ library Error { /// @dev thrown if superform not on factory error SUPERFORM_ID_NONEXISTENT(); - /// @dev thrown if same vault and beacon is used to create new superform + /// @dev thrown if same vault and form implementation is used to create new superform error VAULT_FORM_IMPLEMENTATION_COMBINATION_EXISTS(); /// FORM INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if in case of no txData, if liqData.token != vault.asset() /// in case of txData, if token output of swap != vault.asset() error DIFFERENT_TOKENS(); - /// @dev thrown if the amount in direct deposit is not correct - error DIRECT_DEPOSIT_INVALID_DATA(); - - /// @dev thrown if the token in direct withdraw is not correct - error DIRECT_WITHDRAW_INVALID_TOKEN(); - /// @dev thrown if the amount in direct withdraw is not correct error DIRECT_WITHDRAW_INVALID_LIQ_REQUEST(); @@ -181,6 +203,11 @@ library Error { error XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); /// LIQUIDITY BRIDGE INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev error thrown when txData selector of lifi bridged is a blacklisted selector + error BLACKLISTED_SELECTOR(); + /// @dev thrown if a certain action of the user is not allowed given the txData provided error INVALID_ACTION(); @@ -206,14 +233,16 @@ library Error { error NO_TXDATA_PRESENT(); /// STATE REGISTRY INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if payload is being updated with final amounts length different than amounts length error DIFFERENT_PAYLOAD_UPDATE_AMOUNTS_LENGTH(); /// @dev thrown if payload is being updated with tx data length different than liq data length error DIFFERENT_PAYLOAD_UPDATE_TX_DATA_LENGTH(); - /// @dev thrown if a duplicate proof amb is found - error DUPLICATE_PROOF_BRIDGE_ID(); + /// @dev thrown if keeper update final token is different than the vault underlying + error INVALID_UPDATE_FINAL_TOKEN(); /// @dev thrown if broadcast finality for wormhole is invalid error INVALID_BROADCAST_FINALITY(); @@ -221,7 +250,7 @@ library Error { /// @dev thrown if amb id is not valid leading to an address 0 of the implementation error INVALID_BRIDGE_ID(); - /// @dev thrown if chain id brought in the cross chain message is invalid + /// @dev thrown if chain id involved in xchain message is invalid error INVALID_CHAIN_ID(); /// @dev thrown if payload update amount isn't equal to dst swapper amount @@ -230,6 +259,9 @@ library Error { /// @dev thrown if message amb and proof amb are the same error INVALID_PROOF_BRIDGE_ID(); + /// @dev thrown if order of proof AMBs is incorrect, either duplicated or not incrementing + error INVALID_PROOF_BRIDGE_IDS(); + /// @dev thrown if rescue data lengths are invalid error INVALID_RESCUE_DATA(); @@ -243,6 +275,8 @@ library Error { error SLIPPAGE_OUT_OF_BOUNDS(); /// SUPERPOSITION INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if src senders mismatch in state sync error SRC_SENDER_MISMATCH(); @@ -255,8 +289,10 @@ library Error { ///@notice errors thrown due to function execution logic /// COMMON EXECUTION ERRORS - /// @dev thrown if allowance in direct deposit is not correct - error DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE(); + /// --------------------------------------------------------- + + /// @dev thrown if the swap in a direct deposit resulted in insufficient tokens + error DIRECT_DEPOSIT_SWAP_FAILED(); /// @dev thrown if payload is not unique error DUPLICATE_PAYLOAD(); @@ -264,6 +300,12 @@ library Error { /// @dev thrown if native tokens fail to be sent to superform contracts error FAILED_TO_SEND_NATIVE(); + /// @dev thrown if allowance is not correct to deposit + error INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT(); + + /// @dev thrown if contract has insufficient balance for operations + error INSUFFICIENT_BALANCE(); + /// @dev thrown if native amount is not at least equal to the amount in the request error INSUFFICIENT_NATIVE_AMOUNT(); @@ -276,20 +318,21 @@ library Error { /// @dev thrown if payload type is invalid error INVALID_PAYLOAD_TYPE(); - /// @dev thrown if contract has insufficient balance for operations - error INSUFFICIENT_BALANCE(); - /// LIQUIDITY BRIDGE EXECUTION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if we try to decode the final swap output token in a xChain liquidity bridging action error CANNOT_DECODE_FINAL_SWAP_OUTPUT_TOKEN(); /// @dev thrown if liquidity bridge fails for erc20 or native tokens error FAILED_TO_EXECUTE_TXDATA(address token); - /// @dev thrown if underlying asset mismatches + /// @dev thrown if asset being used for deposit mismatches in multivault deposits error INVALID_DEPOSIT_TOKEN(); /// STATE REGISTRY EXECUTION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if bridge tokens haven't arrived to destination error BRIDGE_TOKENS_PENDING(); @@ -308,6 +351,9 @@ library Error { /// @dev thrown if broadcast fee is invalid error INVALID_BROADCAST_FEE(); + /// @dev thrown if retry fees is less than required + error INVALID_RETRY_FEE(); + /// @dev thrown if broadcast message type is wrong error INVALID_MESSAGE_TYPE(); @@ -317,8 +363,11 @@ library Error { /// @dev thrown if update payload function was called on a wrong payload error INVALID_PAYLOAD_UPDATE_REQUEST(); - /// @dev thrown if src chain is blocked from messaging - error INVALID_SRC_CHAIN_ID(); + /// @dev thrown if a state registry id is 0 + error INVALID_REGISTRY_ID(); + + /// @dev thrown if a form state registry id is 0 + error INVALID_FORM_REGISTRY_ID(); /// @dev thrown if trying to finalize the payload but the withdraw is still locked error LOCKED(); @@ -341,28 +390,20 @@ library Error { /// @dev thrown if payload hash is zero during `retryMessage` on Layezero implementation error ZERO_PAYLOAD_HASH(); - /// @dev thrown in forms where a certain functionality is not allowed or implemented - error NOT_IMPLEMENTED(); - - /*/////////////////////////////////////////////////////////////// - PAYMASTER ERRORS - //////////////////////////////////////////////////////////////*/ - /// DST SWAPPER EXECUTION ERRORS - /// @dev forbid xChain deposits with destination swaps without interim token set (for user protection) - error INVALID_INTERIM_TOKEN(); + /// --------------------------------------------------------- /// @dev thrown if process dst swap is tried for processed payload id error DST_SWAP_ALREADY_PROCESSED(); + /// @dev thrown if indices have duplicates + error DUPLICATE_INDEX(); + /// @dev thrown if failed dst swap is already updated error FAILED_DST_SWAP_ALREADY_UPDATED(); - /// @dev thrown if failed dst swap is already processed - error FAILED_DST_SWAP_ALREADY_PROCESSED(); - - /// @dev thrown if dst swap output is less than minimum expected - error INVALID_SWAP_OUTPUT(); + /// @dev thrown if indices are out of bounds + error INDEX_OUT_OF_BOUNDS(); /// @dev thrown if failed swap token amount is 0 error INVALID_DST_SWAPPER_FAILED_SWAP(); @@ -373,27 +414,53 @@ library Error { /// @dev thrown if failed swap token amount is not 0 and if native amount is less than amount (non zero) error INVALID_DST_SWAPPER_FAILED_SWAP_NO_NATIVE_BALANCE(); + /// @dev forbid xChain deposits with destination swaps without interim token set (for user protection) + error INVALID_INTERIM_TOKEN(); + + /// @dev thrown if dst swap output is less than minimum expected + error INVALID_SWAP_OUTPUT(); + /// FORM EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if try to forward 4626 share from the superform + error CANNOT_FORWARD_4646_TOKEN(); + /// @dev thrown in KYCDAO form if no KYC token is present error NO_VALID_KYC_TOKEN(); - /// @dev thrown if implementation formBeacon is PAUSED, users cannot perform any action + /// @dev thrown in forms where a certain functionality is not allowed or implemented + error NOT_IMPLEMENTED(); + + /// @dev thrown if form implementation is PAUSED, users cannot perform any action error PAUSED(); + /// @dev thrown if shares != deposit output or assets != redeem output when minting SuperPositions + error VAULT_IMPLEMENTATION_FAILED(); + /// @dev thrown if withdrawal tx data is not updated error WITHDRAW_TOKEN_NOT_UPDATED(); /// @dev thrown if withdrawal tx data is not updated error WITHDRAW_TX_DATA_NOT_UPDATED(); + /// @dev thrown when redeeming from vault yields zero collateral + error WITHDRAW_ZERO_COLLATERAL(); + /// PAYMENT HELPER EXECUTION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if chainlink is reporting an improper price error CHAINLINK_MALFUNCTION(); /// @dev thrown if chainlink is reporting an incomplete round error CHAINLINK_INCOMPLETE_ROUND(); + /// @dev thrown if feed decimals is not 8 + error CHAINLINK_UNSUPPORTED_DECIMAL(); + /// EMERGENCY QUEUE EXECUTION ERRORS + /// --------------------------------------------------------- /// @dev thrown if emergency withdraw is not queued error EMERGENCY_WITHDRAW_NOT_QUEUED(); @@ -402,6 +469,8 @@ library Error { error EMERGENCY_WITHDRAW_PROCESSED_ALREADY(); /// SUPERPOSITION EXECUTION ERRORS + /// --------------------------------------------------------- + /// @dev thrown if uri cannot be updated error DYNAMIC_URI_FROZEN(); diff --git a/src/libraries/PayloadUpdaterLib.sol b/src/libraries/PayloadUpdaterLib.sol index a46be73b5..a1ae84064 100644 --- a/src/libraries/PayloadUpdaterLib.sol +++ b/src/libraries/PayloadUpdaterLib.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { DataLib } from "./DataLib.sol"; -import { Error } from "../libraries/Error.sol"; -import { PayloadState, CallbackType, LiqRequest } from "../types/DataTypes.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { Error } from "src/libraries/Error.sol"; +import { PayloadState, CallbackType, LiqRequest } from "src/types/DataTypes.sol"; /// @dev library to validate slippage updation library PayloadUpdaterLib { @@ -34,7 +34,7 @@ library PayloadUpdaterLib { function validateLiqReq(LiqRequest memory req_) internal pure { /// revert if token is address(0) -> user wants settlement without any liq data /// revert if token is not address(0) and txData is already present - if (req_.token == address(0) || (req_.token != address(0) && req_.txData.length != 0)) { + if (req_.token == address(0) || req_.txData.length != 0) { revert Error.CANNOT_UPDATE_WITHDRAW_TX_DATA(); } } diff --git a/src/libraries/ProofLib.sol b/src/libraries/ProofLib.sol index 707e1cac2..da7ca3ebe 100644 --- a/src/libraries/ProofLib.sol +++ b/src/libraries/ProofLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { AMBMessage } from "../types/DataTypes.sol"; +import { AMBMessage } from "src/types/DataTypes.sol"; /// @dev generates proof for amb message and bytes encoded message library ProofLib { diff --git a/src/payments/PayMaster.sol b/src/payments/PayMaster.sol index 7d3b518c6..a11714447 100644 --- a/src/payments/PayMaster.sol +++ b/src/payments/PayMaster.sol @@ -1,18 +1,24 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { Error } from "../libraries/Error.sol"; -import { ISuperRBAC } from "../interfaces/ISuperRBAC.sol"; -import { IPayMaster } from "../interfaces/IPayMaster.sol"; -import { ISuperRegistry } from "../interfaces/ISuperRegistry.sol"; -import { IBridgeValidator } from "../interfaces/IBridgeValidator.sol"; -import { IAmbImplementation } from "../interfaces/IAmbImplementation.sol"; -import { LiquidityHandler } from "../crosschain-liquidity/LiquidityHandler.sol"; -import { LiqRequest } from "../types/DataTypes.sol"; +import { LiquidityHandler } from "src/crosschain-liquidity/LiquidityHandler.sol"; +import { IPayMaster } from "src/interfaces/IPayMaster.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { IAmbImplementation } from "src/interfaces/IAmbImplementation.sol"; +import { Error } from "src/libraries/Error.sol"; +import { LiqRequest } from "src/types/DataTypes.sol"; +import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title PayMaster +/// @dev Manages cross-chain payments and rebalancing of funds /// @author ZeroPoint Labs contract PayMaster is IPayMaster, LiquidityHandler { + + using SafeERC20 for IERC20; + ////////////////////////////////////////////////////////////// // CONSTANTS // ////////////////////////////////////////////////////////////// @@ -45,6 +51,9 @@ contract PayMaster is IPayMaster, LiquidityHandler { ////////////////////////////////////////////////////////////// constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } superRegistry = ISuperRegistry(superRegistry_); } @@ -56,11 +65,19 @@ contract PayMaster is IPayMaster, LiquidityHandler { receive() external payable { } /// @inheritdoc IPayMaster - function withdrawTo(bytes32 superRegistryId_, uint256 nativeAmount_) external override onlyPaymentAdmin { - if (nativeAmount_ > address(this).balance) { - revert Error.FAILED_TO_SEND_NATIVE(); + function withdrawTo(bytes32 superRegistryId_, address token_, uint256 amount_) external override onlyPaymentAdmin { + if (amount_ == 0) { + revert Error.ZERO_INPUT_VALUE(); } + if (token_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + + _withdraw(superRegistry.getAddress(superRegistryId_), token_, amount_); + } + /// @inheritdoc IPayMaster + function withdrawNativeTo(bytes32 superRegistryId_, uint256 nativeAmount_) external override onlyPaymentAdmin { _withdrawNative(superRegistry.getAddress(superRegistryId_), nativeAmount_); } @@ -107,18 +124,35 @@ contract PayMaster is IPayMaster, LiquidityHandler { // INTERNAL FUNCTIONS // ////////////////////////////////////////////////////////////// + /// @dev helper to move tokens same chain + function _withdraw(address receiver_, address token_, uint256 amount_) internal { + IERC20 token = IERC20(token_); + + uint256 balance = token.balanceOf(address(this)); + if (balance < amount_) { + revert Error.INSUFFICIENT_BALANCE(); + } + token.safeTransfer(receiver_, amount_); + + emit TokenWithdrawn(receiver_, token_, amount_); + } + /// @dev helper to move native tokens same chain function _withdrawNative(address receiver_, uint256 amount_) internal { + if (address(this).balance < amount_) { + revert Error.FAILED_TO_SEND_NATIVE(); + } + (bool success,) = payable(receiver_).call{ value: amount_ }(""); if (!success) { revert Error.FAILED_TO_SEND_NATIVE(); } - emit PaymentWithdrawn(receiver_, amount_); + emit NativeWithdrawn(receiver_, amount_); } - /// @dev helper to move native tokens cross-chain + /// @dev helper to move tokens cross-chain (native or not) function _validateAndDispatchTokens(LiqRequest memory liqRequest_, address receiver_) internal { address bridgeValidator = superRegistry.getBridgeValidator(liqRequest_.bridgeId); diff --git a/src/payments/PaymentHelper.sol b/src/payments/PaymentHelper.sol index 9d87ed8d6..86dc43a77 100644 --- a/src/payments/PaymentHelper.sol +++ b/src/payments/PaymentHelper.sol @@ -1,17 +1,34 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { AggregatorV3Interface } from "../vendor/chainlink/AggregatorV3Interface.sol"; -import { IPaymentHelper } from "../interfaces/IPaymentHelper.sol"; -import { ISuperRBAC } from "../interfaces/ISuperRBAC.sol"; -import { ISuperRegistry } from "../interfaces/ISuperRegistry.sol"; -import { IBaseStateRegistry } from "../interfaces/IBaseStateRegistry.sol"; -import { IAmbImplementation } from "../interfaces/IAmbImplementation.sol"; -import { Error } from "../libraries/Error.sol"; -import { DataLib } from "../libraries/DataLib.sol"; -import { ProofLib } from "../libraries/ProofLib.sol"; -import { ArrayCastLib } from "../libraries/ArrayCastLib.sol"; -import "../types/DataTypes.sol"; +import { IPaymentHelper } from "src/interfaces/IPaymentHelper.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol"; +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; +import { IAmbImplementation } from "src/interfaces/IAmbImplementation.sol"; +import { Error } from "src/libraries/Error.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { ProofLib } from "src/libraries/ProofLib.sol"; +import { ArrayCastLib } from "src/libraries/ArrayCastLib.sol"; +import { + SingleDirectSingleVaultStateReq, + SingleXChainSingleVaultStateReq, + SingleDirectMultiVaultStateReq, + SingleXChainMultiVaultStateReq, + MultiDstSingleVaultStateReq, + MultiDstMultiVaultStateReq, + LiqRequest, + AMBMessage, + MultiVaultSFData, + SingleVaultSFData, + AMBExtraData, + InitMultiVaultData, + InitSingleVaultData, + ReturnMultiData, + ReturnSingleData +} from "src/types/DataTypes.sol"; +import { AggregatorV3Interface } from "src/vendor/chainlink/AggregatorV3Interface.sol"; /// @dev interface to read public variable from state registry interface ReadOnlyBaseRegistry is IBaseStateRegistry { @@ -19,8 +36,8 @@ interface ReadOnlyBaseRegistry is IBaseStateRegistry { } /// @title PaymentHelper +/// @dev Helps estimate the cost for the entire transaction lifecycle /// @author ZeroPoint Labs -/// @dev helps estimating the cost for the entire transaction lifecycle contract PaymentHelper is IPaymentHelper { using DataLib for uint256; using ArrayCastLib for LiqRequest; @@ -32,9 +49,13 @@ contract PaymentHelper is IPaymentHelper { // CONSTANTS // ////////////////////////////////////////////////////////////// + uint256 private constant PROOF_LENGTH = 160; + uint8 private constant SUPPORTED_FEED_PRECISION = 8; + uint32 private constant TIMELOCK_FORM_ID = 2; + uint256 private constant MAX_UINT256 = type(uint256).max; + ISuperRegistry public immutable superRegistry; uint64 public immutable CHAIN_ID; - uint32 private constant TIMELOCK_FORM_ID = 2; ////////////////////////////////////////////////////////////// // STATE VARIABLES // @@ -52,9 +73,9 @@ contract PaymentHelper is IPaymentHelper { mapping(uint64 chainId => uint256 gasPerByte) public gasPerByte; mapping(uint64 chainId => uint256 gasForOps) public ackGasCost; mapping(uint64 chainId => uint256 gasForOps) public timelockCost; + mapping(uint64 chainId => uint256 gasForOps) public emergencyCost; /// @dev register transmuter params - uint256 public totalTransmuterFees; bytes public extraDataForTransmuter; ////////////////////////////////////////////////////////////// @@ -72,6 +93,14 @@ contract PaymentHelper is IPaymentHelper { bytes message; } + struct LocalEstimateVars { + uint256 len; + uint256 superformIdsLen; + uint256 totalDstGas; + uint256 ambFees; + bool paused; + } + ////////////////////////////////////////////////////////////// // MODIFIERS // ////////////////////////////////////////////////////////////// @@ -95,6 +124,10 @@ contract PaymentHelper is IPaymentHelper { ////////////////////////////////////////////////////////////// constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + if (block.chainid > type(uint64).max) { revert Error.BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); } @@ -126,13 +159,8 @@ contract PaymentHelper is IPaymentHelper { } /// @inheritdoc IPaymentHelper - function getRegisterTransmuterAMBData() - external - view - override - returns (uint256 totalFees, bytes memory extraData) - { - return (totalTransmuterFees, extraDataForTransmuter); + function getRegisterTransmuterAMBData() external view override returns (bytes memory) { + return extraDataForTransmuter; } /// @inheritdoc IPaymentHelper @@ -145,53 +173,64 @@ contract PaymentHelper is IPaymentHelper { override returns (uint256 liqAmount, uint256 srcAmount, uint256 dstAmount, uint256 totalAmount) { - uint256 len = req_.dstChainIds.length; - uint256 superformIdsLen; - uint256 totalDstGas; + LocalEstimateVars memory v; + v.len = req_.dstChainIds.length; - for (uint256 i; i < len; ++i) { - totalDstGas = 0; + ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + for (uint256 i; i < v.len; ++i) { + bool xChain = req_.dstChainIds[i] != CHAIN_ID; + + v.totalDstGas = 0; /// @dev step 1: estimate amb costs - uint256 ambFees = _estimateAMBFees( - req_.ambIds[i], req_.dstChainIds[i], _generateMultiVaultMessage(req_.superformsData[i]) - ); + v.ambFees = xChain + ? _estimateAMBFees(req_.ambIds[i], req_.dstChainIds[i], _generateMultiVaultMessage(req_.superformsData[i])) + : 0; - superformIdsLen = req_.superformsData[i].superformIds.length; + v.superformIdsLen = req_.superformsData[i].superformIds.length; - srcAmount += ambFees; + srcAmount += v.ambFees; if (isDeposit_) { - /// @dev step 2: estimate update cost (only for deposit) - totalDstGas += _estimateUpdateCost(req_.dstChainIds[i], superformIdsLen); - - /// @dev step 3: estimation processing cost of acknowledgement - /// @notice optimistically estimating. (Ideal case scenario: no failed deposits / withdrawals) - srcAmount += _estimateAckProcessingCost(superformIdsLen); - - /// @dev step 4: estimate liq amount + /// @dev step 2: estimate liq amount liqAmount += _estimateLiqAmount(req_.superformsData[i].liqRequests); - /// @dev step 5: estimate dst swap cost if it exists - totalDstGas += _estimateSwapFees(req_.dstChainIds[i], req_.superformsData[i].hasDstSwaps); - } + if (xChain) { + /// @dev step 3: estimate update cost (only for deposit) + v.totalDstGas += _estimateUpdateCost(req_.dstChainIds[i], v.superformIdsLen); - /// @dev step 6: estimate execution costs in dst (withdraw / deposit) - /// note: execution cost includes acknowledgement messaging cost - totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainIds[i], superformIdsLen); + uint256 arrLen = req_.superformsData[i].retain4626s.length; + uint256 ackLen; + for (uint256 j; j < arrLen; ++j) { + if (!req_.superformsData[i].retain4626s[j]) ++ackLen; + } + /// @dev step 4: estimation processing cost of acknowledgement + /// @notice optimistically estimating. (Ideal case scenario: no failed deposits / withdrawals) + srcAmount += _estimateAckProcessingCost(v.superformIdsLen); - /// @dev step 6: estimate if timelock form processing costs are involved - if (!isDeposit_) { - for (uint256 j; j < superformIdsLen; ++j) { + /// @dev step 5: estimate dst swap cost if it exists + v.totalDstGas += _estimateSwapFees(req_.dstChainIds[i], req_.superformsData[i].hasDstSwaps); + } + } else { + /// @dev step 6: estimate if timelock form processing costs are involved + for (uint256 j; j < v.superformIdsLen; ++j) { (, uint32 formId,) = req_.superformsData[i].superformIds[j].getSuperform(); - if (formId == TIMELOCK_FORM_ID) { - totalDstGas += timelockCost[req_.dstChainIds[i]]; + v.paused = factory.isFormImplementationPaused(formId); + + if (!v.paused && formId == TIMELOCK_FORM_ID) { + v.totalDstGas += timelockCost[req_.dstChainIds[i]]; + } else if (v.paused) { + v.totalDstGas += emergencyCost[req_.dstChainIds[i]]; } } } - /// @dev step 7: convert all dst gas estimates to src chain estimate (withdraw / deposit) - dstAmount += _convertToNativeFee(req_.dstChainIds[i], totalDstGas); + /// @dev step 7: estimate execution costs in dst (withdraw / deposit) + /// note: execution cost includes acknowledgement messaging cost + v.totalDstGas += xChain ? _estimateDstExecutionCost(isDeposit_, req_.dstChainIds[i], v.superformIdsLen) : 0; + + /// @dev step 8: convert all dst gas estimates to src chain estimate (withdraw / deposit) + dstAmount += _convertToNativeFee(req_.dstChainIds[i], v.totalDstGas); } totalAmount = srcAmount + dstAmount + liqAmount; @@ -208,42 +247,53 @@ contract PaymentHelper is IPaymentHelper { returns (uint256 liqAmount, uint256 srcAmount, uint256 dstAmount, uint256 totalAmount) { uint256 len = req_.dstChainIds.length; + ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + for (uint256 i; i < len; ++i) { + bool xChain = req_.dstChainIds[i] != CHAIN_ID; uint256 totalDstGas; /// @dev step 1: estimate amb costs - uint256 ambFees = _estimateAMBFees( - req_.ambIds[i], req_.dstChainIds[i], _generateSingleVaultMessage(req_.superformsData[i]) - ); + uint256 ambFees = xChain + ? _estimateAMBFees(req_.ambIds[i], req_.dstChainIds[i], _generateSingleVaultMessage(req_.superformsData[i])) + : 0; srcAmount += ambFees; if (isDeposit_) { - /// @dev step 2: estimate update cost (only for deposit) - totalDstGas += _estimateUpdateCost(req_.dstChainIds[i], 1); + /// @dev step 2: estimate the liqAmount + liqAmount += _estimateLiqAmount(req_.superformsData[i].liqRequest.castLiqRequestToArray()); + if (xChain) { + /// @dev step 3: estimate update cost (only for deposit) + totalDstGas += _estimateUpdateCost(req_.dstChainIds[i], 1); - /// @dev step 3: estimation execution cost of acknowledgement - srcAmount += _estimateAckProcessingCost(1); + /// @dev step 4: estimation execution cost of acknowledgement + if (!req_.superformsData[i].retain4626) { + srcAmount += _estimateAckProcessingCost(1); + } - /// @dev step 4: estimate the liqAmount - liqAmount += _estimateLiqAmount(req_.superformsData[i].liqRequest.castLiqRequestToArray()); + /// @dev step 5: estimate if swap costs are involved + totalDstGas += + _estimateSwapFees(req_.dstChainIds[i], req_.superformsData[i].hasDstSwap.castBoolToArray()); + } + } else { + /// @dev step 6: estimate if timelock form processing costs are involved + (, uint32 formId,) = req_.superformsData[i].superformId.getSuperform(); + + bool paused = factory.isFormImplementationPaused(formId); - /// @dev step 5: estimate if swap costs are involved - totalDstGas += - _estimateSwapFees(req_.dstChainIds[i], req_.superformsData[i].hasDstSwap.castBoolToArray()); + if (!paused && formId == TIMELOCK_FORM_ID) { + totalDstGas += timelockCost[req_.dstChainIds[i]]; + } else if (paused) { + totalDstGas += emergencyCost[req_.dstChainIds[i]]; + } } - /// @dev step 5: estimate execution costs in dst + /// @dev step 7: estimate execution costs in dst /// note: execution cost includes acknowledgement messaging cost - totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainIds[i], 1); - - /// @dev step 6: estimate if timelock form processing costs are involved - (, uint32 formId,) = req_.superformsData[i].superformId.getSuperform(); - if (!isDeposit_ && formId == TIMELOCK_FORM_ID) { - totalDstGas += timelockCost[req_.dstChainIds[i]]; - } + totalDstGas += xChain ? _estimateDstExecutionCost(isDeposit_, req_.dstChainIds[i], 1) : 0; - /// @dev step 7: convert all dst gas estimates to src chain estimate + /// @dev step 8: convert all dst gas estimates to src chain estimate dstAmount += _convertToNativeFee(req_.dstChainIds[i], totalDstGas); } @@ -263,40 +313,51 @@ contract PaymentHelper is IPaymentHelper { uint256 totalDstGas; uint256 superformIdsLen = req_.superformsData.superformIds.length; - /// @dev step 1: estimate amb costs + ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + + /// @dev step 1: estimate AMB costs uint256 ambFees = _estimateAMBFees(req_.ambIds, req_.dstChainId, _generateMultiVaultMessage(req_.superformsData)); - srcAmount += ambFees; - /// @dev step 2: estimate update cost (only for deposit) - if (isDeposit_) totalDstGas += _estimateUpdateCost(req_.dstChainId, superformIdsLen); + if (isDeposit_) { + /// @dev step 2: estimate update cost (only for deposit) + totalDstGas += _estimateUpdateCost(req_.dstChainId, superformIdsLen); - /// @dev step 3: estimate execution costs in dst - /// note: execution cost includes acknowledgement messaging cost - totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainId, superformIdsLen); + uint256 arrLen = req_.superformsData.retain4626s.length; + uint256 ackLen; - /// @dev step 4: estimation execution cost of acknowledgement - if (isDeposit_) srcAmount += _estimateAckProcessingCost(superformIdsLen); + for (uint256 i; i < arrLen; ++i) { + if (!req_.superformsData.retain4626s[i]) ++ackLen; + } - /// @dev step 5: estimate liq amount - if (isDeposit_) liqAmount += _estimateLiqAmount(req_.superformsData.liqRequests); + /// @dev step 3: estimation execution cost of acknowledgement + srcAmount += _estimateAckProcessingCost(ackLen); - /// @dev step 6: estimate if swap costs are involved - if (isDeposit_) totalDstGas += _estimateSwapFees(req_.dstChainId, req_.superformsData.hasDstSwaps); + /// @dev step 4: estimate the liqAmount + liqAmount += _estimateLiqAmount(req_.superformsData.liqRequests); - /// @dev step 7: estimate if timelock form processing costs are involved - if (!isDeposit_) { + /// @dev step 5: estimate if swap costs are involved + totalDstGas += _estimateSwapFees(req_.dstChainId, req_.superformsData.hasDstSwaps); + } else { + /// @dev step 6: process non-deposit logic for timelock form processing costs for (uint256 i; i < superformIdsLen; ++i) { (, uint32 formId,) = req_.superformsData.superformIds[i].getSuperform(); - if (formId == TIMELOCK_FORM_ID) { - totalDstGas += timelockCost[CHAIN_ID]; + bool paused = factory.isFormImplementationPaused(formId); + + if (!paused && formId == TIMELOCK_FORM_ID) { + totalDstGas += timelockCost[req_.dstChainId]; + } else if (paused) { + totalDstGas += emergencyCost[req_.dstChainId]; } } } - /// @dev step 8: convert all dst gas estimates to src chain estimate + /// @dev step 7: estimate execution costs in destination + totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainId, superformIdsLen); + + /// @dev step 8: convert all destination gas estimates to source chain estimate dstAmount += _convertToNativeFee(req_.dstChainId, totalDstGas); totalAmount = srcAmount + dstAmount + liqAmount; @@ -313,37 +374,44 @@ contract PaymentHelper is IPaymentHelper { returns (uint256 liqAmount, uint256 srcAmount, uint256 dstAmount, uint256 totalAmount) { uint256 totalDstGas; - /// @dev step 1: estimate amb costs + ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + + /// @dev step 1: estimate AMB costs uint256 ambFees = _estimateAMBFees(req_.ambIds, req_.dstChainId, _generateSingleVaultMessage(req_.superformData)); - srcAmount += ambFees; - /// @dev step 2: estimate update cost (only for deposit) - if (isDeposit_) totalDstGas += _estimateUpdateCost(req_.dstChainId, 1); - - /// @dev step 3: estimate execution costs in dst - /// note: execution cost includes acknowledgement messaging cost - totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainId, 1); + if (isDeposit_) { + /// @dev step 2: estimate update cost (only for deposit) + totalDstGas += _estimateUpdateCost(req_.dstChainId, 1); - /// @dev step 4: estimation execution cost of acknowledgement - if (isDeposit_) srcAmount += _estimateAckProcessingCost(1); + /// @dev step 3: estimation execution cost of acknowledgement + if (!req_.superformData.retain4626) { + srcAmount += _estimateAckProcessingCost(1); + } - /// @dev step 5: estimate the liq amount - if (isDeposit_) liqAmount += _estimateLiqAmount(req_.superformData.liqRequest.castLiqRequestToArray()); + /// @dev step 4: estimate the liqAmount + liqAmount += _estimateLiqAmount(req_.superformData.liqRequest.castLiqRequestToArray()); - /// @dev step 6: estimate if swap costs are involved - if (isDeposit_) { + /// @dev step 5: estimate if swap costs are involved totalDstGas += _estimateSwapFees(req_.dstChainId, req_.superformData.hasDstSwap.castBoolToArray()); - } + } else { + /// @dev step 6: process non-deposit logic for timelock form processing costs + (, uint32 formId,) = req_.superformData.superformId.getSuperform(); + + bool paused = factory.isFormImplementationPaused(formId); - /// @dev step 7: estimate if timelock form processing costs are involved - (, uint32 formId,) = req_.superformData.superformId.getSuperform(); - if (!isDeposit_ && formId == TIMELOCK_FORM_ID) { - totalDstGas += timelockCost[CHAIN_ID]; + if (!paused && formId == TIMELOCK_FORM_ID) { + totalDstGas += timelockCost[req_.dstChainId]; + } else if (paused) { + totalDstGas += emergencyCost[req_.dstChainId]; + } } - /// @dev step 8: convert all dst gas estimates to src chain estimate + /// @dev step 7: estimate execution costs in destination + totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainId, 1); + + /// @dev step 8: convert all destination gas estimates to source chain estimate dstAmount += _convertToNativeFee(req_.dstChainId, totalDstGas); totalAmount = srcAmount + dstAmount + liqAmount; @@ -359,13 +427,22 @@ contract PaymentHelper is IPaymentHelper { override returns (uint256 liqAmount, uint256 srcAmount, uint256 totalAmount) { - (, uint32 formId,) = req_.superformData.superformId.getSuperform(); - /// @dev only if timelock form withdrawal is involved - if (!isDeposit_ && formId == TIMELOCK_FORM_ID) { - srcAmount += timelockCost[CHAIN_ID] * _getGasPrice(CHAIN_ID); - } + ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + + if (!isDeposit_) { + /// @dev only if timelock form withdrawal is involved + (, uint32 formId,) = req_.superformData.superformId.getSuperform(); - if (isDeposit_) liqAmount += _estimateLiqAmount(req_.superformData.liqRequest.castLiqRequestToArray()); + bool paused = factory.isFormImplementationPaused(formId); + + if (!paused && formId == TIMELOCK_FORM_ID) { + srcAmount += timelockCost[CHAIN_ID] * _getGasPrice(CHAIN_ID); + } else if (paused) { + srcAmount += emergencyCost[CHAIN_ID] * _getGasPrice(CHAIN_ID); + } + } else { + liqAmount = _estimateLiqAmount(req_.superformData.liqRequest.castLiqRequestToArray()); + } /// @dev not adding dstAmount to save some GAS totalAmount = liqAmount + srcAmount; @@ -381,18 +458,26 @@ contract PaymentHelper is IPaymentHelper { override returns (uint256 liqAmount, uint256 srcAmount, uint256 totalAmount) { - uint256 len = req_.superformData.superformIds.length; - for (uint256 i; i < len; ++i) { - (, uint32 formId,) = req_.superformData.superformIds[i].getSuperform(); - uint256 timelockPrice = timelockCost[uint64(block.chainid)] * _getGasPrice(uint64(block.chainid)); - /// @dev only if timelock form withdrawal is involved - if (!isDeposit_ && formId == TIMELOCK_FORM_ID) { - srcAmount += timelockPrice; + ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + + if (!isDeposit_) { + uint256 len = req_.superformData.superformIds.length; + uint256 timelockPrice = timelockCost[CHAIN_ID] * _getGasPrice(CHAIN_ID); + uint256 emergencyPrice = emergencyCost[CHAIN_ID] * _getGasPrice(CHAIN_ID); + for (uint256 i; i < len; ++i) { + (, uint32 formId,) = req_.superformData.superformIds[i].getSuperform(); + bool paused = factory.isFormImplementationPaused(formId); + + if (!paused && formId == TIMELOCK_FORM_ID) { + srcAmount += timelockPrice; + } else if (paused) { + srcAmount += emergencyPrice; + } } + } else { + liqAmount += _estimateLiqAmount(req_.superformData.liqRequests); } - if (isDeposit_) liqAmount += _estimateLiqAmount(req_.superformData.liqRequests); - /// @dev not adding dstAmount to save some GAS totalAmount = liqAmount + srcAmount; } @@ -412,19 +497,51 @@ contract PaymentHelper is IPaymentHelper { uint256[] memory fees = new uint256[](len); /// @dev just checks the estimate for sending message from src -> dst - for (uint256 i; i < len; ++i) { - fees[i] = CHAIN_ID != dstChainId_ - ? IAmbImplementation(superRegistry.getAmbAddress(ambIds_[i])).estimateFees( + if (CHAIN_ID != dstChainId_) { + for (uint256 i; i < len; ++i) { + fees[i] = IAmbImplementation(superRegistry.getAmbAddress(ambIds_[i])).estimateFees( dstChainId_, message_, extraData_[i] - ) - : 0; + ); - totalFees += fees[i]; + totalFees += fees[i]; + } } return (totalFees, fees); } + /// @dev helps estimate the acknowledgement costs for amb processing + function estimateAckCost(uint256 payloadId_) external view returns (uint256 totalFees) { + EstimateAckCostVars memory v; + IBaseStateRegistry coreStateRegistry = + IBaseStateRegistry(superRegistry.getAddress(keccak256("CORE_STATE_REGISTRY"))); + v.currPayloadId = coreStateRegistry.payloadsCount(); + + if (payloadId_ > v.currPayloadId) revert Error.INVALID_PAYLOAD_ID(); + + v.payloadHeader = coreStateRegistry.payloadHeader(payloadId_); + v.payloadBody = coreStateRegistry.payloadBody(payloadId_); + + (, v.callbackType, v.isMulti,,, v.srcChainId) = DataLib.decodeTxInfo(v.payloadHeader); + + /// if callback type is return then return 0 + if (v.callbackType != 0) return 0; + + if (v.isMulti == 1) { + InitMultiVaultData memory data = abi.decode(v.payloadBody, (InitMultiVaultData)); + v.payloadBody = abi.encode(ReturnMultiData(v.currPayloadId, data.superformIds, data.amounts)); + } else { + InitSingleVaultData memory data = abi.decode(v.payloadBody, (InitSingleVaultData)); + v.payloadBody = abi.encode(ReturnSingleData(v.currPayloadId, data.superformId, data.amount)); + } + + v.ackAmbIds = coreStateRegistry.getMessageAMB(payloadId_); + + v.message = abi.encode(AMBMessage(coreStateRegistry.payloadHeader(payloadId_), v.payloadBody)); + + return _estimateAMBFees(v.ackAmbIds, v.srcChainId, v.message); + } + ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// @@ -439,11 +556,21 @@ contract PaymentHelper is IPaymentHelper { onlyProtocolAdmin { if (config_.nativeFeedOracle != address(0)) { - nativeFeedOracle[chainId_] = AggregatorV3Interface(config_.nativeFeedOracle); + AggregatorV3Interface nativeFeedOracleContract = AggregatorV3Interface(config_.nativeFeedOracle); + if (nativeFeedOracleContract.decimals() != SUPPORTED_FEED_PRECISION) { + revert Error.CHAINLINK_UNSUPPORTED_DECIMAL(); + } + + nativeFeedOracle[chainId_] = nativeFeedOracleContract; } if (config_.gasPriceOracle != address(0)) { - gasPriceOracle[chainId_] = AggregatorV3Interface(config_.gasPriceOracle); + AggregatorV3Interface gasPriceOracleContract = AggregatorV3Interface(config_.nativeFeedOracle); + if (gasPriceOracleContract.decimals() != SUPPORTED_FEED_PRECISION) { + revert Error.CHAINLINK_UNSUPPORTED_DECIMAL(); + } + + gasPriceOracle[chainId_] = gasPriceOracleContract; } swapGasUsed[chainId_] = config_.swapGasUsed; @@ -455,6 +582,9 @@ contract PaymentHelper is IPaymentHelper { gasPerByte[chainId_] = config_.dstGasPerByte; ackGasCost[chainId_] = config_.ackGasCost; timelockCost[chainId_] = config_.timelockCost; + emergencyCost[chainId_] = config_.emergencyCost; + + emit ChainConfigAdded(chainId_, config_); } /// @inheritdoc IPaymentHelper @@ -469,12 +599,32 @@ contract PaymentHelper is IPaymentHelper { { /// @dev Type 1: DST TOKEN PRICE FEED ORACLE if (configType_ == 1) { - nativeFeedOracle[chainId_] = AggregatorV3Interface(abi.decode(config_, (address))); + AggregatorV3Interface nativeFeedOracleContract = AggregatorV3Interface(abi.decode(config_, (address))); + + /// @dev allows setting price feed to address(0), equivalent for resetting native price + if ( + address(nativeFeedOracleContract) != address(0) + && nativeFeedOracleContract.decimals() != SUPPORTED_FEED_PRECISION + ) { + revert Error.CHAINLINK_UNSUPPORTED_DECIMAL(); + } + + nativeFeedOracle[chainId_] = nativeFeedOracleContract; } /// @dev Type 2: DST GAS PRICE ORACLE if (configType_ == 2) { - gasPriceOracle[chainId_] = AggregatorV3Interface(abi.decode(config_, (address))); + AggregatorV3Interface gasPriceOracleContract = AggregatorV3Interface(abi.decode(config_, (address))); + + /// @dev allows setting gas price to address(0), equivalent for resetting gas price + if ( + address(gasPriceOracleContract) != address(0) + && gasPriceOracleContract.decimals() != SUPPORTED_FEED_PRECISION + ) { + revert Error.CHAINLINK_UNSUPPORTED_DECIMAL(); + } + + gasPriceOracle[chainId_] = gasPriceOracleContract; } /// @dev Type 3: SWAP GAS USED @@ -522,18 +672,16 @@ contract PaymentHelper is IPaymentHelper { timelockCost[chainId_] = abi.decode(config_, (uint256)); } + /// @dev Type 12: EMERGENCY PROCESSING COST + if (configType_ == 12) { + emergencyCost[chainId_] = abi.decode(config_, (uint256)); + } + emit ChainConfigUpdated(chainId_, configType_, config_); } /// @inheritdoc IPaymentHelper - function updateRegisterAERC20Params( - uint256 totalTransmuterFees_, - bytes memory extraDataForTransmuter_ - ) - external - onlyEmergencyAdmin - { - totalTransmuterFees = totalTransmuterFees_; + function updateRegisterAERC20Params(bytes memory extraDataForTransmuter_) external onlyEmergencyAdmin { extraDataForTransmuter = extraDataForTransmuter_; } @@ -558,16 +706,13 @@ contract PaymentHelper is IPaymentHelper { uint256 gasReqPerByte = gasPerByte[dstChainId_]; uint256 totalDstGasReqInWei = abi.encode(ambIdEncodedMessage).length * gasReqPerByte; - AMBMessage memory decodedMessage = abi.decode(message_, (AMBMessage)); - decodedMessage.params = message_.computeProofBytes(); - - uint256 totalDstGasReqInWeiForProof = abi.encode(decodedMessage).length * gasReqPerByte; + /// @dev proof length is always of fixed length + uint256 totalDstGasReqInWeiForProof = PROOF_LENGTH * gasReqPerByte; extraDataPerAMB = new bytes[](len); for (uint256 i; i < len; ++i) { uint256 gasReq = i != 0 ? totalDstGasReqInWeiForProof : totalDstGasReqInWei; - /// @dev amb id 1: layerzero /// @dev amb id 2: hyperlane /// @dev amb id 3: wormhole @@ -585,38 +730,6 @@ contract PaymentHelper is IPaymentHelper { } } - /// @dev helps estimate the acknowledgement costs for amb processing - function estimateAckCost(uint256 payloadId_) external view returns (uint256 totalFees) { - EstimateAckCostVars memory v; - IBaseStateRegistry coreStateRegistry = - IBaseStateRegistry(superRegistry.getAddress(keccak256("CORE_STATE_REGISTRY"))); - v.currPayloadId = coreStateRegistry.payloadsCount(); - - if (payloadId_ > v.currPayloadId) revert Error.INVALID_PAYLOAD_ID(); - - v.payloadHeader = coreStateRegistry.payloadHeader(payloadId_); - v.payloadBody = coreStateRegistry.payloadBody(payloadId_); - - (, v.callbackType, v.isMulti,,, v.srcChainId) = DataLib.decodeTxInfo(v.payloadHeader); - - /// if callback type is return then return 0 - if (v.callbackType != 0) return 0; - - if (v.isMulti == 1) { - InitMultiVaultData memory data = abi.decode(v.payloadBody, (InitMultiVaultData)); - v.payloadBody = abi.encode(ReturnMultiData(v.currPayloadId, data.superformIds, data.amounts)); - } else { - InitSingleVaultData memory data = abi.decode(v.payloadBody, (InitSingleVaultData)); - v.payloadBody = abi.encode(ReturnSingleData(v.currPayloadId, data.superformId, data.amount)); - } - - v.ackAmbIds = coreStateRegistry.getMessageAMB(payloadId_); - - v.message = abi.encode(AMBMessage(coreStateRegistry.payloadHeader(payloadId_), v.payloadBody)); - - return _estimateAMBFees(v.ackAmbIds, v.srcChainId, v.message); - } - /// @dev helps estimate the cross-chain message costs function _estimateAMBFees( uint8[] memory ambIds_, @@ -634,18 +747,18 @@ contract PaymentHelper is IPaymentHelper { AMBMessage memory ambIdEncodedMessage = abi.decode(message_, (AMBMessage)); ambIdEncodedMessage.params = abi.encode(ambIds_, ambIdEncodedMessage.params); - bytes memory proof_ = abi.encode(AMBMessage(type(uint256).max, abi.encode(keccak256(message_)))); + bytes memory proof_ = abi.encode(AMBMessage(MAX_UINT256, abi.encode(keccak256(message_)))); /// @dev just checks the estimate for sending message from src -> dst /// @dev only ambIds_[0] = primary amb (rest of the ambs send only the proof) - for (uint256 i; i < len; ++i) { - uint256 tempFee = CHAIN_ID != dstChainId_ - ? IAmbImplementation(superRegistry.getAmbAddress(ambIds_[i])).estimateFees( + if (CHAIN_ID != dstChainId_) { + for (uint256 i; i < len; ++i) { + uint256 tempFee = IAmbImplementation(superRegistry.getAmbAddress(ambIds_[i])).estimateFees( dstChainId_, i != 0 ? proof_ : abi.encode(ambIdEncodedMessage), extraDataPerAMB[i] - ) - : 0; + ); - totalFees += tempFee; + totalFees += tempFee; + } } } @@ -668,18 +781,18 @@ contract PaymentHelper is IPaymentHelper { feeSplitUp = new uint256[](len); - bytes memory proof_ = abi.encode(AMBMessage(type(uint256).max, abi.encode(keccak256(message_)))); + bytes memory proof_ = abi.encode(AMBMessage(MAX_UINT256, abi.encode(keccak256(message_)))); /// @dev just checks the estimate for sending message from src -> dst - for (uint256 i; i < len; ++i) { - uint256 tempFee = CHAIN_ID != dstChainId_ - ? IAmbImplementation(superRegistry.getAmbAddress(ambIds_[i])).estimateFees( + if (CHAIN_ID != dstChainId_) { + for (uint256 i; i < len; ++i) { + uint256 tempFee = IAmbImplementation(superRegistry.getAmbAddress(ambIds_[i])).estimateFees( dstChainId_, i != 0 ? proof_ : abi.encode(ambIdEncodedMessage), extraDataPerAMB[i] - ) - : 0; + ); - totalFees += tempFee; - feeSplitUp[i] = tempFee; + totalFees += tempFee; + feeSplitUp[i] = tempFee; + } } } @@ -759,6 +872,7 @@ contract PaymentHelper is IPaymentHelper { _getNextPayloadId(), sfData_.superformId, sfData_.amount, + sfData_.outputAmount, sfData_.maxSlippage, sfData_.liqRequest, sfData_.hasDstSwap, @@ -767,7 +881,7 @@ contract PaymentHelper is IPaymentHelper { sfData_.extraFormData ) ); - message_ = abi.encode(AMBMessage(type(uint256).max, ambData)); + message_ = abi.encode(AMBMessage(MAX_UINT256, ambData)); } /// @dev generates the amb message for multi vault data @@ -781,6 +895,7 @@ contract PaymentHelper is IPaymentHelper { _getNextPayloadId(), sfData_.superformIds, sfData_.amounts, + sfData_.outputAmounts, sfData_.maxSlippages, sfData_.liqRequests, sfData_.hasDstSwaps, @@ -789,7 +904,7 @@ contract PaymentHelper is IPaymentHelper { sfData_.extraFormData ) ); - message_ = abi.encode(AMBMessage(type(uint256).max, ambData)); + message_ = abi.encode(AMBMessage(MAX_UINT256, ambData)); } /// @dev helps convert the dst gas fee into src chain native fee @@ -815,6 +930,8 @@ contract PaymentHelper is IPaymentHelper { /// @dev converts the usd value to source chain's native token /// @dev native token price is 8 decimal which cancels the 8 decimal multiplied in previous step + uint256 nativeTokenPrice = _getNativeTokenPrice(CHAIN_ID); // native token price - 8 decimal + if (nativeTokenPrice == 0) revert Error.INVALID_NATIVE_TOKEN_PRICE(); nativeFee = (dstUsdValue) / _getNativeTokenPrice(CHAIN_ID); } @@ -830,10 +947,15 @@ contract PaymentHelper is IPaymentHelper { function _getGasPrice(uint64 chainId_) internal view returns (uint256) { address oracleAddr = address(gasPriceOracle[chainId_]); if (oracleAddr != address(0)) { - (, int256 value,, uint256 updatedAt,) = AggregatorV3Interface(oracleAddr).latestRoundData(); - if (value <= 0) revert Error.CHAINLINK_MALFUNCTION(); - if (updatedAt == 0) revert Error.CHAINLINK_INCOMPLETE_ROUND(); - return uint256(value); + try AggregatorV3Interface(oracleAddr).latestRoundData() returns ( + uint80, int256 value, uint256, uint256 updatedAt, uint80 + ) { + if (value <= 0) revert Error.CHAINLINK_MALFUNCTION(); + if (updatedAt == 0) revert Error.CHAINLINK_INCOMPLETE_ROUND(); + return uint256(value); + } catch { + /// @dev do nothing and return the default price at the end of the function + } } return gasPrice[chainId_]; @@ -844,10 +966,15 @@ contract PaymentHelper is IPaymentHelper { function _getNativeTokenPrice(uint64 chainId_) internal view returns (uint256) { address oracleAddr = address(nativeFeedOracle[chainId_]); if (oracleAddr != address(0)) { - (, int256 dstTokenPrice,, uint256 updatedAt,) = AggregatorV3Interface(oracleAddr).latestRoundData(); - if (dstTokenPrice <= 0) revert Error.CHAINLINK_MALFUNCTION(); - if (updatedAt == 0) revert Error.CHAINLINK_INCOMPLETE_ROUND(); - return uint256(dstTokenPrice); + try AggregatorV3Interface(oracleAddr).latestRoundData() returns ( + uint80, int256 dstTokenPrice, uint256, uint256 updatedAt, uint80 + ) { + if (dstTokenPrice <= 0) revert Error.CHAINLINK_MALFUNCTION(); + if (updatedAt == 0) revert Error.CHAINLINK_INCOMPLETE_ROUND(); + return uint256(dstTokenPrice); + } catch { + /// @dev do nothing and return the default price at the end of the function + } } return nativePrice[chainId_]; diff --git a/src/settings/SuperRBAC.sol b/src/settings/SuperRBAC.sol index 73f3aecde..f1b21b54c 100644 --- a/src/settings/SuperRBAC.sol +++ b/src/settings/SuperRBAC.sol @@ -1,17 +1,18 @@ -//SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { IBroadcastRegistry } from "src/interfaces/IBroadcastRegistry.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { Error } from "src/libraries/Error.sol"; +import { BroadcastMessage } from "src/types/DataTypes.sol"; import { AccessControlEnumerable } from "openzeppelin-contracts/contracts/access/extensions/AccessControlEnumerable.sol"; -import { IBroadcastRegistry } from "../interfaces/IBroadcastRegistry.sol"; -import { ISuperRegistry } from "../interfaces/ISuperRegistry.sol"; -import { ISuperRBAC } from "../interfaces/ISuperRBAC.sol"; -import { Error } from "../libraries/Error.sol"; -import { BroadcastMessage } from "../types/DataTypes.sol"; /// @title SuperRBAC -/// @author Zeropoint Labs. -/// @dev Contract to manage roles in the entire superform protocol +/// @dev Contract to manage roles in the Superform protocol +/// @author Zeropoint Labs contract SuperRBAC is ISuperRBAC, AccessControlEnumerable { + ////////////////////////////////////////////////////////////// // CONSTANTS // ////////////////////////////////////////////////////////////// @@ -147,11 +148,15 @@ contract SuperRBAC is ISuperRBAC, AccessControlEnumerable { if (superRegistry_ == address(0)) revert Error.ZERO_ADDRESS(); superRegistry = ISuperRegistry(superRegistry_); + + emit SuperRegistrySet(superRegistry_); } /// @inheritdoc ISuperRBAC function setRoleAdmin(bytes32 role_, bytes32 adminRole_) external override onlyRole(PROTOCOL_ADMIN_ROLE) { _setRoleAdmin(role_, adminRole_); + + emit RoleAdminSet(role_, adminRole_); } /// @inheritdoc ISuperRBAC @@ -171,12 +176,15 @@ contract SuperRBAC is ISuperRBAC, AccessControlEnumerable { role_ == PROTOCOL_ADMIN_ROLE || role_ == EMERGENCY_ADMIN_ROLE || role_ == BROADCASTER_ROLE || role_ == WORMHOLE_VAA_RELAYER_ROLE ) revert Error.CANNOT_REVOKE_NON_BROADCASTABLE_ROLES(); - _revokeRole(role_, superRegistry.getAddress(superRegistryAddressId_)); - if (extraData_.length != 0) { - BroadcastMessage memory rolesPayload = BroadcastMessage( - "SUPER_RBAC", SYNC_REVOKE, abi.encode(++xChainPayloadCounter, role_, superRegistryAddressId_) - ); - _broadcast(abi.encode(rolesPayload), extraData_); + if (_revokeRole(role_, superRegistry.getAddress(superRegistryAddressId_))) { + if (extraData_.length != 0) { + BroadcastMessage memory rolesPayload = BroadcastMessage( + "SUPER_RBAC", SYNC_REVOKE, abi.encode(++xChainPayloadCounter, role_, superRegistryAddressId_) + ); + _broadcast(abi.encode(rolesPayload), extraData_); + } + } else { + revert Error.ROLE_NOT_ASSIGNED(); } } @@ -194,7 +202,11 @@ contract SuperRBAC is ISuperRBAC, AccessControlEnumerable { role == PROTOCOL_ADMIN_ROLE || role == EMERGENCY_ADMIN_ROLE || role == BROADCASTER_ROLE || role == WORMHOLE_VAA_RELAYER_ROLE ) - ) _revokeRole(role, superRegistry.getAddress(superRegistryAddressId)); + ) { + if (!_revokeRole(role, superRegistry.getAddress(superRegistryAddressId))) { + revert Error.ROLE_NOT_ASSIGNED(); + } + } } ////////////////////////////////////////////////////////////// @@ -216,10 +228,26 @@ contract SuperRBAC is ISuperRBAC, AccessControlEnumerable { /// @param extraData_ is the amb override information. function _broadcast(bytes memory message_, bytes memory extraData_) internal { (uint8 ambId, bytes memory broadcastParams) = abi.decode(extraData_, (uint8, bytes)); - /// @dev ambIds are validated inside the factory state registry - /// @dev if the broadcastParams are wrong, this will revert in the amb implementation - IBroadcastRegistry(superRegistry.getAddress(keccak256("BROADCAST_REGISTRY"))).broadcastPayload{ - value: msg.value - }(msg.sender, ambId, message_, broadcastParams); + + /// @dev if the broadcastParams are wrong this will revert + (uint256 gasFee, bytes memory extraData) = abi.decode(broadcastParams, (uint256, bytes)); + + if (msg.value < gasFee) { + revert Error.INVALID_BROADCAST_FEE(); + } + + /// @dev ambIds are validated inside the broadcast state registry + IBroadcastRegistry(superRegistry.getAddress(keccak256("BROADCAST_REGISTRY"))).broadcastPayload{ value: gasFee }( + msg.sender, ambId, gasFee, message_, extraData + ); + + if (msg.value > gasFee) { + /// @dev forwards the rest to msg.sender + (bool success,) = payable(msg.sender).call{ value: msg.value - gasFee }(""); + + if (!success) { + revert Error.FAILED_TO_SEND_NATIVE(); + } + } } } diff --git a/src/settings/SuperRegistry.sol b/src/settings/SuperRegistry.sol index 3d770ed23..55453cff1 100644 --- a/src/settings/SuperRegistry.sol +++ b/src/settings/SuperRegistry.sol @@ -1,63 +1,80 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.23; -import { ISuperRBAC } from "../interfaces/ISuperRBAC.sol"; -import { ISuperRegistry } from "../interfaces/ISuperRegistry.sol"; -import { QuorumManager } from "../crosschain-data/utils/QuorumManager.sol"; -import { Error } from "../libraries/Error.sol"; +import { QuorumManager } from "src/crosschain-data/utils/QuorumManager.sol"; +import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { Error } from "src/libraries/Error.sol"; /// @title SuperRegistry -/// @author Zeropoint Labs. -/// @dev Keeps information on all addresses used in the Superforms ecosystem. +/// @dev Keeps information on all addresses used in the Superform ecosystem +/// @author Zeropoint Labs contract SuperRegistry is ISuperRegistry, QuorumManager { ////////////////////////////////////////////////////////////// // CONSTANTS // ////////////////////////////////////////////////////////////// - uint256 private constant MIN_DELAY = 1 hours; + uint256 private constant MIN_DELAY = 15 minutes; uint256 private constant MAX_DELAY = 24 hours; uint64 public immutable CHAIN_ID; /// @dev core protocol - identifiers /// @notice should not be allowed to be changed bytes32 public constant override SUPERFORM_ROUTER = keccak256("SUPERFORM_ROUTER"); + /// @dev can be used to set a new factory that has form ids paused /// @notice should not be allowed to be changed bytes32 public constant override SUPERFORM_FACTORY = keccak256("SUPERFORM_FACTORY"); + /// @dev not accessed in protocol /// @dev could be allowed to be changed bytes32 public constant override SUPER_TRANSMUTER = keccak256("SUPER_TRANSMUTER"); + /// @dev can be used to set a new paymaster to forward payments to /// @dev could be allowed to be changed bytes32 public constant override PAYMASTER = keccak256("PAYMASTER"); + /// @dev accessed in some areas of the protocol to calculate AMB fees. Already has a function to alter the /// configuration /// @dev could be allowed to be changed bytes32 public constant override PAYMENT_HELPER = keccak256("PAYMENT_HELPER"); + /// @dev accessed in many areas of the protocol. has direct access to superforms /// @notice should not be allowed to be changed bytes32 public constant override CORE_STATE_REGISTRY = keccak256("CORE_STATE_REGISTRY"); + /// @dev accessed in many areas of the protocol. has direct access to timelock form /// @notice should not be allowed to be changed bytes32 public constant override TIMELOCK_STATE_REGISTRY = keccak256("TIMELOCK_STATE_REGISTRY"); + /// @dev used to sync messages for pausing superforms or deploying transmuters /// @notice should not be allowed to be changed bytes32 public constant override BROADCAST_REGISTRY = keccak256("BROADCAST_REGISTRY"); + /// @dev not accessed in protocol /// @notice should not be allowed to be changed bytes32 public constant override SUPER_POSITIONS = keccak256("SUPER_POSITIONS"); + /// @dev accessed in many areas of the protocol /// @notice should not be allowed to be changed bytes32 public constant override SUPER_RBAC = keccak256("SUPER_RBAC"); + /// @dev not accessed in protocol /// @dev could be allowed to be changed bytes32 public constant override PAYLOAD_HELPER = keccak256("PAYLOAD_HELPER"); + /// @dev accessed in CSR and validators. can be used to alter behaviour of update deposit payloads /// @notice should not be allowed to be changed bytes32 public constant override DST_SWAPPER = keccak256("DST_SWAPPER"); + /// @dev accessed in base form to send payloads to emergency queue /// @notice should not be allowed to be changed bytes32 public constant override EMERGENCY_QUEUE = keccak256("EMERGENCY_QUEUE"); + + /// @dev receiver of bridge refunds and airdropped tokens + /// @notice should not be allowed to be changed + bytes32 public constant override SUPERFORM_RECEIVER = keccak256("SUPERFORM_RECEIVER"); + /// @dev default keepers - identifiers /// @dev could be allowed to be changed bytes32 public constant override PAYMENT_ADMIN = keccak256("PAYMENT_ADMIN"); @@ -87,7 +104,7 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { mapping(uint8 ambId => address ambAddresses) public ambAddresses; mapping(uint8 ambId => bool isBroadcastAMB) public isBroadcastAMB; - mapping(uint64 chainId => uint256 vaultLimitPerTx) public vaultLimitPerTx; + mapping(uint64 chainId => uint256 vaultLimitPerDestination) public vaultLimitPerDestination; mapping(uint8 registryId => address registryAddress) public registryAddresses; /// @dev is the reverse mapping of registryAddresses @@ -99,6 +116,13 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { // MODIFIERS // ////////////////////////////////////////////////////////////// + modifier onlyEmergencyAdmin() { + if (!ISuperRBAC(registry[SUPER_RBAC][CHAIN_ID]).hasEmergencyAdminRole(msg.sender)) { + revert Error.NOT_EMERGENCY_ADMIN(); + } + _; + } + modifier onlyProtocolAdmin() { if (!ISuperRBAC(registry[SUPER_RBAC][CHAIN_ID]).hasProtocolAdminRole(msg.sender)) { revert Error.NOT_PROTOCOL_ADMIN(); @@ -111,6 +135,10 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { ////////////////////////////////////////////////////////////// constructor(address superRBAC_) { + if (superRBAC_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + if (block.chainid > type(uint64).max) { revert Error.BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); } @@ -125,11 +153,13 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { // EXTERNAL VIEW FUNCTIONS // ////////////////////////////////////////////////////////////// + /// @inheritdoc ISuperRegistry function getAddress(bytes32 id_) external view override returns (address addr) { addr = registry[id_][CHAIN_ID]; if (addr == address(0)) revert Error.ZERO_ADDRESS(); } + /// @inheritdoc ISuperRegistry function getAddressByChainId(bytes32 id_, uint64 chainId_) external view override returns (address addr) { addr = registry[id_][chainId_]; if (addr == address(0)) revert Error.ZERO_ADDRESS(); @@ -167,11 +197,17 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { /// @inheritdoc ISuperRegistry function getStateRegistryId(address registryAddress_) external view override returns (uint8 registryId_) { registryId_ = stateRegistryIds[registryAddress_]; + if (registryId_ == 0) revert Error.INVALID_REGISTRY_ID(); } /// @inheritdoc ISuperRegistry - function getVaultLimitPerTx(uint64 chainId_) external view override returns (uint256 vaultLimitPerTx_) { - vaultLimitPerTx_ = vaultLimitPerTx[chainId_]; + function getVaultLimitPerDestination(uint64 chainId_) + external + view + override + returns (uint256 vaultLimitPerDestination_) + { + vaultLimitPerDestination_ = vaultLimitPerDestination[chainId_]; } /// @inheritdoc ISuperRegistry @@ -197,6 +233,7 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { return false; } + /// @inheritdoc ISuperRegistry function PERMIT2() external view override returns (address) { if (permit2Address == address(0)) revert Error.ZERO_ADDRESS(); return permit2Address; @@ -206,6 +243,16 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// + /// @inheritdoc ISuperRegistry + function setVaultLimitPerDestination(uint64 chainId_, uint256 vaultLimit_) external override onlyEmergencyAdmin { + if (vaultLimit_ == 0) { + revert Error.ZERO_INPUT_VALUE(); + } + + vaultLimitPerDestination[chainId_] = vaultLimit_; + emit SetVaultLimitPerDestination(chainId_, vaultLimit_); + } + /// @inheritdoc ISuperRegistry function setDelay(uint256 delay_) external override onlyProtocolAdmin { if (delay_ < MIN_DELAY || delay_ > MAX_DELAY) { @@ -228,16 +275,6 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { emit SetPermit2(permit2_); } - /// @inheritdoc ISuperRegistry - function setVaultLimitPerTx(uint64 chainId_, uint256 vaultLimit_) external override onlyProtocolAdmin { - if (vaultLimit_ == 0) { - revert Error.ZERO_INPUT_VALUE(); - } - - vaultLimitPerTx[chainId_] = vaultLimit_; - emit SetVaultLimitPerTx(chainId_, vaultLimit_); - } - /// @inheritdoc ISuperRegistry function setAddress(bytes32 id_, address newAddress_, uint64 chainId_) external override onlyProtocolAdmin { address oldAddress = registry[id_][chainId_]; @@ -276,6 +313,7 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { address bridgeAddress = bridgeAddress_[i]; address bridgeValidatorT = bridgeValidator_[i]; if (bridgeAddress == address(0)) revert Error.ZERO_ADDRESS(); + if (bridgeId == 0) revert Error.ZERO_INPUT_VALUE(); if (bridgeValidatorT == address(0)) revert Error.ZERO_ADDRESS(); if (bridgeAddresses[bridgeId] != address(0)) revert Error.DISABLED(); @@ -306,7 +344,8 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { bool broadcastAMB = isBroadcastAMB_[i]; if (ambAddress == address(0)) revert Error.ZERO_ADDRESS(); - if (ambAddresses[ambId] != address(0) || ambIds[ambAddress] != 0) revert Error.DISABLED(); + if (ambId == 0) revert Error.ZERO_INPUT_VALUE(); + if (ambAddresses[ambId] != address(0)) revert Error.DISABLED(); ambAddresses[ambId] = ambAddress; ambIds[ambAddress] = ambId; @@ -331,7 +370,8 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { address registryAddress = registryAddress_[i]; uint8 registryId = registryId_[i]; if (registryAddress == address(0)) revert Error.ZERO_ADDRESS(); - if (registryAddresses[registryId] != address(0) || stateRegistryIds[registryAddress] != 0) { + if (registryId == 0) revert Error.ZERO_INPUT_VALUE(); + if (registryAddresses[registryId] != address(0)) { revert Error.DISABLED(); } @@ -343,6 +383,10 @@ contract SuperRegistry is ISuperRegistry, QuorumManager { /// @inheritdoc QuorumManager function setRequiredMessagingQuorum(uint64 srcChainId_, uint256 quorum_) external override onlyProtocolAdmin { + if (srcChainId_ == 0) { + revert Error.INVALID_CHAIN_ID(); + } + requiredQuorum[srcChainId_] = quorum_; emit QuorumSet(srcChainId_, quorum_); diff --git a/src/types/DataTypes.sol b/src/types/DataTypes.sol index a17e749fb..f7114f053 100644 --- a/src/types/DataTypes.sol +++ b/src/types/DataTypes.sol @@ -27,7 +27,8 @@ enum PayloadState { struct LiqRequest { /// @dev generated data bytes txData; - /// @dev input token. Relevant for withdraws especially to know when to update txData + /// @dev input token for deposits, desired output token on target liqDstChainId for withdraws. Must be set for + /// txData to be updated on destination for withdraws address token; /// @dev intermediary token on destination. Relevant for xChain deposits where a destination swap is needed for /// validation purposes @@ -45,13 +46,17 @@ struct LiqRequest { struct MultiVaultSFData { // superformids must have same destination. Can have different underlyings uint256[] superformIds; - uint256[] amounts; + uint256[] amounts; // on deposits, amount of token to deposit on dst, on withdrawals, superpositions to burn + uint256[] outputAmounts; // on deposits, amount of shares to receive, on withdrawals, amount of assets to receive uint256[] maxSlippages; LiqRequest[] liqRequests; // if length = 1; amount = sum(amounts) | else amounts must match the amounts being sent bytes permit2data; bool[] hasDstSwaps; bool[] retain4626s; // if true, we don't mint SuperPositions, and send the 4626 back to the user instead address receiverAddress; + /// this address must always be an EOA otherwise funds may be lost + address receiverAddressSP; + /// this address can be a EOA or a contract that implements onERC1155Receiver. must always be set for deposits bytes extraFormData; // extraFormData } @@ -60,12 +65,16 @@ struct SingleVaultSFData { // superformids must have same destination. Can have different underlyings uint256 superformId; uint256 amount; + uint256 outputAmount; // on deposits, amount of shares to receive, on withdrawals, amount of assets to receive uint256 maxSlippage; LiqRequest liqRequest; // if length = 1; amount = sum(amounts)| else amounts must match the amounts being sent bytes permit2data; bool hasDstSwap; bool retain4626; // if true, we don't mint SuperPositions, and send the 4626 back to the user instead address receiverAddress; + /// this address must always be an EOA otherwise funds may be lost + address receiverAddressSP; + /// this address can be a EOA or a contract that implements onERC1155Receiver. must always be set for deposits bytes extraFormData; // extraFormData } @@ -108,10 +117,12 @@ struct SingleDirectMultiVaultStateReq { } /// @dev struct for SuperRouter with re-arranged data for the message (contains the payloadId) +/// @dev realize that receiverAddressSP is not passed, only needed on source chain to mint struct InitMultiVaultData { uint256 payloadId; uint256[] superformIds; uint256[] amounts; + uint256[] outputAmounts; uint256[] maxSlippages; LiqRequest[] liqData; bool[] hasDstSwaps; @@ -125,6 +136,7 @@ struct InitSingleVaultData { uint256 payloadId; uint256 superformId; uint256 amount; + uint256 outputAmount; uint256 maxSlippage; LiqRequest liqData; bool hasDstSwap; @@ -135,8 +147,7 @@ struct InitSingleVaultData { /// @dev struct for Emergency Queue struct QueuedWithdrawal { - address srcSender; - address refundAddress; + address receiverAddress; uint256 superformId; uint256 amount; uint256 srcPayloadId; @@ -153,7 +164,6 @@ enum TimelockStatus { /// @dev holds information about the timelock payload struct TimelockPayload { uint8 isXChain; - address srcSender; uint64 srcChainId; uint256 lockedTill; InitSingleVaultData data; @@ -188,12 +198,6 @@ struct ReturnSingleData { uint256 superformId; uint256 amount; } -/// @dev struct that contains the data on the fees to pay - -struct SingleDstAMBParams { - uint256 gasToPay; - bytes encodedAMBExtraData; -} /// @dev struct that contains the data on the fees to pay to the AMBs struct AMBExtraData { @@ -206,9 +210,3 @@ struct BroadCastAMBExtraData { uint256[] gasPerDst; bytes[] extraDataPerDst; } - -/// @dev acknowledgement extra data (contains gas information from dst to src callbacks) -struct AckAMBData { - uint8[] ambIds; - bytes extraData; -} diff --git a/src/vendor/hyperlane/IMessageRecipient.sol b/src/vendor/hyperlane/IMessageRecipient.sol index 339d5dc0c..4e8784e96 100644 --- a/src/vendor/hyperlane/IMessageRecipient.sol +++ b/src/vendor/hyperlane/IMessageRecipient.sol @@ -7,5 +7,5 @@ interface IMessageRecipient { /// @param _origin Domain ID of the chain from which the message came /// @param _sender Address of the message sender on the origin chain as bytes32 /// @param _message Raw bytes content of message body - function handle(uint32 _origin, bytes32 _sender, bytes calldata _message) external; + function handle(uint32 _origin, bytes32 _sender, bytes calldata _message) external payable; } diff --git a/src/vendor/kycDAO/IKycDAONTNFT.sol b/src/vendor/kycDAO/IKycDAONTNFT.sol new file mode 100644 index 000000000..0f638068d --- /dev/null +++ b/src/vendor/kycDAO/IKycDAONTNFT.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @title A standard interface for checking and updating the expiry and validity of KYC Non-transferable NFTs + */ +interface IKycdaoNTNFT { + /// SOLE FUNCTION USED BY IMPLEMENTERS + + /// @dev Check whether a given address has ANY token which is valid, + /// i.e. is verified and has an expiry in the future + /// @param _addr Address to check for tokens + /// @return valid Whether the address has a valid token + function hasValidToken(address _addr) external view returns (bool valid); + + /// MINTING + + /// @dev Authorize the minting of a new token + /// @param _auth_code The auth code used to authorize the mint + /// @param _dst Address to mint the token to + /// @param _metadata_cid The metadata CID for the token + /// @param _expiry The time, in secs since epoch, at which to set the token's expiry on mint + /// @param _seconds_to_pay The number of seconds of subscription time that need to be paid for when the token is + /// minted + /// @param _verification_tier The verification tier of the token + function authorizeMintWithCode( + uint32 _auth_code, + address _dst, + string calldata _metadata_cid, + uint256 _expiry, + uint32 _seconds_to_pay, + string calldata _verification_tier + ) + external; + + /// @dev Mint the token by using an authorization code from an authorized account + /// @param _auth_code The auth code used to authorize the mint + function mintWithCode(uint32 _auth_code) external payable; + + /// @dev Returns the amount in NATIVE (wei) which is expected for a given mint which uses an auth code + /// @param _auth_code The auth code used to authorize the mint + /// @param _dst Address to mint the token to + function getRequiredMintCostForCode(uint32 _auth_code, address _dst) external view returns (uint256); + + /// @dev Returns the cost for subscription per year in USD, to SUBSCRIPTION_COST_DECIMALS decimal places + function getSubscriptionCostPerYearUSD() external view returns (uint256); + + /// (TO BE IMPLEMENTED) + //TODO: Will look at minting with signatures in a future release + /// @dev Mint the token using a signature to verify authorization + /// @param _auth_code The auth code used to authorize the mint + /// @param _metadata_cid The metadata CID for the token + /// @param _expiry The time, in secs since epoch, at which to set the token's expiry on mint + /// @param _seconds_to_pay The number of seconds of subscription time that need to be paid for when the token is + /// minted + /// @param _verification_tier The verification tier of the token + /// @param _signature The signature by the minting authority for this mint + function mintWithSignature( + uint32 _auth_code, + string memory _metadata_cid, + uint256 _expiry, + uint32 _seconds_to_pay, + string calldata _verification_tier, + bytes calldata _signature + ) + external + payable; + + /// @dev Returns the amount in NATIVE (wei) which is expected for a given amount of subscription time in seconds + /// @param _seconds The number of seconds of subscription time to calculate the cost for + function getRequiredMintCostForSeconds(uint32 _seconds) external view returns (uint256); + + /// CHECK TOKEN STATUS + + /// @dev Get the current expiry of a specific token in secs since epoch + /// @param _tokenId ID of the token to query + /// @return expiry The expiry of the given token in secs since epoch + function tokenExpiry(uint256 _tokenId) external view returns (uint256 expiry); + + /// @dev Get the verification tier of a specific token + /// @param _tokenId ID of the token to query + /// @return tier The tier of the given token + function tokenTier(uint256 _tokenId) external view returns (string memory tier); + + /// UPDATE STATUS + + /// @dev Set whether a token is verified or not + /// @param _tokenId ID of the token + /// @param _verified A bool indicating whether this token is verified + function setVerifiedToken(uint256 _tokenId, bool _verified) external; + + /// @dev Update the given token to a new expiry + /// @param _tokenId ID of the token whose expiry should be updated + /// @param _expiry New expiry date for the token in secs since epoch + function updateExpiry(uint256 _tokenId, uint256 _expiry) external; +} diff --git a/src/vendor/lifi/AmarokFacet.sol b/src/vendor/lifi/AmarokFacet.sol new file mode 100644 index 000000000..192a00afd --- /dev/null +++ b/src/vendor/lifi/AmarokFacet.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import { ILiFi } from "./ILiFi.sol"; +import { LibSwap } from "./LibSwap.sol"; + +/// @title Amarok Facet +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for bridging through Connext Amarok +/// @notice taken from LiFi contracts https://github.com/lifinance/contracts and stripped down to needs +/// @custom:version 2.0.0 +contract AmarokFacet { + /// @param callData The data to execute on the receiving chain. If no crosschain call is needed, then leave empty. + /// @param callTo The address of the contract on dest chain that will receive bridged funds and execute data + /// @param relayerFee The amount of relayer fee the tx called xcall with + /// @param slippageTol Max bps of original due to slippage (i.e. would be 9995 to tolerate .05% slippage) + /// @param delegate Destination delegate address + /// @param destChainDomainId The Amarok-specific domainId of the destination chain + /// @param payFeeWithSendingAsset Whether to pay the relayer fee with the sending asset or not + struct AmarokData { + bytes callData; + address callTo; + uint256 relayerFee; + uint256 slippageTol; + address delegate; + uint32 destChainDomainId; + bool payFeeWithSendingAsset; + } + + /// External Methods /// + + /// @notice Bridges tokens via Amarok + /// @param _bridgeData Data containing core information for bridging + /// @param _amarokData Data specific to bridge + function startBridgeTokensViaAmarok( + ILiFi.BridgeData calldata _bridgeData, + AmarokData calldata _amarokData + ) + external + payable + { } + + /// @notice Performs a swap before bridging via Amarok + /// @param _bridgeData The core information needed for bridging + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _amarokData Data specific to Amarok + function swapAndStartBridgeTokensViaAmarok( + ILiFi.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + AmarokData calldata _amarokData + ) + external + payable + { } +} diff --git a/src/vendor/lifi/CBridgeFacetPacked.sol b/src/vendor/lifi/CBridgeFacetPacked.sol new file mode 100644 index 000000000..0a143b648 --- /dev/null +++ b/src/vendor/lifi/CBridgeFacetPacked.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/// @title CBridge Facet Packed +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for bridging through CBridge +/// @notice taken from LiFi contracts https://github.com/lifinance/contracts and stripped down to needs +/// @custom:version 1.0.3 +contract CBridgeFacetPacked { + /// @notice Bridges Native tokens via cBridge (packed) + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaCBridgeNativePacked() external payable { } + + /// @notice Bridges native tokens via cBridge + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param nonce A number input to guarantee uniqueness of transferId. + /// @param maxSlippage Destination swap minimal accepted amount + function startBridgeTokensViaCBridgeNativeMin( + bytes32 transactionId, + address receiver, + uint64 destinationChainId, + uint64 nonce, + uint32 maxSlippage + ) + external + payable + { } + + /// @notice Bridges ERC20 tokens via cBridge + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaCBridgeERC20Packed() external { } + + /// @notice Bridges ERC20 tokens via cBridge + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param sendingAssetId Address of the source asset to bridge + /// @param amount Amount of the source asset to bridge + /// @param nonce A number input to guarantee uniqueness of transferId + /// @param maxSlippage Destination swap minimal accepted amount + function startBridgeTokensViaCBridgeERC20Min( + bytes32 transactionId, + address receiver, + uint64 destinationChainId, + address sendingAssetId, + uint256 amount, + uint64 nonce, + uint32 maxSlippage + ) + external + { } +} diff --git a/src/vendor/lifi/CelerIMFacetBase.sol b/src/vendor/lifi/CelerIMFacetBase.sol new file mode 100644 index 000000000..c807f6955 --- /dev/null +++ b/src/vendor/lifi/CelerIMFacetBase.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import { ILiFi } from "./ILiFi.sol"; +import { LibSwap } from "./LibSwap.sol"; +import { MsgDataTypes } from "./celer-network/MsgDataTypes.sol"; + +interface CelerIM { + /// @param maxSlippage The max slippage accepted, given as percentage in point (pip). + /// @param nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice. + /// @param callTo The address of the contract to be called at destination. + /// @param callData The encoded calldata with below data + /// bytes32 transactionId, + /// LibSwap.SwapData[] memory swapData, + /// address receiver, + /// address refundAddress + /// @param messageBusFee The fee to be paid to CBridge message bus for relaying the message + /// @param bridgeType Defines the bridge operation type (must be one of the values of CBridge library + /// MsgDataTypes.BridgeSendType) + struct CelerIMData { + uint32 maxSlippage; + uint64 nonce; + bytes callTo; + bytes callData; + uint256 messageBusFee; + MsgDataTypes.BridgeSendType bridgeType; + } +} + +/// @title CelerIM Facet Base +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for bridging tokens and data through CBridge +/// @notice Used to differentiate between contract instances for mutable and immutable diamond as these cannot be shared +/// @notice taken from LiFi contracts https://github.com/lifinance/contracts and stripped down to needs +/// @custom:version 2.0.0 +abstract contract CelerIMFacetBase { + /// External Methods /// + + /// @notice Bridges tokens via CBridge + /// @param _bridgeData The core information needed for bridging + /// @param _celerIMData Data specific to CelerIM + function startBridgeTokensViaCelerIM( + ILiFi.BridgeData memory _bridgeData, + CelerIM.CelerIMData calldata _celerIMData + ) + external + payable + { } + + /// @notice Performs a swap before bridging via CBridge + /// @param _bridgeData The core information needed for bridging + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _celerIMData Data specific to CelerIM + function swapAndStartBridgeTokensViaCelerIM( + ILiFi.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + CelerIM.CelerIMData calldata _celerIMData + ) + external + payable + { } +} diff --git a/src/vendor/lifi/GenericSwapFacet.sol b/src/vendor/lifi/GenericSwapFacet.sol new file mode 100644 index 000000000..726ed42ac --- /dev/null +++ b/src/vendor/lifi/GenericSwapFacet.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import { LibSwap } from "./LibSwap.sol"; + +/// @title Generic Swap Facet +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for swapping through ANY APPROVED DEX +/// @dev Uses calldata to execute APPROVED arbitrary methods on DEXs +/// @notice taken from LiFi contracts https://github.com/lifinance/contracts and stripped down to needs +/// @custom:version 1.0.0 +contract GenericSwapFacet { + /// External Methods /// + + /// @notice Performs multiple swaps in one transaction + /// @param _transactionId the transaction id associated with the operation + /// @param _integrator the name of the integrator + /// @param _referrer the address of the referrer + /// @param _receiver the address to receive the swapped tokens into (also excess tokens) + /// @param _minAmount the minimum amount of the final asset to receive + /// @param _swapData an object containing swap related data to perform swaps before bridging + function swapTokensGeneric( + bytes32 _transactionId, + string calldata _integrator, + string calldata _referrer, + address payable _receiver, + uint256 _minAmount, + LibSwap.SwapData[] calldata _swapData + ) + external + payable + { } +} diff --git a/src/vendor/lifi/HopFacetPacked.sol b/src/vendor/lifi/HopFacetPacked.sol new file mode 100644 index 000000000..d9ae34aed --- /dev/null +++ b/src/vendor/lifi/HopFacetPacked.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/// @title Hop Facet (Optimized for Rollups) +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for bridging through Hop +/// @notice taken from LiFi contracts https://github.com/lifinance/contracts and stripped down to needs +/// @custom:version 1.0.6 +contract HopFacetPacked { + /// @notice Bridges Native tokens via Hop Protocol from L2 + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaHopL2NativePacked() external payable { } + + /// @notice Bridges Native tokens via Hop Protocol from L2 + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param bonderFee Fees payed to hop bonder + /// @param amountOutMin Source swap minimal accepted amount + /// @param destinationAmountOutMin Destination swap minimal accepted amount + /// @param destinationDeadline Destination swap maximal time + /// @param hopBridge Address of the Hop L2_AmmWrapper + function startBridgeTokensViaHopL2NativeMin( + bytes8 transactionId, + address receiver, + uint256 destinationChainId, + uint256 bonderFee, + uint256 amountOutMin, + uint256 destinationAmountOutMin, + uint256 destinationDeadline, + address hopBridge + ) + external + payable + { } + + /// @notice Bridges ERC20 tokens via Hop Protocol from L2 + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaHopL2ERC20Packed() external { } + + /// @notice Bridges ERC20 tokens via Hop Protocol from L2 + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param sendingAssetId Address of the source asset to bridge + /// @param minAmount Amount of the source asset to bridge + /// @param bonderFee Fees payed to hop bonder + /// @param amountOutMin Source swap minimal accepted amount + /// @param destinationAmountOutMin Destination swap minimal accepted amount + /// @param destinationDeadline Destination swap maximal time + /// @param hopBridge Address of the Hop L2_AmmWrapper + function startBridgeTokensViaHopL2ERC20Min( + bytes8 transactionId, + address receiver, + uint256 destinationChainId, + address sendingAssetId, + uint256 minAmount, + uint256 bonderFee, + uint256 amountOutMin, + uint256 destinationAmountOutMin, + uint256 destinationDeadline, + address hopBridge + ) + external + { } + + /// @notice Bridges Native tokens via Hop Protocol from L1 + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaHopL1NativePacked() external payable { } + + /// @notice Bridges Native tokens via Hop Protocol from L1 + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param destinationAmountOutMin Destination swap minimal accepted amount + /// @param relayer needed for gas spikes + /// @param relayerFee needed for gas spikes + /// @param hopBridge Address of the Hop Bridge + function startBridgeTokensViaHopL1NativeMin( + bytes8 transactionId, + address receiver, + uint256 destinationChainId, + uint256 destinationAmountOutMin, + address relayer, + uint256 relayerFee, + address hopBridge + ) + external + payable + { } + + /// @notice Bridges Native tokens via Hop Protocol from L1 + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaHopL1ERC20Packed() external payable { } + + /// @notice Bridges ERC20 tokens via Hop Protocol from L1 + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param sendingAssetId Address of the source asset to bridge + /// @param minAmount Amount of the source asset to bridge + /// @param destinationAmountOutMin Destination swap minimal accepted amount + /// @param relayer needed for gas spikes + /// @param relayerFee needed for gas spikes + /// @param hopBridge Address of the Hop Bridge + function startBridgeTokensViaHopL1ERC20Min( + bytes8 transactionId, + address receiver, + uint256 destinationChainId, + address sendingAssetId, + uint256 minAmount, + uint256 destinationAmountOutMin, + address relayer, + uint256 relayerFee, + address hopBridge + ) + external + { } +} diff --git a/src/vendor/lifi/LiFiTxDataExtractor.sol b/src/vendor/lifi/LiFiTxDataExtractor.sol index 383d230ee..6d632d56e 100644 --- a/src/vendor/lifi/LiFiTxDataExtractor.sol +++ b/src/vendor/lifi/LiFiTxDataExtractor.sol @@ -4,6 +4,11 @@ pragma solidity ^0.8.23; import { ILiFi } from "src/vendor/lifi/ILiFi.sol"; import { LibSwap } from "src/vendor/lifi/LibSwap.sol"; import { StandardizedCallFacet } from "./StandardizedCallFacet.sol"; +import { AmarokFacet } from "./AmarokFacet.sol"; +import { CBridgeFacetPacked } from "./CBridgeFacetPacked.sol"; +import { HopFacetPacked } from "./HopFacetPacked.sol"; +import { CelerIMFacetBase, CelerIM } from "./CelerIMFacetBase.sol"; +import { StargateFacet } from "./StargateFacet.sol"; /// @title LiFiTxDataExtractor /// @author LI.FI (https://li.fi) @@ -11,24 +16,151 @@ import { StandardizedCallFacet } from "./StandardizedCallFacet.sol"; /// @notice upgraded to solidity 0.8.23 and adapted from CalldataVerificationFacet and LibBytes without any changes to /// used functions (just stripped down functionality and renamed contract name) /// @notice taken from LiFi contracts https://github.com/lifinance/contracts -/// @custom:version 1.1.0 - +/// @custom:version 2.2.0 contract LiFiTxDataExtractor { error SliceOverflow(); error SliceOutOfBounds(); - /// @notice Extracts the bridge data from the calldata + /// @dev this function blacklists certain packed and min selectors. + /// @notice this is a patch to prevent a user to bypass our txData validation checks + /// @notice the offered solution here is not scallable and should be replaced by a better solution with a lifi + /// maintained list + function _validateSelector(bytes4 selector) internal pure returns (bool valid) { + if (selector == CBridgeFacetPacked.startBridgeTokensViaCBridgeNativePacked.selector) { + return false; + } + if (selector == CBridgeFacetPacked.startBridgeTokensViaCBridgeNativeMin.selector) { + return false; + } + if (selector == CBridgeFacetPacked.startBridgeTokensViaCBridgeERC20Packed.selector) { + return false; + } + if (selector == CBridgeFacetPacked.startBridgeTokensViaCBridgeERC20Min.selector) { + return false; + } + if (selector == HopFacetPacked.startBridgeTokensViaHopL2NativePacked.selector) { + return false; + } + if (selector == HopFacetPacked.startBridgeTokensViaHopL2NativeMin.selector) { + return false; + } + if (selector == HopFacetPacked.startBridgeTokensViaHopL2ERC20Packed.selector) { + return false; + } + + if (selector == HopFacetPacked.startBridgeTokensViaHopL2ERC20Min.selector) { + return false; + } + + if (selector == HopFacetPacked.startBridgeTokensViaHopL1NativePacked.selector) { + return false; + } + + if (selector == HopFacetPacked.startBridgeTokensViaHopL1NativeMin.selector) { + return false; + } + + if (selector == HopFacetPacked.startBridgeTokensViaHopL1ERC20Packed.selector) { + return false; + } + + if (selector == HopFacetPacked.startBridgeTokensViaHopL1ERC20Min.selector) { + return false; + } + /// @dev prevent recursive calls + if (selector == StandardizedCallFacet.standardizedCall.selector) { + return false; + } + return true; + } + + function _extractSelector(bytes calldata data) internal pure returns (bytes4 selector) { + selector = bytes4(data[:4]); + if (selector == StandardizedCallFacet.standardizedCall.selector) selector = bytes4(data[4:8]); + } + + /// @notice Extracts the bridge data from the calldata. Extracts receiver correctly pending certain facet features /// @param data The calldata to extract the bridge data from /// @return bridgeData The bridge data extracted from the calldata - function _extractBridgeData(bytes calldata data) internal pure returns (ILiFi.BridgeData memory bridgeData) { + function _extractBridgeData(bytes calldata data) + internal + pure + returns (ILiFi.BridgeData memory bridgeData, address receiver) + { + bytes memory callData = data; + if (bytes4(data[:4]) == StandardizedCallFacet.standardizedCall.selector) { // StandardizedCall - bytes memory unwrappedData = abi.decode(data[4:], (bytes)); - bridgeData = abi.decode(_slice(unwrappedData, 4, unwrappedData.length - 4), (ILiFi.BridgeData)); - return bridgeData; + callData = abi.decode(data[4:], (bytes)); + } + + bytes4 selector = abi.decode(callData, (bytes4)); + + // Case: Amarok + if (selector == AmarokFacet.startBridgeTokensViaAmarok.selector) { + AmarokFacet.AmarokData memory amarokData; + (bridgeData, amarokData) = + abi.decode(_slice(callData, 4, callData.length - 4), (ILiFi.BridgeData, AmarokFacet.AmarokData)); + receiver = amarokData.callTo; + + return (bridgeData, receiver); } + if (selector == AmarokFacet.swapAndStartBridgeTokensViaAmarok.selector) { + AmarokFacet.AmarokData memory amarokData; + + (bridgeData,, amarokData) = abi.decode( + _slice(callData, 4, callData.length - 4), (ILiFi.BridgeData, LibSwap.SwapData[], AmarokFacet.AmarokData) + ); + receiver = amarokData.callTo; + + return (bridgeData, receiver); + } + + // Case: Stargate + if (selector == StargateFacet.startBridgeTokensViaStargate.selector) { + StargateFacet.StargateData memory stargateData; + (bridgeData, stargateData) = + abi.decode(_slice(callData, 4, callData.length - 4), (ILiFi.BridgeData, StargateFacet.StargateData)); + + receiver = abi.decode(stargateData.callTo, (address)); + + return (bridgeData, receiver); + } + if (selector == StargateFacet.swapAndStartBridgeTokensViaStargate.selector) { + StargateFacet.StargateData memory stargateData; + (bridgeData,, stargateData) = abi.decode( + _slice(callData, 4, callData.length - 4), + (ILiFi.BridgeData, LibSwap.SwapData[], StargateFacet.StargateData) + ); + receiver = abi.decode(stargateData.callTo, (address)); + + return (bridgeData, receiver); + } + + // Case: Celer + if (selector == CelerIMFacetBase.startBridgeTokensViaCelerIM.selector) { + CelerIM.CelerIMData memory celerIMData; + (bridgeData, celerIMData) = + abi.decode(_slice(callData, 4, callData.length - 4), (ILiFi.BridgeData, CelerIM.CelerIMData)); + + receiver = abi.decode(celerIMData.callTo, (address)); + + return (bridgeData, receiver); + } + if (selector == CelerIMFacetBase.swapAndStartBridgeTokensViaCelerIM.selector) { + CelerIM.CelerIMData memory celerIMData; + + (bridgeData,, celerIMData) = abi.decode( + _slice(callData, 4, callData.length - 4), (ILiFi.BridgeData, LibSwap.SwapData[], CelerIM.CelerIMData) + ); + receiver = abi.decode(celerIMData.callTo, (address)); + + return (bridgeData, receiver); + } + // normal call bridgeData = abi.decode(data[4:], (ILiFi.BridgeData)); + receiver = bridgeData.receiver; } /// @notice Extracts the swap data from the calldata diff --git a/src/vendor/lifi/StandardizedCallFacet.sol b/src/vendor/lifi/StandardizedCallFacet.sol index b1c8e904e..5b037079f 100644 --- a/src/vendor/lifi/StandardizedCallFacet.sol +++ b/src/vendor/lifi/StandardizedCallFacet.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; /// @title StandardizedCallFacet /// @author LI.FI (https://li.fi) /// @notice taken from LiFi contracts https://github.com/lifinance/contracts -/// @custom:version 1.1.0 +/// @custom:version 2.2.0 contract StandardizedCallFacet { /// External Methods /// diff --git a/src/vendor/lifi/StargateFacet.sol b/src/vendor/lifi/StargateFacet.sol new file mode 100644 index 000000000..6c262d4cd --- /dev/null +++ b/src/vendor/lifi/StargateFacet.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import { ILiFi } from "./ILiFi.sol"; +import { LibSwap } from "./LibSwap.sol"; + +/// @title Stargate Facet +/// @author Li.Finance (https://li.finance) +/// @notice Provides functionality for bridging through Stargate +/// @notice taken from LiFi contracts https://github.com/lifinance/contracts and stripped down to needs +/// @custom:version 2.2.0 +contract StargateFacet { + /// @param srcPoolId Source pool id. + /// @param dstPoolId Dest pool id. + /// @param minAmountLD The min qty you would accept on the destination. + /// @param dstGasForCall Additional gas fee for extral call on the destination. + /// @param lzFee Estimated message fee. + /// @param refundAddress Refund adddress. Extra gas (if any) is returned to this address + /// @param callTo The address to send the tokens to on the destination. + /// @param callData Additional payload. + struct StargateData { + uint256 srcPoolId; + uint256 dstPoolId; + uint256 minAmountLD; + uint256 dstGasForCall; + uint256 lzFee; + address payable refundAddress; + bytes callTo; + bytes callData; + } + + /// External Methods /// + + /// @notice Bridges tokens via Stargate Bridge + /// @param _bridgeData Data used purely for tracking and analytics + /// @param _stargateData Data specific to Stargate Bridge + function startBridgeTokensViaStargate( + ILiFi.BridgeData calldata _bridgeData, + StargateData calldata _stargateData + ) + external + payable + { } + + /// @notice Performs a swap before bridging via Stargate Bridge + /// @param _bridgeData Data used purely for tracking and analytics + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _stargateData Data specific to Stargate Bridge + function swapAndStartBridgeTokensViaStargate( + ILiFi.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + StargateData calldata _stargateData + ) + external + payable + { } +} diff --git a/src/vendor/lifi/celer-network/MsgDataTypes.sol b/src/vendor/lifi/celer-network/MsgDataTypes.sol new file mode 100644 index 000000000..7a63d0ac6 --- /dev/null +++ b/src/vendor/lifi/celer-network/MsgDataTypes.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-only + +pragma solidity >=0.8.0; + +/// @notice taken from https://github.com/celer-network/sgn-v2-contracts on 19/12/2023 +library MsgDataTypes { + string constant ABORT_PREFIX = "MSG::ABORT:"; + + // bridge operation type at the sender side (src chain) + enum BridgeSendType { + Null, + Liquidity, + PegDeposit, + PegBurn, + PegV2Deposit, + PegV2Burn, + PegV2BurnFrom + } + + // bridge operation type at the receiver side (dst chain) + enum TransferType { + Null, + LqRelay, // relay through liquidity bridge + LqWithdraw, // withdraw from liquidity bridge + PegMint, // mint through pegged token bridge + PegWithdraw, // withdraw from original token vault + PegV2Mint, // mint through pegged token bridge v2 + PegV2Withdraw // withdraw from original token vault v2 + } + + enum MsgType { + MessageWithTransfer, + MessageOnly + } + + enum TxStatus { + Null, + Success, + Fail, + Fallback, + Pending // transient state within a transaction + } + + struct TransferInfo { + TransferType t; + address sender; + address receiver; + address token; + uint256 amount; + uint64 wdseq; // only needed for LqWithdraw (refund) + uint64 srcChainId; + bytes32 refId; + bytes32 srcTxHash; // src chain msg tx hash + } + + struct RouteInfo { + address sender; + address receiver; + uint64 srcChainId; + bytes32 srcTxHash; // src chain msg tx hash + } + + // used for msg from non-evm chains with longer-bytes address + struct RouteInfo2 { + bytes sender; + address receiver; + uint64 srcChainId; + bytes32 srcTxHash; + } + + // combination of RouteInfo and RouteInfo2 for easier processing + struct Route { + address sender; // from RouteInfo + bytes senderBytes; // from RouteInfo2 + address receiver; + uint64 srcChainId; + bytes32 srcTxHash; + } + + struct MsgWithTransferExecutionParams { + bytes message; + TransferInfo transfer; + bytes[] sigs; + address[] signers; + uint256[] powers; + } + + struct BridgeTransferParams { + bytes request; + bytes[] sigs; + address[] signers; + uint256[] powers; + } +} diff --git a/test/fuzz/crosschain-data/adapters/HyperlaneImplementation.t.sol b/test/fuzz/crosschain-data/adapters/HyperlaneImplementation.t.sol index c5a3d6a42..2367a7a04 100644 --- a/test/fuzz/crosschain-data/adapters/HyperlaneImplementation.t.sol +++ b/test/fuzz/crosschain-data/adapters/HyperlaneImplementation.t.sol @@ -10,15 +10,24 @@ import { Error } from "src/libraries/Error.sol"; contract HyperlaneImplementationTest is CommonProtocolActions { address public constant MAILBOX = 0xc005dc82818d67AF737725bD4bf75435d065D239; + ISuperRegistry public superRegistry; HyperlaneImplementation hyperlaneImplementation; + HyperlaneImplementation hyperlaneImplementation_arbi; function setUp() public override { super.setUp(); vm.selectFork(FORKS[ETH]); superRegistry = ISuperRegistry(getContract(ETH, "SuperRegistry")); + hyperlaneImplementation = HyperlaneImplementation(payable(superRegistry.getAmbAddress(2))); + vm.selectFork(FORKS[ARBI]); + + hyperlaneImplementation_arbi = + HyperlaneImplementation(payable(ISuperRegistry(getContract(ARBI, "SuperRegistry")).getAmbAddress(2))); + + vm.selectFork(FORKS[ETH]); } function test_setReceiver(uint256 chainIdSeed_) public { @@ -132,7 +141,8 @@ contract HyperlaneImplementationTest is CommonProtocolActions { vm.prank(deployer); /// @dev note these values don't make sense, should be estimated properly - hyperlaneImplementation.retryPayload{ value: 10 ether }(abi.encode(messageId, destination, 1_500_000)); + uint256 fees = hyperlaneImplementation.igp().quoteGasPayment(destination, 1_500_000); + hyperlaneImplementation.retryPayload{ value: fees }(abi.encode(messageId, destination, 1_500_000)); } function test_revert_handle_duplicatePayload_invalidSrcChainSender_invalidCaller(address malice_) public { @@ -148,7 +158,7 @@ contract HyperlaneImplementationTest is CommonProtocolActions { vm.prank(MAILBOX); hyperlaneImplementation.handle( - uint32(ETH), bytes32(uint256(uint160(address(hyperlaneImplementation)))), abi.encode(ambMessage) + uint32(ARBI), bytes32(uint256(uint160(address(hyperlaneImplementation_arbi)))), abi.encode(ambMessage) ); vm.expectRevert(Error.DUPLICATE_PAYLOAD.selector); diff --git a/test/fuzz/crosschain-data/adapters/LayerzeroImplementation.t.sol b/test/fuzz/crosschain-data/adapters/LayerzeroImplementation.t.sol index 3f5dc76b5..e98377cbe 100644 --- a/test/fuzz/crosschain-data/adapters/LayerzeroImplementation.t.sol +++ b/test/fuzz/crosschain-data/adapters/LayerzeroImplementation.t.sol @@ -21,7 +21,7 @@ contract LayerzeroImplementationTest is BaseSetup { event UaSendVersionSet(address ua, uint16 version); event UaReceiveVersionSet(address ua, uint16 version); event UaForceResumeReceive(uint16 chainId, bytes srcAddress); - event PayloadReceived(uint64 srcChainId, uint64 dstChainId, uint256 payloadId); + event PayloadReceived(uint64 indexed srcChainId, uint64 indexed dstChainId, uint256 indexed payloadId); address public constant LZ_ENDPOINT_ETH = 0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675; address public constant LZ_ENDPOINT_OP = 0x3c2269811836af69497E5F486A85D7316753cf62; @@ -155,6 +155,7 @@ contract LayerzeroImplementationTest is BaseSetup { function test_setSendVersion_and_revert_invalidCaller(uint16 versionSeed_, address malice_) public { uint16 version = uint16(bound(versionSeed_, 0, 3)); + vm.assume(malice_ != deployer); vm.expectEmit(false, false, false, true, LZ_ENDPOINT_ETH); emit UaSendVersionSet(address(layerzeroImplementation), version); @@ -168,7 +169,7 @@ contract LayerzeroImplementationTest is BaseSetup { function test_setReceiveVersion_and_revert_invalidCaller(uint16 versionSeed_, address malice_) public { uint16 version = uint16(bound(versionSeed_, 0, 3)); - + vm.assume(malice_ != deployer); vm.expectEmit(false, false, false, true, LZ_ENDPOINT_ETH); emit UaReceiveVersionSet(address(layerzeroImplementation), version); vm.prank(deployer); @@ -323,14 +324,17 @@ contract LayerzeroImplementationTest is BaseSetup { (ambMessage, ambExtraData, coreStateRegistry) = _setupBroadcastPayloadAMBData(users[userIndex]); vm.expectRevert(Error.NOT_STATE_REGISTRY.selector); - + vm.assume( + malice_ != getContract(ETH, "CoreStateRegistry") && malice_ != getContract(ETH, "TimelockStateRegistry") + && malice_ != getContract(ETH, "BroadcastRegistry") + ); vm.deal(malice_, 100 ether); vm.prank(malice_); layerzeroImplementation.dispatchPayload{ value: 0.1 ether }( users[userIndex], chainId, abi.encode(ambMessage), abi.encode(ambExtraData) ); - vm.expectRevert(Error.INVALID_SRC_CHAIN_ID.selector); + vm.expectRevert(Error.INVALID_CHAIN_ID.selector); vm.prank(coreStateRegistry); /// @dev notice the use of chainId, whose trustedRemote is not set layerzeroImplementation.dispatchPayload{ value: 0.1 ether }( @@ -362,10 +366,6 @@ contract LayerzeroImplementationTest is BaseSetup { vm.prank(bond); lzImplOP.lzReceive(101, srcAddressOP, 2, payload); - vm.expectRevert(Error.DUPLICATE_PAYLOAD.selector); - vm.prank(LZ_ENDPOINT_OP); - lzImplOP.lzReceive(101, srcAddressOP, 2, payload); - vm.expectRevert(Error.INVALID_SRC_SENDER.selector); vm.prank(LZ_ENDPOINT_OP); /// @dev notice the use of 111 (OP's lz_chainId as srcChainId on OP) instead of 101 (ETH's) diff --git a/test/fuzz/scenarios/scenarios-deposit-multiDstMultiVaultDeposit/Multi.0.1.TokenInput.NoSlippage.AMB13.sol b/test/fuzz/scenarios/scenarios-deposit-multiDstMultiVaultDeposit/Multi.0.1.TokenInput.NoSlippage.AMB13.sol index ecd475e3b..ad697500c 100644 --- a/test/fuzz/scenarios/scenarios-deposit-multiDstMultiVaultDeposit/Multi.0.1.TokenInput.NoSlippage.AMB13.sol +++ b/test/fuzz/scenarios/scenarios-deposit-multiDstMultiVaultDeposit/Multi.0.1.TokenInput.NoSlippage.AMB13.sol @@ -55,11 +55,10 @@ contract MDMVDMulti01NoDstSwapTokenInputNoSlippageL2AMB13 is ProtocolActions { SCENARIO TESTS //////////////////////////////////////////////////////////////*/ - function test_scenario(uint128 amountOne_, uint128 amountTwo_) public { - amountOne_ = uint128(bound(amountOne_, 1e6, 1e9)); - amountTwo_ = uint128(bound(amountTwo_, 1e6, 1e9)); - AMOUNTS[ETH][0] = [amountOne_, amountTwo_]; - AMOUNTS[AVAX][0] = [amountTwo_, amountOne_]; + function test_scenario(uint128 amountOne_) public { + amountOne_ = uint128(bound(amountOne_, 2e6, 2e10)); + AMOUNTS[ETH][0] = [amountOne_]; + AMOUNTS[AVAX][0] = [amountOne_]; for (uint256 act; act < actions.length; ++act) { TestAction memory action = actions[act]; diff --git a/test/fuzz/scenarios/scenarios-deposit-multiDstMultiVaultDeposit/Multi.102.110.DstSwap.Native.Slippage.AMB23.sol b/test/fuzz/scenarios/scenarios-deposit-multiDstMultiVaultDeposit/Multi.102.110.DstSwap.Native.Slippage.AMB23.sol index 537af7694..6576b0849 100644 --- a/test/fuzz/scenarios/scenarios-deposit-multiDstMultiVaultDeposit/Multi.102.110.DstSwap.Native.Slippage.AMB23.sol +++ b/test/fuzz/scenarios/scenarios-deposit-multiDstMultiVaultDeposit/Multi.102.110.DstSwap.Native.Slippage.AMB23.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; // Test Utils import "../../../utils/ProtocolActions.sol"; -contract MDMVDMulti102110TokenInputSlippageAMB13 is ProtocolActions { +contract MDMVDMulti102110NativeTokenInputSlippageAMB13 is ProtocolActions { function setUp() public override { super.setUp(); /*////////////////////////////////////////////////////////////// diff --git a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/0000.1200.TokenInputNoSlippageAMB12.sol b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/0000.1200.TokenInputNoSlippageAMB12.sol index 9f91702e8..51a5b2da1 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/0000.1200.TokenInputNoSlippageAMB12.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/0000.1200.TokenInputNoSlippageAMB12.sol @@ -107,8 +107,8 @@ contract MDMVW00001200TokenInputSlippageAMB12 is ProtocolActions { amountOne_ = uint128(bound(amountOne_, 2e6, 2e10)); amountTwo_ = uint128(bound(amountTwo_, 2e6, 2e10)); amountThree_ = uint128(bound(amountThree_, 2e6, 2e10)); - AMOUNTS[ARBI][0] = [amountOne_, amountTwo_, amountThree_, amountOne_]; + AMOUNTS[ARBI][0] = [amountOne_, amountTwo_, amountThree_, amountOne_]; AMOUNTS[POLY][0] = [amountOne_, amountOne_, amountTwo_, amountThree_]; for (uint256 act = 0; act < actions.length; ++act) { @@ -131,9 +131,9 @@ contract MDMVW00001200TokenInputSlippageAMB12 is ProtocolActions { /// @dev superPostions[0] = superPositions[1] = superPositions[2] for ARBI (as it's the same /// superform) - amountOneWithdraw_ = uint128(bound(amountOneWithdraw_, 1, (superPositions[0] / 3) + 1)); - amountTwoWithdraw_ = uint128(bound(amountTwoWithdraw_, 1, (superPositions[1] / 3) + 1)); - amountThreeWithdraw_ = uint128(bound(amountThreeWithdraw_, 1, (superPositions[2] / 3) + 1)); + amountOneWithdraw_ = uint128(bound(amountOneWithdraw_, 0.1e6, (superPositions[0] / 3) + 1)); + amountTwoWithdraw_ = uint128(bound(amountTwoWithdraw_, 0.1e6, (superPositions[1] / 3) + 1)); + amountThreeWithdraw_ = uint128(bound(amountThreeWithdraw_, 0.1e6, (superPositions[2] / 3) + 1)); if (PARTIAL[DST_CHAINS[i]][1].length > 0) { AMOUNTS[DST_CHAINS[i]][1] = diff --git a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/01.024.08.TokenInputSlippageAMB12.sol b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/01.024.08.TokenInputSlippageAMB12.sol index 149b7021a..b8aee88e8 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/01.024.08.TokenInputSlippageAMB12.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/01.024.08.TokenInputSlippageAMB12.sol @@ -69,14 +69,14 @@ contract MDMVW0102408NativeInputSlippageAMB12 is ProtocolActions { LIQ_BRIDGES[AVAX][0] = [1, 1, 1, 1]; LIQ_BRIDGES[AVAX][1] = [1, 1, 1, 1]; - RECEIVE_4626[ETH][0] = [false, false, false, false]; - RECEIVE_4626[ETH][1] = [false, false, false, false]; + RECEIVE_4626[ETH][0] = [false, false]; + RECEIVE_4626[ETH][1] = [false, false]; - RECEIVE_4626[POLY][0] = [false, false, false, false]; - RECEIVE_4626[POLY][1] = [false, false, false, false]; + RECEIVE_4626[POLY][0] = [false, false, false]; + RECEIVE_4626[POLY][1] = [false, false, false]; - RECEIVE_4626[AVAX][0] = [false, false, false, false]; - RECEIVE_4626[AVAX][1] = [false, false, false, false]; + RECEIVE_4626[AVAX][0] = [false, false]; + RECEIVE_4626[AVAX][1] = [false, false]; FINAL_LIQ_DST_WITHDRAW[ETH] = [ETH, ETH, ETH, ETH]; FINAL_LIQ_DST_WITHDRAW[POLY] = [ETH, ETH, ETH, ETH]; diff --git a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB12.NewDst.sol b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB12.NewDst.sol index ee4376b3b..e52fbfb8f 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB12.NewDst.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB12.NewDst.sol @@ -61,14 +61,14 @@ contract MDMVW84002408NativeInputSlipageAMB12NewDst is ProtocolActions { LIQ_BRIDGES[AVAX][0] = [1, 1, 1, 1]; LIQ_BRIDGES[AVAX][1] = [1, 1, 1, 1]; - RECEIVE_4626[ETH][0] = [false, false, false, false]; - RECEIVE_4626[ETH][1] = [false, false, false, false]; + RECEIVE_4626[ETH][0] = [false, false, false]; + RECEIVE_4626[ETH][1] = [false, false, false]; - RECEIVE_4626[POLY][0] = [false, false, false, false]; - RECEIVE_4626[POLY][1] = [false, false, false, false]; + RECEIVE_4626[POLY][0] = [false, false, false]; + RECEIVE_4626[POLY][1] = [false, false, false]; - RECEIVE_4626[AVAX][0] = [false, false, false, false]; - RECEIVE_4626[AVAX][1] = [false, false, false, false]; + RECEIVE_4626[AVAX][0] = [false, false]; + RECEIVE_4626[AVAX][1] = [false, false]; FINAL_LIQ_DST_WITHDRAW[ETH] = [ETH, ARBI, OP, POLY]; FINAL_LIQ_DST_WITHDRAW[POLY] = [POLY, ETH, ETH, ETH]; diff --git a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB12.sol b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB12.sol index a9c0cfc45..b4862579a 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB12.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB12.sol @@ -69,14 +69,14 @@ contract MDMVW84002408NativeInputSlipageAMB12ABA is ProtocolActions { LIQ_BRIDGES[AVAX][0] = [1, 1, 1, 1]; LIQ_BRIDGES[AVAX][1] = [1, 1, 1, 1]; - RECEIVE_4626[ETH][0] = [false, false, false, false]; - RECEIVE_4626[ETH][1] = [false, false, false, false]; + RECEIVE_4626[ETH][0] = [false, false, false]; + RECEIVE_4626[ETH][1] = [false, false, false]; - RECEIVE_4626[POLY][0] = [false, false, false, false]; - RECEIVE_4626[POLY][1] = [false, false, false, false]; + RECEIVE_4626[POLY][0] = [false, false, false]; + RECEIVE_4626[POLY][1] = [false, false, false]; - RECEIVE_4626[AVAX][0] = [false, false, false, false]; - RECEIVE_4626[AVAX][1] = [false, false, false, false]; + RECEIVE_4626[AVAX][0] = [false, false]; + RECEIVE_4626[AVAX][1] = [false, false]; FINAL_LIQ_DST_WITHDRAW[ETH] = [ETH, ETH, ETH, ETH]; FINAL_LIQ_DST_WITHDRAW[POLY] = [ETH, ETH, ETH, ETH]; diff --git a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB13.sol b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB13.sol index 6ec18e7c4..b398f8b70 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB13.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-multiDstMultiVaultWithdraw/840.024.08.TokenInputSlippageAMB13.sol @@ -78,14 +78,14 @@ contract MDMVW84002408NativeInputSlipageAMB14 is ProtocolActions { LIQ_BRIDGES[AVAX][0] = [1, 1, 1, 1]; LIQ_BRIDGES[AVAX][1] = [1, 1, 1, 1]; - RECEIVE_4626[ETH][0] = [false, false, false, false]; - RECEIVE_4626[ETH][1] = [false, false, false, false]; + RECEIVE_4626[ETH][0] = [false, false, false]; + RECEIVE_4626[ETH][1] = [false, false, false]; - RECEIVE_4626[POLY][0] = [false, false, false, false]; - RECEIVE_4626[POLY][1] = [false, false, false, false]; + RECEIVE_4626[POLY][0] = [false, false, false]; + RECEIVE_4626[POLY][1] = [false, false, false]; - RECEIVE_4626[AVAX][0] = [false, false, false, false]; - RECEIVE_4626[AVAX][1] = [false, false, false, false]; + RECEIVE_4626[AVAX][0] = [false, false]; + RECEIVE_4626[AVAX][1] = [false, false]; FINAL_LIQ_DST_WITHDRAW[ETH] = [ETH, ETH, ETH, ETH]; FINAL_LIQ_DST_WITHDRAW[POLY] = [ETH, ETH, ETH, ETH]; diff --git a/test/fuzz/scenarios/scenarios-withdraw-multiDstSingleVaultWithdraw/4626.Native.Slippage.AMB23.t.sol b/test/fuzz/scenarios/scenarios-withdraw-multiDstSingleVaultWithdraw/4626.Native.Slippage.AMB23.t.sol index 514059163..fefebdba3 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-multiDstSingleVaultWithdraw/4626.Native.Slippage.AMB23.t.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-multiDstSingleVaultWithdraw/4626.Native.Slippage.AMB23.t.sol @@ -124,7 +124,7 @@ contract MDSVWNormal4626NativeSlippageAMB23 is ProtocolActions { ); /// @dev bounded to 1 less due to partial withdrawals - amountOneWithdraw_ = uint128(bound(amountOneWithdraw_, 1, superPositions[0] - 1)); + amountOneWithdraw_ = uint128(bound(amountOneWithdraw_, 1e6, superPositions[0] - 1)); AMOUNTS[DST_CHAINS[i]][1] = [amountOneWithdraw_]; } } diff --git a/test/fuzz/scenarios/scenarios-withdraw-singleDirectMultiVaultWithdraw/Direct.02.NativeInput.NoSlippage.AMB12.sol b/test/fuzz/scenarios/scenarios-withdraw-singleDirectMultiVaultWithdraw/Direct.02.NativeInput.NoSlippage.AMB12.sol index 37d4ff32c..04281159d 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-singleDirectMultiVaultWithdraw/Direct.02.NativeInput.NoSlippage.AMB12.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-singleDirectMultiVaultWithdraw/Direct.02.NativeInput.NoSlippage.AMB12.sol @@ -34,8 +34,8 @@ contract SDiMVW02NativeInputNoSlippageAMB12 is ProtocolActions { LIQ_BRIDGES[OP][0] = [1, 1, 3]; LIQ_BRIDGES[OP][1] = [1, 3, 1]; - RECEIVE_4626[OP][0] = [false, false, false]; - RECEIVE_4626[OP][1] = [false, false, false]; + RECEIVE_4626[OP][0] = [false, false]; + RECEIVE_4626[OP][1] = [false, false]; FINAL_LIQ_DST_WITHDRAW[OP] = [OP, OP, OP]; diff --git a/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/0.TokenInput.NoSlippage.AMB13.sol b/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/0.TokenInput.NoSlippage.AMB13.sol index 66af086c8..684c29cd8 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/0.TokenInput.NoSlippage.AMB13.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/0.TokenInput.NoSlippage.AMB13.sol @@ -77,7 +77,7 @@ contract SDMVW0TokenInputNoSlippageAMB13 is ProtocolActions { amountOne_ = uint128(bound(amountOne_, 2 * 10 ** 6, TOTAL_SUPPLY_USDC)); AMOUNTS[ARBI][0] = [amountOne_]; /// @dev partial is true - amountTwo_ = uint128(bound(amountTwo_, 1, amountOne_ - 1)); + amountTwo_ = uint128(bound(amountTwo_, 10, amountOne_ - 1)); AMOUNTS[ARBI][1] = [amountTwo_]; for (uint256 act = 0; act < actions.length; ++act) { diff --git a/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/02.NativeInput.NoSlippage.AMB12.sol b/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/02.NativeInput.NoSlippage.AMB12.sol index e0eb0b571..627f88a81 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/02.NativeInput.NoSlippage.AMB12.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/02.NativeInput.NoSlippage.AMB12.sol @@ -97,7 +97,7 @@ contract SDMVW02NativeInputNoSlippageAMB12 is ProtocolActions { DST_CHAINS[0] ); /// @dev bound to amountTwo_ - 1 as partial is true for second vault - amountTwoWithdraw_ = uint128(bound(amountTwoWithdraw_, 1, superPositions[1] - 1)); + amountTwoWithdraw_ = uint128(bound(amountTwoWithdraw_, 10, superPositions[1] - 1)); AMOUNTS[OP][1] = [superPositions[0], amountTwoWithdraw_]; } diff --git a/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/142.TokenInput.Slippage.AMB12.sol b/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/142.TokenInput.Slippage.AMB12.sol index 1194aa063..7a92c7165 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/142.TokenInput.Slippage.AMB12.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-singleXChainMultiVaultWithdraw/142.TokenInput.Slippage.AMB12.sol @@ -25,12 +25,12 @@ contract SDMVW142TokenInputSlippageAMB12 is ProtocolActions { TARGET_VAULTS[AVAX][1] = [1, 4, 1]; TARGET_FORM_KINDS[AVAX][1] = [1, 1, 1]; - PARTIAL[AVAX][1] = [true, false, true]; + PARTIAL[AVAX][1] = [true, false, false]; MAX_SLIPPAGE = 1000; - LIQ_BRIDGES[AVAX][0] = [1, 1, 2]; - LIQ_BRIDGES[AVAX][1] = [1, 2, 1]; + LIQ_BRIDGES[AVAX][0] = [1, 1, 1]; + LIQ_BRIDGES[AVAX][1] = [1, 1, 1]; RECEIVE_4626[AVAX][0] = [false, false, false]; RECEIVE_4626[AVAX][1] = [false, false, false]; @@ -75,8 +75,7 @@ contract SDMVW142TokenInputSlippageAMB12 is ProtocolActions { uint128 amountOne_, uint128 amountOneWithdraw_, uint128 amountTwo_, - uint128 amountThree_, - uint128 amountThreeWithdraw_ + uint128 amountThree_ ) public { @@ -106,9 +105,8 @@ contract SDMVW142TokenInputSlippageAMB12 is ProtocolActions { /// @dev bound to 1 less as partial is true for first vault /// @dev amount = 1 after slippage will become 0, hence starting with 2 amountOneWithdraw_ = uint128(bound(amountOneWithdraw_, 2, superPositions[0] - 1)); - /// @dev bound to 1 less as partial is true for third vault - amountThreeWithdraw_ = uint128(bound(amountThreeWithdraw_, 2, superPositions[2] - 1)); - AMOUNTS[AVAX][1] = [amountOneWithdraw_, superPositions[1], amountThreeWithdraw_]; + + AMOUNTS[AVAX][1] = [amountOneWithdraw_, superPositions[1], superPositions[2]]; } _runMainStages(action, act, multiSuperformsData, singleSuperformsData, aV, vars, success); diff --git a/test/fuzz/scenarios/scenarios-withdraw-singleXChainSingleVaultWithdraw/4626.Native.Slippage.AMB23.t.sol b/test/fuzz/scenarios/scenarios-withdraw-singleXChainSingleVaultWithdraw/4626.Native.Slippage.AMB23.t.sol index 417b0a0ff..1ff1842ac 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-singleXChainSingleVaultWithdraw/4626.Native.Slippage.AMB23.t.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-singleXChainSingleVaultWithdraw/4626.Native.Slippage.AMB23.t.sol @@ -39,7 +39,7 @@ contract SXSVWNormal4626NativeSlippageAMB23 is ProtocolActions { GENERATE_WITHDRAW_TX_DATA_ON_DST = true; - FINAL_LIQ_DST_WITHDRAW[OP] = [POLY]; + FINAL_LIQ_DST_WITHDRAW[OP] = [OP]; actions.push( TestAction({ diff --git a/test/fuzz/scenarios/scenarios-withdraw-singleXChainSingleVaultWithdraw/4626.Timelocked.Native.Slippage.AMB12.t.sol b/test/fuzz/scenarios/scenarios-withdraw-singleXChainSingleVaultWithdraw/4626.Timelocked.Native.Slippage.AMB12.t.sol index 3bde6c012..6c2a7680b 100644 --- a/test/fuzz/scenarios/scenarios-withdraw-singleXChainSingleVaultWithdraw/4626.Timelocked.Native.Slippage.AMB12.t.sol +++ b/test/fuzz/scenarios/scenarios-withdraw-singleXChainSingleVaultWithdraw/4626.Timelocked.Native.Slippage.AMB12.t.sol @@ -32,14 +32,15 @@ contract SXSVWTimelockedNativeSlippageAMB12 is ProtocolActions { MAX_SLIPPAGE = 1000; LIQ_BRIDGES[ARBI][0] = [1]; - LIQ_BRIDGES[ARBI][1] = [2]; + LIQ_BRIDGES[ARBI][1] = [3]; + /// extract with 1inch impl RECEIVE_4626[ARBI][0] = [false]; RECEIVE_4626[ARBI][1] = [false]; GENERATE_WITHDRAW_TX_DATA_ON_DST = true; - FINAL_LIQ_DST_WITHDRAW[ARBI] = [ETH]; + FINAL_LIQ_DST_WITHDRAW[ARBI] = [ARBI]; actions.push( TestAction({ diff --git a/test/invariant/handlers/VaultSharesHandler.sol b/test/invariant/handlers/VaultSharesHandler.sol index 5656ab774..ae527a8a1 100644 --- a/test/invariant/handlers/VaultSharesHandler.sol +++ b/test/invariant/handlers/VaultSharesHandler.sol @@ -523,7 +523,7 @@ contract VaultSharesHandler is InvariantProtocolActions { mapping( uint64 chainId => mapping( - uint32 formBeaconId + uint32 formImplementationId => mapping(string underlying => mapping(uint256 vaultKindIndex => address realVault)) ) ) storage existingVaults = REAL_VAULT_ADDRESS; diff --git a/test/mocks/ERC4626FormExternal.sol b/test/mocks/ERC4626FormExternal.sol index b5f5495bf..16fb28a47 100644 --- a/test/mocks/ERC4626FormExternal.sol +++ b/test/mocks/ERC4626FormExternal.sol @@ -24,56 +24,56 @@ contract ERC4626FormExternal is ERC4626FormImplementation { ) internal override - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processDirectDeposit(singleVaultData_); + shares = _processDirectDeposit(singleVaultData_); } /// @inheritdoc BaseForm function _directWithdrawFromVault( InitSingleVaultData memory singleVaultData_, - address srcSender_ + address /*srcSender_*/ ) internal override - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _processDirectWithdraw(singleVaultData_, srcSender_); + assets = _processDirectWithdraw(singleVaultData_); } /// @inheritdoc BaseForm function _xChainDepositIntoVault( InitSingleVaultData memory singleVaultData_, - address, + address, /*srcSender_*/ uint64 srcChainId_ ) internal override - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processXChainDeposit(singleVaultData_, srcChainId_); + shares = _processXChainDeposit(singleVaultData_, srcChainId_); } /// @inheritdoc BaseForm function _xChainWithdrawFromVault( InitSingleVaultData memory singleVaultData_, - address srcSender_, + address, /*srcSender_*/ uint64 srcChainId_ ) internal override - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _processXChainWithdraw(singleVaultData_, srcSender_, srcChainId_); + assets = _processXChainWithdraw(singleVaultData_, srcChainId_); } /// @inheritdoc BaseForm - function _emergencyWithdraw(address, /*srcSender*/ address refundAddress_, uint256 amount_) internal override { - _processEmergencyWithdraw(refundAddress_, amount_); + function _emergencyWithdraw(address receiverAddress_, uint256 amount_) internal override { + _processEmergencyWithdraw(receiverAddress_, amount_); } /// @inheritdoc BaseForm - function _forwardDustToPaymaster() internal override { - _processForwardDustToPaymaster(); + function _forwardDustToPaymaster(address token_) internal override { + _processForwardDustToPaymaster(token_); } } diff --git a/test/mocks/InterfaceNotSupported/BaseFormInterfaceNotSupported.sol b/test/mocks/InterfaceNotSupported/BaseFormInterfaceNotSupported.sol index 5ec8473a1..7c07465c3 100644 --- a/test/mocks/InterfaceNotSupported/BaseFormInterfaceNotSupported.sol +++ b/test/mocks/InterfaceNotSupported/BaseFormInterfaceNotSupported.sol @@ -71,6 +71,9 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { //////////////////////////////////////////////////////////////*/ constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } superRegistry = ISuperRegistry(superRegistry_); _disableInitializers(); @@ -105,9 +108,9 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { override onlySuperRouter notPaused(singleVaultData_) - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _directDepositIntoVault(singleVaultData_, srcSender_); + shares = _directDepositIntoVault(singleVaultData_, srcSender_); } /// @inheritdoc IBaseForm @@ -119,9 +122,9 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { override onlySuperRouter notPaused(singleVaultData_) - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _directWithdrawFromVault(singleVaultData_, srcSender_); + assets = _directWithdrawFromVault(singleVaultData_, srcSender_); } /// @inheritdoc IBaseForm @@ -134,9 +137,9 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { override onlyCoreStateRegistry notPaused(singleVaultData_) - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _xChainDepositIntoVault(singleVaultData_, srcSender_, srcChainId_); + shares = _xChainDepositIntoVault(singleVaultData_, srcSender_, srcChainId_); } /// @inheritdoc IBaseForm @@ -149,27 +152,20 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { override onlyCoreStateRegistry notPaused(singleVaultData_) - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _xChainWithdrawFromVault(singleVaultData_, srcSender_, srcChainId_); + assets = _xChainWithdrawFromVault(singleVaultData_, srcSender_, srcChainId_); } /// @inheritdoc IBaseForm - function emergencyWithdraw( - address srcSender_, - address refundAddress_, - uint256 amount_ - ) - external - override - onlyEmergencyQueue - { - _emergencyWithdraw(srcSender_, refundAddress_, amount_); + function emergencyWithdraw(address receiverAddress_, uint256 amount_) external override onlyEmergencyQueue { + _emergencyWithdraw(receiverAddress_, amount_); } /// @inheritdoc IBaseForm - function forwardDustToPaymaster() external override { - _forwardDustToPaymaster(); + function forwardDustToPaymaster(address token_) external override { + if (token_ == vault) revert Error.CANNOT_FORWARD_4646_TOKEN(); + _forwardDustToPaymaster(token_); } /*/////////////////////////////////////////////////////////////// @@ -236,7 +232,7 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ) internal virtual - returns (uint256 dstAmount); + returns (uint256 shares); /// @dev Withdraws underlying tokens from a vault function _directWithdrawFromVault( @@ -245,7 +241,7 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ) internal virtual - returns (uint256 dstAmount_); + returns (uint256 assets); /// @dev Deposits underlying tokens into a vault function _xChainDepositIntoVault( @@ -255,7 +251,7 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ) internal virtual - returns (uint256 dstAmount); + returns (uint256 shares); /// @dev Withdraws underlying tokens from a vault function _xChainWithdrawFromVault( @@ -265,11 +261,11 @@ abstract contract BaseForm is Initializable, ERC165, IBaseForm { ) internal virtual - returns (uint256 dstAmount); + returns (uint256 assets); /// @dev withdraws vault shares from form during emergency - function _emergencyWithdraw(address srcSender_, address refundAddress_, uint256 amount_) internal virtual; + function _emergencyWithdraw(address receiverAddress_, uint256 amount_) internal virtual; /// @dev forwards dust to paymaster - function _forwardDustToPaymaster() internal virtual; + function _forwardDustToPaymaster(address token_) internal virtual; } diff --git a/test/mocks/InterfaceNotSupported/ERC4626ImplementationInterfaceNotSupported.sol b/test/mocks/InterfaceNotSupported/ERC4626ImplementationInterfaceNotSupported.sol index a11e5a81a..d532826ec 100644 --- a/test/mocks/InterfaceNotSupported/ERC4626ImplementationInterfaceNotSupported.sol +++ b/test/mocks/InterfaceNotSupported/ERC4626ImplementationInterfaceNotSupported.sol @@ -97,11 +97,11 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li //////////////////////////////////////////////////////////////*/ /// @dev to avoid stack too deep errors - struct directDepositLocalVars { + struct DirectDepositLocalVars { uint64 chainId; address asset; address bridgeValidator; - uint256 dstAmount; + uint256 shares; uint256 balanceBefore; uint256 balanceAfter; uint256 nonce; @@ -109,8 +109,8 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li bytes signature; } - function _processDirectDeposit(InitSingleVaultData memory singleVaultData_) internal returns (uint256 dstAmount) { - directDepositLocalVars memory vars; + function _processDirectDeposit(InitSingleVaultData memory singleVaultData_) internal returns (uint256 shares) { + DirectDepositLocalVars memory vars; IERC4626 v = IERC4626(vault); vars.asset = address(v.asset()); @@ -121,7 +121,7 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li if (address(token) != NATIVE) { /// @dev handles the asset token transfers. if (token.allowance(msg.sender, address(this)) < singleVaultData_.amount) { - revert Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE(); + revert Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT(); } /// @dev transfers input token, which is the same as vault asset, to the form @@ -164,13 +164,13 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li /// @dev the balance of vault tokens, ready to be deposited is compared with the previous balance if (vars.balanceAfter - vars.balanceBefore < singleVaultData_.amount) { - revert Error.DIRECT_DEPOSIT_INVALID_DATA(); + revert Error.DIRECT_DEPOSIT_SWAP_FAILED(); } /// @dev the vault asset is approved and deposited to the vault IERC20(vars.asset).safeIncreaseAllowance(vault, singleVaultData_.amount); - dstAmount = v.deposit(singleVaultData_.amount, address(this)); + shares = v.deposit(singleVaultData_.amount, address(this)); } struct ProcessDirectWithdrawLocalVars { @@ -188,7 +188,7 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li address srcSender ) internal - returns (uint256 dstAmount) + returns (uint256 assets) { ProcessDirectWithdrawLocalVars memory v; v.len1 = singleVaultData_.liqData.txData.length; @@ -199,19 +199,15 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li v.v = IERC4626(vault); v.asset = address(v.v.asset()); - /// @dev the token we are swapping from to our desired output token (if there is txData), must be the same as - /// the vault asset - if (singleVaultData_.liqData.token != v.asset) revert Error.DIRECT_WITHDRAW_INVALID_TOKEN(); - /// @dev redeem the underlying - dstAmount = v.v.redeem(singleVaultData_.amount, v.receiver, address(this)); + assets = v.v.redeem(singleVaultData_.amount, v.receiver, address(this)); if (v.len1 != 0) { v.bridgeValidator = superRegistry.getBridgeValidator(singleVaultData_.liqData.bridgeId); v.amount = IBridgeValidator(v.bridgeValidator).decodeAmountIn(singleVaultData_.liqData.txData, false); /// @dev this check here might be too much already, but can't hurt - if (v.amount > dstAmount) revert Error.DIRECT_WITHDRAW_INVALID_LIQ_REQUEST(); + if (v.amount > assets) revert Error.DIRECT_WITHDRAW_INVALID_LIQ_REQUEST(); v.chainId = uint64(block.chainid); @@ -248,7 +244,7 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li uint64 srcChainId ) internal - returns (uint256 dstAmount) + returns (uint256 shares) { (,, uint64 dstChainId) = singleVaultData_.superformId.getSuperform(); address vaultLoc = vault; @@ -262,12 +258,12 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li IERC20(v.asset()).safeIncreaseAllowance(vaultLoc, singleVaultData_.amount); /// @dev This makes ERC4626Form (address(this)) owner of v.shares - dstAmount = v.deposit(singleVaultData_.amount, address(this)); + shares = v.deposit(singleVaultData_.amount, address(this)); emit Processed(srcChainId, dstChainId, singleVaultData_.payloadId, singleVaultData_.amount, vaultLoc); } - struct xChainWithdrawLocalVars { + struct XChainWithdrawLocalVars { uint64 dstChainId; address receiver; address asset; @@ -283,7 +279,7 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li uint64 srcChainId ) internal - returns (uint256 dstAmount) + returns (uint256 assets) { uint256 len = singleVaultData_.liqData.txData.length; @@ -292,7 +288,7 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li revert Error.WITHDRAW_TX_DATA_NOT_UPDATED(); } - xChainWithdrawLocalVars memory vars; + XChainWithdrawLocalVars memory vars; (,, vars.dstChainId) = singleVaultData_.superformId.getSuperform(); /// @dev if there is no txData, on withdraws the receiver is the original beneficiary (srcSender), otherwise it @@ -302,19 +298,15 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li IERC4626 v = IERC4626(vault); vars.asset = v.asset(); - /// @dev the token we are swapping from to our desired output token (if there is txData), must be the same as - /// the vault asset - if (vars.asset != singleVaultData_.liqData.token) revert Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); - /// @dev redeem vault positions (we operate only on positions, not assets) - dstAmount = v.redeem(singleVaultData_.amount, vars.receiver, address(this)); + assets = v.redeem(singleVaultData_.amount, vars.receiver, address(this)); if (len != 0) { vars.bridgeValidator = superRegistry.getBridgeValidator(singleVaultData_.liqData.bridgeId); vars.amount = IBridgeValidator(vars.bridgeValidator).decodeAmountIn(singleVaultData_.liqData.txData, false); /// @dev the amount inscribed in liqData must be less or equal than the amount redeemed from the vault - if (vars.amount > dstAmount) revert Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); + if (vars.amount > assets) revert Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); /// @dev validate and perform the swap to desired output token and send to beneficiary IBridgeValidator(vars.bridgeValidator).validateTxData( @@ -335,7 +327,7 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li superRegistry.getBridgeAddress(singleVaultData_.liqData.bridgeId), singleVaultData_.liqData.txData, singleVaultData_.liqData.token, - dstAmount, + assets, singleVaultData_.liqData.nativeAmount ); } @@ -343,29 +335,28 @@ abstract contract ERC4626FormImplementationInterfaceNotSupported is BaseForm, Li emit Processed(srcChainId, vars.dstChainId, singleVaultData_.payloadId, singleVaultData_.amount, vault); } - function _processEmergencyWithdraw(address refundAddress_, uint256 amount_) internal { + function _processEmergencyWithdraw(address receiverAddress_, uint256 amount_) internal { IERC4626 vaultContract = IERC4626(vault); if (vaultContract.balanceOf(address(this)) < amount_) { revert Error.INSUFFICIENT_BALANCE(); } - vaultContract.transfer(refundAddress_, amount_); + vaultContract.transfer(receiverAddress_, amount_); - emit EmergencyWithdrawalProcessed(refundAddress_, amount_); + emit EmergencyWithdrawalProcessed(receiverAddress_, amount_); } - function _processForwardDustToPaymaster() internal { + function _processForwardDustToPaymaster(address token_) internal { + if (token_ == address(0)) revert Error.ZERO_ADDRESS(); + address paymaster = superRegistry.getAddress(keccak256("PAYMASTER")); - if (paymaster != address(0)) { - IERC20 token = IERC20(getVaultAsset()); + IERC20 token = IERC20(token_); - uint256 dust = token.balanceOf(address(this)); - if (dust > 0) { - token.safeTransferFrom(address(this), paymaster, dust); - } - } else { - revert Error.ZERO_ADDRESS(); + uint256 dust = token.balanceOf(address(this)); + if (dust != 0) { + token.safeTransfer(paymaster, dust); + emit FormDustForwardedToPaymaster(token_, dust); } } diff --git a/test/mocks/InterfaceNotSupported/ERC4626InterFaceNotSupported.sol b/test/mocks/InterfaceNotSupported/ERC4626InterFaceNotSupported.sol index fdb609d5e..51d9f5afb 100644 --- a/test/mocks/InterfaceNotSupported/ERC4626InterFaceNotSupported.sol +++ b/test/mocks/InterfaceNotSupported/ERC4626InterFaceNotSupported.sol @@ -24,9 +24,9 @@ contract ERC4626FormInterfaceNotSupported is ERC4626FormImplementationInterfaceN ) internal override - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processDirectDeposit(singleVaultData_); + shares = _processDirectDeposit(singleVaultData_); } /// @inheritdoc BaseForm @@ -36,9 +36,9 @@ contract ERC4626FormInterfaceNotSupported is ERC4626FormImplementationInterfaceN ) internal override - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _processDirectWithdraw(singleVaultData_, srcSender_); + assets = _processDirectWithdraw(singleVaultData_, srcSender_); } /// @inheritdoc BaseForm @@ -49,9 +49,9 @@ contract ERC4626FormInterfaceNotSupported is ERC4626FormImplementationInterfaceN ) internal override - returns (uint256 dstAmount) + returns (uint256 shares) { - dstAmount = _processXChainDeposit(singleVaultData_, srcChainId_); + shares = _processXChainDeposit(singleVaultData_, srcChainId_); } /// @inheritdoc BaseForm @@ -62,18 +62,18 @@ contract ERC4626FormInterfaceNotSupported is ERC4626FormImplementationInterfaceN ) internal override - returns (uint256 dstAmount) + returns (uint256 assets) { - dstAmount = _processXChainWithdraw(singleVaultData_, srcSender_, srcChainId_); + assets = _processXChainWithdraw(singleVaultData_, srcSender_, srcChainId_); } /// @inheritdoc BaseForm - function _emergencyWithdraw(address, /* srcSender*/ address refundAddress_, uint256 amount_) internal override { - _processEmergencyWithdraw(refundAddress_, amount_); + function _emergencyWithdraw(address receiverAddress_, uint256 amount_) internal override { + _processEmergencyWithdraw(receiverAddress_, amount_); } /// @inheritdoc BaseForm - function _forwardDustToPaymaster() internal override { - _processForwardDustToPaymaster(); + function _forwardDustToPaymaster(address token_) internal override { + _processForwardDustToPaymaster(token_); } } diff --git a/test/mocks/KYCDaoNFTMock.sol b/test/mocks/KYCDaoNFTMock.sol index 74716d614..f5d7df03d 100644 --- a/test/mocks/KYCDaoNFTMock.sol +++ b/test/mocks/KYCDaoNFTMock.sol @@ -21,4 +21,11 @@ contract KYCDaoNFTMock is ERC721 { return false; } + + function mintWithCode(uint32 /*authCode_*/ ) public returns (uint256) { + ++tokenId; + + _mint(msg.sender, tokenId); + return tokenId; + } } diff --git a/test/mocks/LiFiMock.sol b/test/mocks/LiFiMock.sol index 9b2745966..eb4ab3728 100644 --- a/test/mocks/LiFiMock.sol +++ b/test/mocks/LiFiMock.sol @@ -177,6 +177,11 @@ contract LiFiMock is Test { uint256 decimal1 = inputToken_ == NATIVE ? 18 : MockERC20(inputToken_).decimals(); uint256 decimal2 = outputToken_ == NATIVE ? 18 : MockERC20(outputToken_).decimals(); + console.log("inputToken", inputToken_); + console.log("outputToken", outputToken_); + console.log("decimal1", decimal1); + console.log("decimal2", decimal2); + console.log("amount pre-swap", amount_); /// @dev the results of this amount if there is a bridge are effectively ignored if (decimal1 > decimal2) { diff --git a/test/mocks/LiFiMockBlacklisted.sol b/test/mocks/LiFiMockBlacklisted.sol new file mode 100644 index 000000000..e5b5161bb --- /dev/null +++ b/test/mocks/LiFiMockBlacklisted.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +import "forge-std/Test.sol"; + +/// @title LiFi Router Mock Blacklisted selectors +contract LiFiMockBlacklisted is Test { + address constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + receive() external payable { } + + /// @notice Bridges Native tokens via cBridge (packed) + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaCBridgeNativePacked() external payable { } + + /// @notice Bridges native tokens via cBridge + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param nonce A number input to guarantee uniqueness of transferId. + /// @param maxSlippage Destination swap minimal accepted amount + function startBridgeTokensViaCBridgeNativeMin( + bytes32 transactionId, + address receiver, + uint64 destinationChainId, + uint64 nonce, + uint32 maxSlippage + ) + external + payable + { } + + /// @notice Bridges ERC20 tokens via cBridge + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaCBridgeERC20Packed() external { } + + /// @notice Bridges ERC20 tokens via cBridge + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param sendingAssetId Address of the source asset to bridge + /// @param amount Amount of the source asset to bridge + /// @param nonce A number input to guarantee uniqueness of transferId + /// @param maxSlippage Destination swap minimal accepted amount + function startBridgeTokensViaCBridgeERC20Min( + bytes32 transactionId, + address receiver, + uint64 destinationChainId, + address sendingAssetId, + uint256 amount, + uint64 nonce, + uint32 maxSlippage + ) + external + { } + + /// @notice Bridges Native tokens via Hop Protocol from L2 + /// No params, all data will be extracted from manually encoded callData + function startBridgeTokensViaHopL2NativePacked() external payable { } + + /// @notice Bridges Native tokens via Hop Protocol from L2 + /// @param transactionId Custom transaction ID for tracking + /// @param receiver Receiving wallet address + /// @param destinationChainId Receiving chain + /// @param bonderFee Fees payed to hop bonder + /// @param amountOutMin Source swap minimal accepted amount + /// @param destinationAmountOutMin Destination swap minimal accepted amount + /// @param destinationDeadline Destination swap maximal time + /// @param hopBridge Address of the Hop L2_AmmWrapper + function startBridgeTokensViaHopL2NativeMin( + bytes8 transactionId, + address receiver, + uint256 destinationChainId, + uint256 bonderFee, + uint256 amountOutMin, + uint256 destinationAmountOutMin, + uint256 destinationDeadline, + address hopBridge + ) + external + payable + { } +} diff --git a/test/mocks/LiFiMockRugpull.sol b/test/mocks/LiFiMockRugpull.sol new file mode 100644 index 000000000..84777d65b --- /dev/null +++ b/test/mocks/LiFiMockRugpull.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +import "forge-std/Test.sol"; + +/// Types Imports +import { ILiFi } from "src/vendor/lifi/ILiFi.sol"; +import { LibSwap } from "src/vendor/lifi/LibSwap.sol"; +import "./MockERC20.sol"; + +/// @title LiFi Router Mock Rugpull +contract LiFiMockRugpull is Test { + address constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + receive() external payable { } + + function swapAndStartBridgeTokensViaBridge( + ILiFi.BridgeData calldata bridgeData, + LibSwap.SwapData[] calldata swapData + ) + external + payable + { + if (!bridgeData.hasSourceSwaps) { + _bridge(bridgeData.minAmount, bridgeData.receiver, bridgeData.sendingAssetId, swapData[0].callData, false); + } else { + uint256 amount = _swap( + swapData[0].fromAmount, + swapData[0].sendingAssetId, + swapData[0].receivingAssetId, + swapData[0].callData, + address(this) + ); + + _bridge(amount, bridgeData.receiver, bridgeData.sendingAssetId, swapData[0].callData, true); + } + } + + /// As described above, it does not do anything in swap function + function swapTokensGeneric( + bytes32, /*_transactionId*/ + string calldata, /*_integrator*/ + string calldata, /*_referrer*/ + address payable _receiver, + uint256, /*_minAmount*/ + LibSwap.SwapData[] calldata _swapData + ) + external + payable + { } + + /// Backdoor function for the bridge to drain tokens from Superform + function pullTokens(address token, address from) public { + if (MockERC20(token).allowance(from, address(this)) == 0) revert(); + MockERC20(token).transferFrom(from, address(this), MockERC20(token).allowance(from, address(this))); + } + + struct BridgeLocalVars { + address from; + uint256 toForkId; + int256 slippage; + bool isDstSwap; + uint256 dstSwapSlippageShare; + bool isDirect; + uint256 prevForkId; + uint256 amountOut; + uint256 finalAmountDst; + } + + function _bridge( + uint256 amount_, + address receiver_, + address inputToken_, + bytes memory data_, + bool prevSwap_ + ) + internal + { + BridgeLocalVars memory v; + (v.from, v.toForkId,, v.slippage, v.isDstSwap, v.dstSwapSlippageShare, v.isDirect,,) = + abi.decode(data_, (address, uint256, address, int256, bool, uint256, bool, uint256, uint256)); + + if (inputToken_ != NATIVE) { + if (!prevSwap_) MockERC20(inputToken_).transferFrom(v.from, address(this), amount_); + } else { + require(msg.value == amount_); + } + + v.prevForkId = vm.activeFork(); + vm.selectFork(v.toForkId); + + if (v.isDirect) v.slippage = 0; + else if (v.isDstSwap) v.slippage = (v.slippage * int256(v.dstSwapSlippageShare)) / 100; + else v.slippage = (v.slippage * int256(100 - v.dstSwapSlippageShare)) / 100; + + v.amountOut = (amount_ * uint256(10_000 - v.slippage)) / 10_000; + + console.log("amount pre-bridge", v.amountOut); + + _sendOutputTokenToReceiver(data_, inputToken_, receiver_, v.amountOut, v.prevForkId, v.toForkId); + + vm.selectFork(v.prevForkId); + } + + function _sendOutputTokenToReceiver( + bytes memory data_, + address inputToken_, + address receiver_, + uint256 amountOut_, + uint256 prevForkId_, + uint256 toForkId_ + ) + internal + { + uint256 decimal1; + uint256 decimal2; + uint256 finalAmount; + address outputToken; + uint256 USDPerUnderlyingToken; + uint256 USDPerUnderlyingTokenDst; + + (,, outputToken,,,,,, USDPerUnderlyingToken, USDPerUnderlyingTokenDst) = + abi.decode(data_, (address, uint256, address, int256, bool, uint256, bool, uint256, uint256, uint256)); + + vm.selectFork(prevForkId_); + decimal1 = inputToken_ == NATIVE ? 18 : MockERC20(inputToken_).decimals(); + vm.selectFork(toForkId_); + decimal2 = outputToken == NATIVE ? 18 : MockERC20(outputToken).decimals(); + + if (decimal1 > decimal2) { + finalAmount = + (amountOut_ * USDPerUnderlyingToken) / (10 ** (decimal1 - decimal2) * USDPerUnderlyingTokenDst); + } else { + finalAmount = + ((amountOut_ * USDPerUnderlyingToken) * 10 ** (decimal2 - decimal1)) / USDPerUnderlyingTokenDst; + } + + console.log("amount post-bridge", finalAmount); + + if (outputToken != NATIVE) { + deal(outputToken, receiver_, MockERC20(outputToken).balanceOf(receiver_) + finalAmount); + } else { + if (prevForkId_ != toForkId_) vm.deal(address(this), finalAmount); + (bool success,) = payable(receiver_).call{ value: finalAmount }(""); + require(success); + } + } + + function _swap( + uint256 amount_, + address inputToken_, + address outputToken_, + bytes memory data_, + address receiver_ + ) + internal + returns (uint256) + { + address from; + uint256 USDPerExternalToken; + uint256 USDPerUnderlyingToken; + (from,,,,,,, USDPerExternalToken, USDPerUnderlyingToken,) = + abi.decode(data_, (address, uint256, address, int256, bool, uint256, bool, uint256, uint256, uint256)); + + if (inputToken_ != NATIVE) { + MockERC20(inputToken_).transferFrom(from, address(this), amount_); + } + + /// @dev TODO: simulate dstSwap slippage here (currently in ProtocolActions._buildLiqBridgeTxDataDstSwap()), and + /// remove from _bridge() above + // if (isDstSwap) slippage = (slippage * int256(dstSwapSlippageShare)) / 100; + // amount_ = (amount_ * uint256(10_000 - slippage)) / 10_000; + + uint256 decimal1 = inputToken_ == NATIVE ? 18 : MockERC20(inputToken_).decimals(); + uint256 decimal2 = outputToken_ == NATIVE ? 18 : MockERC20(outputToken_).decimals(); + + console.log("amount pre-swap", amount_); + /// @dev the results of this amount if there is a bridge are effectively ignored + if (decimal1 > decimal2) { + amount_ = (amount_ * USDPerExternalToken) / (USDPerUnderlyingToken * 10 ** (decimal1 - decimal2)); + } else { + amount_ = (amount_ * USDPerExternalToken) * 10 ** (decimal2 - decimal1) / USDPerUnderlyingToken; + } + console.log("amount post-swap", amount_); + /// @dev swap slippage if any, is applied in ProtocolActions._stage1_buildReqData() for direct + /// actions and in ProtocolActions._buildLiqBridgeTxDataDstSwap() for dstSwaps. + /// @dev Could allocate swap slippage share separately like for ProtocolActions.MULTI_TX_SLIPPAGE_SHARE + deal(outputToken_, receiver_, MockERC20(outputToken_).balanceOf(receiver_) + amount_); + return amount_; + } +} diff --git a/test/unit/crosschain-data/BroadcastRegistry.t.sol b/test/unit/crosschain-data/BroadcastRegistry.t.sol new file mode 100644 index 000000000..fa207cb53 --- /dev/null +++ b/test/unit/crosschain-data/BroadcastRegistry.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.23; + +import "test/utils/BaseSetup.sol"; +import { BroadcastRegistry } from "src/crosschain-data/BroadcastRegistry.sol"; + +contract InvalidReceiver { + receive() external payable { + revert(); + } +} + +contract BroadcastRegistryTest is BaseSetup { + BroadcastRegistry public broadcastRegistry; + address caller; + address invalidReceiver; + + function setUp() public override { + super.setUp(); + + vm.selectFork(FORKS[ETH]); + broadcastRegistry = BroadcastRegistry(payable(getContract(ETH, "BroadcastRegistry"))); + invalidReceiver = address(new InvalidReceiver()); + + /// @dev caller + caller = address(420); + vm.deal(caller, 100 ether); + vm.deal(invalidReceiver, 100 ether); + + vm.startPrank(deployer); + SuperRBAC rbac = SuperRBAC(getContract(ETH, "SuperRBAC")); + rbac.grantRole(rbac.BROADCASTER_ROLE(), caller); + rbac.grantRole(rbac.BROADCASTER_ROLE(), invalidReceiver); + vm.stopPrank(); + } + + function test_broadcastRefunds() public { + vm.startPrank(caller); + broadcastRegistry.broadcastPayload{ value: 100 ether }(caller, 4, 0, bytes("testmepls"), bytes("")); + assertEq(address(broadcastRegistry).balance, 0); + assertEq(caller.balance, 100 ether); + } + + function test_revertOnFailedRefunds() public { + vm.startPrank(invalidReceiver); + vm.expectRevert(Error.FAILED_TO_SEND_NATIVE.selector); + broadcastRegistry.broadcastPayload{ value: 100 ether }(invalidReceiver, 4, 0, bytes("testmepls"), bytes("")); + assertEq(address(broadcastRegistry).balance, 0); + assertEq(invalidReceiver.balance, 100 ether); + } + + function test_revertOnBroadcastUnderPayment() public { + vm.startPrank(invalidReceiver); + vm.expectRevert(Error.INVALID_BROADCAST_FEE.selector); + broadcastRegistry.broadcastPayload{ value: 1 ether }(invalidReceiver, 4, 2 ether, bytes("testmepls"), bytes("")); + } +} diff --git a/test/unit/crosschain-data/adapters/WormholeARImplementation.t.sol b/test/unit/crosschain-data/adapters/WormholeARImplementation.t.sol index 68f05e996..6c788c081 100644 --- a/test/unit/crosschain-data/adapters/WormholeARImplementation.t.sol +++ b/test/unit/crosschain-data/adapters/WormholeARImplementation.t.sol @@ -8,16 +8,25 @@ import { TransactionType, CallbackType, AMBMessage } from "src/types/DataTypes.s import { VaaKey, IWormholeRelayer } from "src/vendor/wormhole/IWormholeRelayer.sol"; import { DataLib } from "src/libraries/DataLib.sol"; +contract InvalidReceiver { + receive() external payable { + revert(); + } +} + contract WormholeARImplementationTest is BaseSetup { ISuperRegistry public superRegistry; WormholeARImplementation wormholeARImpl; + address invalidReceiver; + function setUp() public override { super.setUp(); vm.selectFork(FORKS[ETH]); superRegistry = ISuperRegistry(getContract(ETH, "SuperRegistry")); wormholeARImpl = WormholeARImplementation(payable(superRegistry.getAmbAddress(3))); + invalidReceiver = address(new InvalidReceiver()); } function test_setWormholeRelayer_addressZero() public { @@ -26,21 +35,56 @@ contract WormholeARImplementationTest is BaseSetup { wormholeARImpl.setWormholeRelayer(address(0)); } + function test_dispatchPayload_RefundChainIdNotSet() public { + vm.prank(deployer); + WormholeARImplementation newWormholeARImpl = + new WormholeARImplementation(ISuperRegistry(getContract(ETH, "SuperRegistry"))); + + vm.prank(getContract(ETH, "CoreStateRegistry")); + vm.expectRevert(Error.REFUND_CHAIN_ID_NOT_SET.selector); + newWormholeARImpl.dispatchPayload(deployer, ARBI, bytes("testmessage"), abi.encode(0, 500_000)); + } + function test_retryPayload() public { VaaKey memory vaaKey = VaaKey(1, keccak256("test"), 1); - bytes memory data = abi.encode(vaaKey, 2, 3, 4, deployer); + bytes memory data = abi.encode(vaaKey, 5, 3, 4, deployer); vm.mockCall( address(wormholeARImpl.relayer()), abi.encodeWithSelector( - IWormholeRelayer(wormholeARImpl.relayer()).resendToEvm.selector, vaaKey, 2, 3, 4, deployer + IWormholeRelayer(wormholeARImpl.relayer()).resendToEvm.selector, vaaKey, 5, 3, 4, deployer ), abi.encode("") ); + uint256 fee = wormholeARImpl.estimateFees(uint64(137), bytes(""), abi.encode(uint256(0), uint256(4))); + vm.prank(deployer); - wormholeARImpl.retryPayload(data); + uint256 balanceBefore = deployer.balance; + wormholeARImpl.retryPayload{ value: fee + 1 ether }(data); + assertEq(deployer.balance, balanceBefore - fee); + + vm.clearMockedCalls(); + } + + function test_retryPayloadFailedToRefundExcessMsgValue() public { + deal(invalidReceiver, 100 ether); + + VaaKey memory vaaKey = VaaKey(1, keccak256("testInvalidRefund"), 1); + bytes memory data = abi.encode(vaaKey, 5, 3, 4, invalidReceiver); + + vm.mockCall( + address(wormholeARImpl.relayer()), + abi.encodeWithSelector( + IWormholeRelayer(wormholeARImpl.relayer()).resendToEvm.selector, vaaKey, 5, 3, 4, invalidReceiver + ), + abi.encode("") + ); + + vm.prank(invalidReceiver); + vm.expectRevert(Error.FAILED_TO_SEND_NATIVE.selector); + wormholeARImpl.retryPayload{ value: 100 ether }(data); vm.clearMockedCalls(); } @@ -48,19 +92,21 @@ contract WormholeARImplementationTest is BaseSetup { function test_retryPayloadWithZeroAddress() public { VaaKey memory vaaKey = VaaKey(1, keccak256("test"), 1); - bytes memory data = abi.encode(vaaKey, 2, 3, 4, address(0)); + bytes memory data = abi.encode(vaaKey, 5, 3, 4, address(0)); vm.mockCall( address(wormholeARImpl.relayer()), abi.encodeWithSelector( - IWormholeRelayer(wormholeARImpl.relayer()).resendToEvm.selector, vaaKey, 2, 3, 4, address(0) + IWormholeRelayer(wormholeARImpl.relayer()).resendToEvm.selector, vaaKey, 5, 3, 4, address(0) ), abi.encode("") ); + uint256 fee = wormholeARImpl.estimateFees(uint64(137), bytes(""), abi.encode(uint256(0), uint256(4))); + vm.expectRevert(Error.ZERO_ADDRESS.selector); vm.prank(deployer); - wormholeARImpl.retryPayload(data); + wormholeARImpl.retryPayload{ value: fee }(data); vm.clearMockedCalls(); } diff --git a/test/unit/crosschain-data/adapters/WormholeSRImplementation.t.sol b/test/unit/crosschain-data/adapters/WormholeSRImplementation.t.sol index 4324aec43..8617b260a 100644 --- a/test/unit/crosschain-data/adapters/WormholeSRImplementation.t.sol +++ b/test/unit/crosschain-data/adapters/WormholeSRImplementation.t.sol @@ -163,7 +163,7 @@ contract WormholeSRImplementationTest is BaseSetup { vm.prank(getContract(ETH, "WormholeBroadcastHelper")); - vm.expectRevert(Error.INVALID_SRC_CHAIN_ID.selector); + vm.expectRevert(Error.INVALID_CHAIN_ID.selector); wormholeSRImpl.receiveMessage(""); vm.clearMockedCalls(); diff --git a/test/unit/crosschain-data/extensions/CoreStateRegistry.t.sol b/test/unit/crosschain-data/extensions/CoreStateRegistry.t.sol index 892df6a39..e5abfbd6a 100644 --- a/test/unit/crosschain-data/extensions/CoreStateRegistry.t.sol +++ b/test/unit/crosschain-data/extensions/CoreStateRegistry.t.sol @@ -27,9 +27,14 @@ contract CoreStateRegistryTest is ProtocolActions { vm.prank(deployer); uint256[] memory amounts = new uint256[](1); + address[] memory bridgedTokens = new address[](1); /// @dev 1e18 after decimal corrections and bridge slippage would give the following value amounts[0] = 999_900_000_000_000_000; - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, amounts); + bridgedTokens[0] = getContract(AVAX, "DAI"); + + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, amounts + ); vm.prank(getContract(AVAX, "CoreStateRegistry")); MockERC20(getContract(AVAX, "DAI")).transfer(deployer, 999_900_000_000_000_000); @@ -60,15 +65,31 @@ contract CoreStateRegistryTest is ProtocolActions { finalAmounts[0] = 0; finalAmounts[1] = 419; + address[] memory bridgedTokens = new address[](2); + bridgedTokens[0] = getContract(AVAX, "DAI"); + bridgedTokens[1] = getContract(AVAX, "DAI"); + vm.prank(deployer); vm.expectRevert(Error.ZERO_AMOUNT.selector); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, finalAmounts + ); finalAmounts[0] = 419; finalAmounts[1] = 419; vm.prank(deployer); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, finalAmounts); + bridgedTokens[0] = getContract(AVAX, "WETH"); + vm.expectRevert(Error.INVALID_UPDATE_FINAL_TOKEN.selector); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, finalAmounts + ); + + vm.prank(deployer); + bridgedTokens[0] = getContract(AVAX, "DAI"); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, finalAmounts + ); vm.prank(getContract(AVAX, "CoreStateRegistry")); MockERC20(getContract(AVAX, "DAI")).transfer(deployer, 840); @@ -146,11 +167,13 @@ contract CoreStateRegistryTest is ProtocolActions { superformIds, uint256MemArr, uint256MemArr, + uint256MemArr, liqReqArr, bytes(""), new bool[](4), new bool[](4), receiverAddress, + receiverAddress, bytes("") ); /// @dev approves before call @@ -180,8 +203,16 @@ contract CoreStateRegistryTest is ProtocolActions { finalAmounts[2] = 419; finalAmounts[3] = 100; + address[] memory bridgedTokens = new address[](4); + bridgedTokens[0] = getContract(AVAX, "DAI"); + bridgedTokens[1] = getContract(AVAX, "DAI"); + bridgedTokens[2] = getContract(AVAX, "DAI"); + bridgedTokens[3] = getContract(AVAX, "DAI"); + vm.prank(deployer); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, finalAmounts + ); uint256 nativeValue = PaymentHelper(getContract(AVAX, "PaymentHelper")).estimateAckCost(1); vm.prank(deployer); @@ -233,9 +264,20 @@ contract CoreStateRegistryTest is ProtocolActions { vm.prank(deployer); uint256[] memory amounts = new uint256[](1); - amounts[0] = 999_900_000_000_000_000; - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, amounts); + + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(AVAX, "WETH"); + vm.expectRevert(Error.INVALID_UPDATE_FINAL_TOKEN.selector); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, amounts + ); + + vm.prank(deployer); + bridgedTokens[0] = getContract(AVAX, "DAI"); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, amounts + ); uint256 nativeValue = PaymentHelper(getContract(AVAX, "PaymentHelper")).estimateAckCost(1); @@ -262,11 +304,16 @@ contract CoreStateRegistryTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 2222; + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(AVAX, "DAI"); + vm.prank(deployer); vm.expectEmit(); // We emit the event we expect to see. emit ICoreStateRegistry.FailedXChainDeposits(1); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, amounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, amounts + ); } /// @dev test processPayload without updating multi vault deposit payload @@ -294,14 +341,21 @@ contract CoreStateRegistryTest is ProtocolActions { _successfulSingleDeposit(ambIds_); vm.selectFork(FORKS[AVAX]); + uint256[] memory amounts = new uint256[](1); amounts[0] = 0; + + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(AVAX, "DAI"); + vm.prank(deployer); SuperRegistry(getContract(AVAX, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); vm.prank(deployer); vm.expectRevert(Error.ZERO_AMOUNT.selector); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, amounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, amounts + ); vm.prank(deployer); SuperRegistry(getContract(AVAX, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 2); @@ -311,7 +365,9 @@ contract CoreStateRegistryTest is ProtocolActions { vm.prank(deployer); vm.expectRevert(Error.INSUFFICIENT_QUORUM.selector); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, amounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, amounts + ); } /// @dev test all revert cases with single vault withdraw payload update @@ -374,7 +430,7 @@ contract CoreStateRegistryTest is ProtocolActions { ETH, ETH, false, - deployer, + receiverAddress, uint256(ETH), /// @dev amount is 1 less than (actualWithdrawAmount * 0.9) => slippage > 10% => should revert ((actualWithdrawAmount * 9) / 10) - 1, @@ -403,18 +459,23 @@ contract CoreStateRegistryTest is ProtocolActions { _successfulMultiDeposit(ambIds_); uint256[] memory finalAmounts = new uint256[](1); + address[] memory bridgedTokens = new address[](1); vm.selectFork(FORKS[AVAX]); vm.prank(deployer); vm.expectRevert(Error.INSUFFICIENT_QUORUM.selector); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, finalAmounts + ); vm.prank(deployer); SuperRegistry(getContract(AVAX, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); vm.prank(deployer); vm.expectRevert(Error.DIFFERENT_PAYLOAD_UPDATE_AMOUNTS_LENGTH.selector); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, finalAmounts + ); } /// @dev test revert cases for duplicate proof bridge id @@ -424,11 +485,11 @@ contract CoreStateRegistryTest is ProtocolActions { ambIds_[1] = 2; ambIds_[2] = 3; ambIds_[3] = 2; - _failingMultiDeposit(ambIds_, Error.DUPLICATE_PROOF_BRIDGE_ID.selector); + _failingMultiDeposit(ambIds_, Error.INVALID_PROOF_BRIDGE_IDS.selector); ambIds_[2] = 2; ambIds_[3] = 3; - _failingMultiDeposit(ambIds_, Error.DUPLICATE_PROOF_BRIDGE_ID.selector); + _failingMultiDeposit(ambIds_, Error.INVALID_PROOF_BRIDGE_IDS.selector); } function test_processPayload_reverts() public { @@ -519,6 +580,11 @@ contract CoreStateRegistryTest is ProtocolActions { uint256[] memory finalAmounts = new uint256[](2); finalAmounts[0] = 420; finalAmounts[1] = 420; + + address[] memory bridgedTokens = new address[](2); + bridgedTokens[0] = getContract(AVAX, "DAI"); + bridgedTokens[1] = getContract(AVAX, "DAI"); + vm.selectFork(FORKS[AVAX]); vm.prank(deployer); SuperRegistry(getContract(AVAX, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); @@ -531,7 +597,9 @@ contract CoreStateRegistryTest is ProtocolActions { abi.encode(false) ); vm.prank(deployer); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, finalAmounts + ); vm.prank(deployer); vm.expectRevert(Error.PAYLOAD_ALREADY_PROCESSED.selector); @@ -550,6 +618,9 @@ contract CoreStateRegistryTest is ProtocolActions { uint256[] memory finalAmounts = new uint256[](1); finalAmounts[0] = 999_900_000_000_000_000; + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(AVAX, "DAI"); + vm.selectFork(FORKS[AVAX]); vm.prank(deployer); SuperRegistry(getContract(AVAX, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); @@ -563,7 +634,9 @@ contract CoreStateRegistryTest is ProtocolActions { ); vm.prank(deployer); - CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(payable(getContract(AVAX, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, finalAmounts + ); vm.prank(deployer); vm.expectRevert(Error.PAYLOAD_ALREADY_PROCESSED.selector); @@ -616,6 +689,7 @@ contract CoreStateRegistryTest is ProtocolActions { superformId, /// @dev 1e18 after decimal corrections and bridge slippage would give the following value 999_900_000_000_000_000, + 999_900_000_000_000_000, 100, LiqRequest( _buildLiqBridgeTxData(liqBridgeTxDataArgs, false), getContract(ETH, "DAI"), address(0), 1, AVAX, 0 @@ -624,6 +698,7 @@ contract CoreStateRegistryTest is ProtocolActions { false, false, receiverAddress, + receiverAddress, bytes("") ); /// @dev approves before call @@ -680,12 +755,14 @@ contract CoreStateRegistryTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest(bytes(""), getContract(ETH, "DAI"), address(0), 1, ETH, 0), bytes(""), false, false, receiverAddress, + receiverAddress, bytes("") ); @@ -764,11 +841,13 @@ contract CoreStateRegistryTest is ProtocolActions { superformIds, uint256MemArr, uint256MemArr, + uint256MemArr, liqReqArr, bytes(""), new bool[](2), new bool[](2), receiverAddress, + receiverAddress, bytes("") ); /// @dev approves before call @@ -822,12 +901,14 @@ contract CoreStateRegistryTest is ProtocolActions { MultiVaultSFData memory data = MultiVaultSFData( superformIds, amountArr, + amountArr, maxSlippages, liqReqArr, bytes(""), new bool[](2), new bool[](2), receiverAddress, + receiverAddress, bytes("") ); @@ -905,11 +986,13 @@ contract CoreStateRegistryTest is ProtocolActions { superformIds, uint256MemArr, uint256MemArr, + uint256MemArr, liqReqArr, bytes(""), new bool[](2), new bool[](2), receiverAddress, + receiverAddress, bytes("") ); /// @dev approves before call diff --git a/test/unit/crosschain-data/extensions/TwoStepsFormStateRegistry.t.sol b/test/unit/crosschain-data/extensions/TwoStepsFormStateRegistry.t.sol index 3ebc1bfeb..aa1127115 100644 --- a/test/unit/crosschain-data/extensions/TwoStepsFormStateRegistry.t.sol +++ b/test/unit/crosschain-data/extensions/TwoStepsFormStateRegistry.t.sol @@ -17,7 +17,7 @@ contract TimelockStateRegistryTest is ProtocolActions { function test_updateTxDataBranch() external { /// @dev mocks receive payload as a form vm.selectFork(FORKS[ETH]); - (address superform, uint256 superformId) = _legacySuperformPackWithShift(); + (address superform, uint256 superformId) = _legacySuperformPackWithShift(ETH); LiqBridgeTxDataArgs memory liqBridgeTxDataArgs = LiqBridgeTxDataArgs( 1, @@ -43,24 +43,24 @@ contract TimelockStateRegistryTest is ProtocolActions { vm.prank(getContract(AVAX, "SuperformRouter")); SuperPositions(getContract(AVAX, "SuperPositions")).updateTxHistory( - 1, DataLib.packTxInfo(1, 2, 0, 3, deployer, ETH) + 1, DataLib.packTxInfo(1, 2, 0, 3, deployer, ETH), receiverAddress ); vm.prank(getContract(AVAX, "SuperformRouter")); SuperPositions(getContract(AVAX, "SuperPositions")).updateTxHistory( - 1, DataLib.packTxInfo(1, 2, 0, 3, deployer, ETH) + 1, DataLib.packTxInfo(1, 2, 0, 3, deployer, ETH), receiverAddress ); vm.prank(superform); timelockStateRegistry.receivePayload( 0, - deployer, ETH, block.timestamp - 5 seconds, InitSingleVaultData( 1, superformId, 420, + 420, 0, LiqRequest( _buildLiqBridgeTxData(liqBridgeTxDataArgs, true), getContract(ETH, "DAI"), address(0), 1, ETH, 0 @@ -88,13 +88,13 @@ contract TimelockStateRegistryTest is ProtocolActions { vm.prank(superform); timelockStateRegistry.receivePayload( 0, - deployer, ETH, block.timestamp - 5 seconds, InitSingleVaultData( 1, superformId, 420, + 420, 1000, /// @dev note txData (2nd arg) is empty and token (3rd arg) is not address(0) to /// indicate keeper to create and update txData using finalizePayload() @@ -139,7 +139,7 @@ contract TimelockStateRegistryTest is ProtocolActions { function test_processPayloadMintPositionBranch() external { /// @dev mocks receive payload as a form vm.selectFork(FORKS[AVAX]); - (, uint256 superformId) = _legacySuperformPackWithShift(); + (, uint256 superformId) = _legacySuperformPackWithShift(AVAX); bytes memory _message = abi.encode( AMBMessage( @@ -150,7 +150,7 @@ contract TimelockStateRegistryTest is ProtocolActions { vm.prank(getContract(AVAX, "SuperformRouter")); SuperPositions(getContract(AVAX, "SuperPositions")).updateTxHistory( - 1, DataLib.packTxInfo(1, 2, 0, 3, deployer, ETH) + 1, DataLib.packTxInfo(1, 2, 0, 3, deployer, ETH), receiverAddress ); vm.prank(getContract(AVAX, "HyperlaneImplementation")); @@ -174,9 +174,11 @@ contract TimelockStateRegistryTest is ProtocolActions { timelockStateRegistry.dispatchPayload(address(420), ambIds, uint64(1), bytes(""), bytes("")); } - function _legacySuperformPackWithShift() internal view returns (address superform, uint256 superformId_) { - uint64 chainId_ = ETH; - + function _legacySuperformPackWithShift(uint64 chainId_) + internal + view + returns (address superform, uint256 superformId_) + { superform = getContract( chainId_, string.concat("DAI", "ERC4626TimelockMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[1])) diff --git a/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol b/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol index 9e9ad823d..764c88924 100644 --- a/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol +++ b/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol @@ -147,7 +147,9 @@ contract PayloadHelperMultiTest is ProtocolActions { _runMainStages(action, act, multiSuperformsData, singleSuperformsData, aV, vars, success); } - _checkDstPayloadLiqData(); + _checkDstPayloadLiqData( + getContract(FINAL_LIQ_DST_WITHDRAW[ARBI][0], UNDERLYING_TOKENS[actions[1].externalToken]) + ); } function _checkSrcPayload() internal { @@ -156,7 +158,7 @@ contract PayloadHelperMultiTest is ProtocolActions { address _PayloadHelper = contracts[CHAIN_0][bytes32(bytes("PayloadHelper"))]; IPayloadHelper helper = IPayloadHelper(_PayloadHelper); - (uint8 txType, uint8 callbackType, uint8 multi, address srcSender, uint64 srcChainId) = + (uint8 txType, uint8 callbackType, uint8 multi, address srcSender, address receiverAddress, uint64 srcChainId) = helper.decodePayloadHistory(1); assertEq(txType, 0); @@ -169,6 +171,8 @@ contract PayloadHelperMultiTest is ProtocolActions { assertEq(multi, 1); /// 0 for not multi vault assertEq(srcSender, users[0]); + + assertEq(receiverAddress, users[0]); } struct CheckDstPayloadInternalVars { @@ -197,25 +201,12 @@ contract PayloadHelperMultiTest is ProtocolActions { function _checkDstPayloadInit(CheckDstPayloadInitArgs memory args) internal { vm.selectFork(FORKS[DST_CHAINS[0]]); - CheckDstPayloadInternalVars memory v; - - ( - v.txType, - v.callbackType, - v.srcSender, - v.srcChainId, - v.amounts, - v.slippage, - , - , - , - v.receiverAddress, - v.srcPayloadId - ) = IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); - - v.extraDataGenerated = new bytes[](2); - v.extraDataGenerated[0] = abi.encode("500000"); - v.extraDataGenerated[1] = abi.encode("0"); + IPayloadHelper.DecodedDstPayload memory v = + IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); + + bytes[] memory extraDataGenerated = new bytes[](2); + extraDataGenerated[0] = abi.encode("500000"); + extraDataGenerated[1] = abi.encode("0"); assertEq(v.txType, 0); @@ -230,47 +221,47 @@ contract PayloadHelperMultiTest is ProtocolActions { for (uint256 i; i < v.amounts.length; ++i) { /// @dev ETH<>DAI swap on OP - v.daiAfterFirstSwap = (AMOUNTS[ARBI][0][i] * args.USDPerETHonOP_) / args.USDPerDAIonOP_; + uint256 daiAfterFirstSwap = (AMOUNTS[ARBI][0][i] * args.USDPerETHonOP_) / args.USDPerDAIonOP_; /// @dev DAI on OP <> DAI on ARBI - v.daiAfterSecondSwap = (v.daiAfterFirstSwap * args.USDPerDAIonOP_) / args.USDPerDAIonARBI_; + uint256 daiAfterSecondSwap = (daiAfterFirstSwap * args.USDPerDAIonOP_) / args.USDPerDAIonARBI_; /// @dev daiAfterSecondSwap doesn't include bridge slippage hence should be greater - assertLe(v.amounts[i], v.daiAfterSecondSwap); + assertLe(v.amounts[i], daiAfterSecondSwap); } - for (uint256 i = 0; i < v.slippage.length; ++i) { - assertEq(v.slippage[i], MAX_SLIPPAGE); + for (uint256 i = 0; i < v.slippages.length; ++i) { + assertEq(v.slippages[i], MAX_SLIPPAGE); } /// @notice: just asserting if fees are greater than 0 /// no way to write serious tests on forked testnet at this point. should come back to this later on. - (v.ambFees,) = IPaymentHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PaymentHelper"))]).estimateAMBFees( - AMBs, DST_CHAINS[0], abi.encode(1), v.extraDataGenerated + (uint256 ambFees,) = IPaymentHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PaymentHelper"))]).estimateAMBFees( + AMBs, DST_CHAINS[0], abi.encode(1), extraDataGenerated ); - assertGe(v.ambFees, 0); + assertGe(ambFees, 0); } struct CheckDstPayloadLiqDataInternalVars { uint8[] bridgeIds; - bytes[] txDatas; + bytes[] txData; address[] tokens; uint64[] liqDstChainIds; uint256[] amounts; uint256[] nativeAmounts; } - function _checkDstPayloadLiqData() internal { + function _checkDstPayloadLiqData(address externalToken_) internal { vm.selectFork(FORKS[DST_CHAINS[0]]); CheckDstPayloadLiqDataInternalVars memory v; - (v.bridgeIds, v.txDatas, v.tokens, v.liqDstChainIds, v.amounts, v.nativeAmounts) = IPayloadHelper( + (v.txData, v.tokens,, v.bridgeIds, v.liqDstChainIds, v.amounts, v.nativeAmounts) = IPayloadHelper( contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))] ).decodeCoreStateRegistryPayloadLiqData(2); assertEq(v.bridgeIds[0], 1); - assertGt(v.txDatas[0].length, 0); + assertGt(v.txData[0].length, 0); - assertEq(v.tokens[0], getContract(DST_CHAINS[0], UNDERLYING_TOKENS[TARGET_UNDERLYINGS[ARBI][1][0]])); + assertEq(v.tokens[0], externalToken_); assertEq(v.liqDstChainIds[0], FINAL_LIQ_DST_WITHDRAW[ARBI][0]); @@ -282,9 +273,7 @@ contract PayloadHelperMultiTest is ProtocolActions { function _checkDstPayloadReturn() internal { vm.selectFork(FORKS[CHAIN_0]); - CheckDstPayloadInternalVars memory v; - - (v.txType, v.callbackType, v.srcSender, v.srcChainId, v.amounts, v.slippage,, v.hasDstSwaps,,, v.srcPayloadId) = + IPayloadHelper.DecodedDstPayload memory v = IPayloadHelper(contracts[CHAIN_0][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); assertEq(v.txType, 0); @@ -296,9 +285,9 @@ contract PayloadHelperMultiTest is ProtocolActions { /// chain id of polygon is 42161 assertEq(v.srcPayloadId, 1); - for (uint256 i = 0; i < v.slippage.length; ++i) { + for (uint256 i = 0; i < v.slippages.length; ++i) { assertLe(v.amounts[i], AMOUNTS[ARBI][0][i]); - assertEq(v.slippage[i], MAX_SLIPPAGE); + assertEq(v.slippages[i], MAX_SLIPPAGE); } } } diff --git a/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol b/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol index 658868f06..a4ecbfc62 100644 --- a/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol +++ b/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol @@ -134,7 +134,9 @@ contract PayloadHelperSingleTest is ProtocolActions { _runMainStages(action, act, multiSuperformsData, singleSuperformsData, aV, vars, success); } - _checkDstPayloadLiqData(); + _checkDstPayloadLiqData( + getContract(FINAL_LIQ_DST_WITHDRAW[POLY][0], UNDERLYING_TOKENS[actions[1].externalToken]) + ); } function test_decodePayloadHistory_InvalidPayloadId() public { @@ -166,26 +168,10 @@ contract PayloadHelperSingleTest is ProtocolActions { PayloadHelper(getContract(ETH, "PayloadHelper")).decodeCoreStateRegistryPayload(1); } - struct CheckDstPayloadInternalVars { - bytes[] extraDataGenerated; - uint256 ambFees; - uint8 txType; - uint8 callbackType; - address srcSender; - uint64 srcChainId; - uint256[] amounts; - uint256[] slippage; - uint256[] superformIds; - bool[] hasDstSwaps; - bytes extraFormData; - address receiverAddress; - uint256 srcPayloadId; - } - function _checkSrcPayload() internal { vm.selectFork(FORKS[CHAIN_0]); - (uint8 txType, uint8 callbackType, uint8 multi, address srcSender, uint64 srcChainId) = + (uint8 txType, uint8 callbackType, uint8 multi, address srcSender, address receiverAddress, uint64 srcChainId) = IPayloadHelper(contracts[CHAIN_0][bytes32(bytes("PayloadHelper"))]).decodePayloadHistory(1); /// @dev 0 for deposit @@ -200,32 +186,23 @@ contract PayloadHelperSingleTest is ProtocolActions { /// @dev 0 for not multi vault assertEq(multi, 0); assertEq(srcSender, users[0]); + + assertEq(receiverAddress, users[0]); } function _checkDstPayloadInit() internal { vm.selectFork(FORKS[DST_CHAINS[0]]); - CheckDstPayloadInternalVars memory v; vm.expectRevert(Error.INVALID_PAYLOAD_ID.selector); IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(3); - ( - v.txType, - v.callbackType, - v.srcSender, - v.srcChainId, - v.amounts, - v.slippage, - , - , - , - v.receiverAddress, - v.srcPayloadId - ) = IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); + IPayloadHelper.DecodedDstPayload memory v = + IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).getDstPayloadProof(1); - v.extraDataGenerated = new bytes[](2); - v.extraDataGenerated[0] = abi.encode("500000"); - v.extraDataGenerated[1] = abi.encode("0"); + + bytes[] memory extraDataGenerated = new bytes[](2); + extraDataGenerated[0] = abi.encode("500000"); + extraDataGenerated[1] = abi.encode("0"); /// @dev 0 for deposit assertEq(v.txType, 0); @@ -240,28 +217,26 @@ contract PayloadHelperSingleTest is ProtocolActions { assertEq(v.receiverAddress, users[0]); - for (uint256 i = 0; i < v.slippage.length; ++i) { + for (uint256 i = 0; i < v.slippages.length; ++i) { console.log("v.amounts[i]: %s", v.amounts[i]); console.log("AMOUNTS[POLY][0][i]: %s", AMOUNTS[POLY][0][i]); /// @dev TODO: fix this assertion considering exchange rates // assertLe(v.amounts[i], AMOUNTS[POLY][0][i]); - assertEq(v.slippage[i], MAX_SLIPPAGE); + assertEq(v.slippages[i], MAX_SLIPPAGE); } /// @notice: just asserting if fees are greater than 0 /// FIXME no way to write serious tests on forked testnet at this point. should come back to this later on. - (v.ambFees,) = IPaymentHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PaymentHelper"))]).estimateAMBFees( - AMBs, DST_CHAINS[0], abi.encode(1), v.extraDataGenerated + (uint256 ambFees,) = IPaymentHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PaymentHelper"))]).estimateAMBFees( + AMBs, DST_CHAINS[0], abi.encode(1), extraDataGenerated ); - assertGe(v.ambFees, 0); + assertGe(ambFees, 0); } function _checkDstPayloadReturn() internal { vm.selectFork(FORKS[CHAIN_0]); - CheckDstPayloadInternalVars memory v; - - (v.txType, v.callbackType, v.srcSender, v.srcChainId, v.amounts, v.slippage,, v.hasDstSwaps,,, v.srcPayloadId) = + IPayloadHelper.DecodedDstPayload memory v = IPayloadHelper(contracts[CHAIN_0][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); /// @dev 0 for deposit @@ -274,9 +249,9 @@ contract PayloadHelperSingleTest is ProtocolActions { assertEq(v.srcChainId, 137); assertEq(v.srcPayloadId, 1); - for (uint256 i = 0; i < v.slippage.length; ++i) { + for (uint256 i = 0; i < v.slippages.length; ++i) { assertLe(v.amounts[i], AMOUNTS[POLY][0][i]); - assertEq(v.slippage[i], MAX_SLIPPAGE); + assertEq(v.slippages[i], MAX_SLIPPAGE); } } @@ -289,11 +264,11 @@ contract PayloadHelperSingleTest is ProtocolActions { uint256[] nativeAmounts; } - function _checkDstPayloadLiqData() internal { + function _checkDstPayloadLiqData(address externalToken_) internal { vm.selectFork(FORKS[DST_CHAINS[0]]); CheckDstPayloadLiqDataInternalVars memory v; - (v.bridgeIds, v.txDatas, v.tokens, v.liqDstChainIds, v.amounts, v.nativeAmounts) = IPayloadHelper( + (v.txDatas, v.tokens,, v.bridgeIds, v.liqDstChainIds, v.amounts, v.nativeAmounts) = IPayloadHelper( contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))] ).decodeCoreStateRegistryPayloadLiqData(2); @@ -301,7 +276,7 @@ contract PayloadHelperSingleTest is ProtocolActions { assertGt(v.txDatas[0].length, 0); - assertEq(v.tokens[0], getContract(DST_CHAINS[0], UNDERLYING_TOKENS[TARGET_UNDERLYINGS[POLY][0][0]])); + assertEq(v.tokens[0], externalToken_); assertEq(v.liqDstChainIds[0], FINAL_LIQ_DST_WITHDRAW[POLY][0]); diff --git a/test/unit/crosschain-liquidity/DstSwapper.t.sol b/test/unit/crosschain-liquidity/DstSwapper.t.sol index 2fdaf3d78..e29d31507 100644 --- a/test/unit/crosschain-liquidity/DstSwapper.t.sol +++ b/test/unit/crosschain-liquidity/DstSwapper.t.sol @@ -39,7 +39,7 @@ contract DstSwapperTest is ProtocolActions { _buildLiqBridgeTxDataDstSwap(1, native, getContract(ETH, "DAI"), dstSwapper, ETH, 1e18, 0); vm.expectRevert(Error.INVALID_INTERIM_TOKEN.selector); - DstSwapper(dstSwapper).processTx(1, 0, 1, txData); + DstSwapper(dstSwapper).processTx(1, 1, txData); } else { revert(); } @@ -63,21 +63,17 @@ contract DstSwapperTest is ProtocolActions { bytes memory txData = _buildLiqBridgeTxDataDstSwap(1, native, getContract(ETH, "DAI"), dstSwapper, ETH, 1e18, 0); vm.expectRevert(Error.INVALID_PAYLOAD_ID.selector); - DstSwapper(dstSwapper).processTx(1000, 0, 1, txData); + DstSwapper(dstSwapper).processTx(1000, 1, txData); - DstSwapper(dstSwapper).processTx(1, 0, 1, txData); - - /// @dev try with a non-existent index - vm.expectRevert(Error.INVALID_INDEX.selector); - DstSwapper(dstSwapper).processTx(1, 420, 1, txData); + DstSwapper(dstSwapper).processTx(1, 1, txData); /// @dev retry the same payload id and indices vm.expectRevert(Error.DST_SWAP_ALREADY_PROCESSED.selector); - DstSwapper(dstSwapper).processTx(1, 0, 1, txData); + DstSwapper(dstSwapper).processTx(1, 1, txData); /// @dev no funds in multi-tx processor at this point; should revert - vm.expectRevert(abi.encodeWithSelector(Error.FAILED_TO_EXECUTE_TXDATA.selector, native)); - DstSwapper(dstSwapper).processTx(2, 0, 1, txData); + vm.expectRevert(Error.INSUFFICIENT_BALANCE.selector); + DstSwapper(dstSwapper).processTx(2, 1, txData); } else { revert(); } @@ -94,8 +90,89 @@ contract DstSwapperTest is ProtocolActions { bytes memory txData = _buildLiqBridgeTxDataDstSwap(1, getContract(ETH, "WETH"), getContract(ETH, "DAI"), dstSwapper, ETH, 1e18, 0); /// @dev no funds in multi-tx processor at this point; should revert - vm.expectRevert(abi.encodeWithSelector(Error.FAILED_TO_EXECUTE_TXDATA.selector, getContract(ETH, "WETH"))); - DstSwapper(dstSwapper).processTx(1, 0, 1, txData); + vm.expectRevert(Error.INSUFFICIENT_BALANCE.selector); + DstSwapper(dstSwapper).processTx(1, 1, txData); + } + + function test_partial_multi_vault_dstSwap() public { + address payable dstSwapper = payable(getContract(ETH, "DstSwapper")); + address payable coreStateRegistry = payable(getContract(ETH, "CoreStateRegistry")); + + vm.selectFork(FORKS[ETH]); + + /// simulate an existing payload in csr + address superform = getContract(ETH, string.concat("DAI", "VaultMock", "Superform", "1")); + uint256 superformId = DataLib.packSuperform(superform, 1, ETH); + + uint256[] memory superformIds = new uint256[](2); + superformIds[0] = superformId; + superformIds[1] = superformId; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1e18; + amounts[1] = 1e18; + + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 1e18; + outputAmounts[1] = 1e18; + + uint256[] memory slippages = new uint256[](2); + slippages[0] = 1000; + slippages[1] = 1000; + + LiqRequest memory liq; + liq.interimToken = getContract(ETH, "WETH"); + + LiqRequest[] memory liqs = new LiqRequest[](2); + liqs[1] = liq; + + bool[] memory hasDstSwaps = new bool[](2); + hasDstSwaps[1] = true; + + vm.prank(getContract(ETH, "LayerzeroImplementation")); + CoreStateRegistry(coreStateRegistry).receivePayload( + 137, + abi.encode( + AMBMessage( + DataLib.packTxInfo(1, 1, 1, 1, address(0), 1), + abi.encode( + new uint8[](0), + abi.encode( + InitMultiVaultData( + 1, + superformIds, + amounts, + outputAmounts, + new uint256[](2), + liqs, + hasDstSwaps, + new bool[](2), + receiverAddress, + bytes("") + ) + ) + ) + ) + ) + ); + + vm.startPrank(deployer); + deal(getContract(ETH, "WETH"), dstSwapper, 1e18); + + bytes memory txData = _buildLiqBridgeTxDataDstSwap( + 1, getContract(ETH, "WETH"), getContract(ETH, "DAI"), dstSwapper, ETH, 1e17, 1001 + ); + + uint256[] memory indices = new uint256[](1); + indices[0] = 1; + + uint8[] memory bridgeIds = new uint8[](1); + bridgeIds[0] = 1; + + bytes[] memory txDataArr = new bytes[](1); + txDataArr[0] = txData; + + DstSwapper(dstSwapper).batchProcessTx(1, indices, bridgeIds, txDataArr); } function test_single_non_native_updateFailedTx() public { @@ -113,36 +190,38 @@ contract DstSwapperTest is ProtocolActions { deal(weth, dstSwapper, 1e18); vm.expectRevert(Error.ZERO_AMOUNT.selector); - DstSwapper(dstSwapper).updateFailedTx(1, 0, weth, 0); + DstSwapper(dstSwapper).updateFailedTx(1, weth, 0); vm.expectRevert(Error.INSUFFICIENT_BALANCE.selector); - DstSwapper(dstSwapper).updateFailedTx(1, 0, weth, 3e18); + DstSwapper(dstSwapper).updateFailedTx(1, weth, 3e18); - DstSwapper(dstSwapper).updateFailedTx(1, 0, weth, 1e18); + DstSwapper(dstSwapper).updateFailedTx(1, weth, 1e18); vm.expectRevert(Error.FAILED_DST_SWAP_ALREADY_UPDATED.selector); - DstSwapper(dstSwapper).updateFailedTx(1, 0, weth, 1e18); + DstSwapper(dstSwapper).updateFailedTx(1, weth, 1e18); vm.expectRevert(Error.INVALID_PAYLOAD_TYPE.selector); - DstSwapper(dstSwapper).updateFailedTx(2, 0, weth, 1e18); + DstSwapper(dstSwapper).updateFailedTx(2, weth, 1e18); vm.expectRevert(Error.INVALID_INTERIM_TOKEN.selector); - DstSwapper(dstSwapper).updateFailedTx(3, 0, weth, 1e18); + DstSwapper(dstSwapper).updateFailedTx(3, weth, 1e18); /// @dev set quorum to 0 for simplicity in testing setup SuperRegistry(getContract(OP, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); uint256[] memory finalAmounts = new uint256[](1); + address[] memory bridgedTokens = new address[](1); finalAmounts[0] = 2e18; + bridgedTokens[0] = getContract(OP, "WETH"); vm.expectRevert(Error.INVALID_DST_SWAP_AMOUNT.selector); - CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, bridgedTokens, finalAmounts); finalAmounts[0] = 1e18; - CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, bridgedTokens, finalAmounts); vm.expectRevert(Error.INVALID_PAYLOAD_STATUS.selector); - DstSwapper(dstSwapper).updateFailedTx(1, 0, weth, 1e18); + DstSwapper(dstSwapper).updateFailedTx(1, weth, 1e18); vm.stopPrank(); AMBs = [2, 3]; @@ -189,7 +268,7 @@ contract DstSwapperTest is ProtocolActions { vm.startPrank(deployer); deal(weth, dstSwapper, 1e18); - DstSwapper(dstSwapper).updateFailedTx(1, 0, weth, 1e18); + DstSwapper(dstSwapper).updateFailedTx(1, weth, 1e18); vm.stopPrank(); @@ -210,7 +289,7 @@ contract DstSwapperTest is ProtocolActions { vm.startPrank(deployer); deal(dstSwapper, 1e18); - DstSwapper(dstSwapper).updateFailedTx(2, 0, native, 1e18); + DstSwapper(dstSwapper).updateFailedTx(2, native, 1e18); vm.stopPrank(); @@ -232,18 +311,22 @@ contract DstSwapperTest is ProtocolActions { vm.startPrank(deployer); vm.expectRevert(Error.INSUFFICIENT_BALANCE.selector); - DstSwapper(dstSwapper).updateFailedTx(1, 0, native, 1e18); + DstSwapper(dstSwapper).updateFailedTx(1, native, 1e18); deal(dstSwapper, 1e18); - DstSwapper(dstSwapper).updateFailedTx(1, 0, native, 1e18); + DstSwapper(dstSwapper).updateFailedTx(1, native, 1e18); /// @dev set quorum to 0 for simplicity in testing setup SuperRegistry(getContract(OP, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); uint256[] memory finalAmounts = new uint256[](1); finalAmounts[0] = 1e18; - CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, finalAmounts); + + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(OP, "WETH"); + + CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, bridgedTokens, finalAmounts); vm.stopPrank(); @@ -302,6 +385,18 @@ contract DstSwapperTest is ProtocolActions { amounts[1] = 1e18; uint256[] memory indices = new uint256[](2); + indices[0] = 2; + indices[1] = 1; + + vm.expectRevert(Error.INDEX_OUT_OF_BOUNDS.selector); + DstSwapper(dstSwapper).batchUpdateFailedTx(1, indices, interimTokens, amounts); + + indices[0] = 1; + indices[1] = 1; + + vm.expectRevert(Error.DUPLICATE_INDEX.selector); + DstSwapper(dstSwapper).batchUpdateFailedTx(1, indices, interimTokens, amounts); + indices[0] = 0; indices[1] = 1; @@ -313,7 +408,11 @@ contract DstSwapperTest is ProtocolActions { /// @dev set quorum to 0 for simplicity in testing setup SuperRegistry(getContract(OP, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); - CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, amounts); + address[] memory bridgedTokens = new address[](2); + bridgedTokens[0] = getContract(OP, "WETH"); + bridgedTokens[1] = getContract(OP, "WETH"); + + CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, bridgedTokens, amounts); vm.stopPrank(); AMBs = [2, 3]; @@ -378,7 +477,11 @@ contract DstSwapperTest is ProtocolActions { /// @dev set quorum to 0 for simplicity in testing setup SuperRegistry(getContract(OP, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); - CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, amounts); + address[] memory bridgedTokens = new address[](2); + bridgedTokens[0] = getContract(OP, "WETH"); + bridgedTokens[1] = getContract(OP, "WETH"); + + CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, bridgedTokens, amounts); vm.stopPrank(); @@ -449,8 +552,14 @@ contract DstSwapperTest is ProtocolActions { (bool success,) = payable(dstSwapper).call{ value: 2e18 }(""); if (!success) revert(); - vm.expectRevert(Error.INVALID_INDEX.selector); + vm.expectRevert(Error.INDEX_OUT_OF_BOUNDS.selector); + DstSwapper(dstSwapper).batchProcessTx(1, indices, bridgeId, txData); + + vm.expectRevert(Error.DUPLICATE_INDEX.selector); + indices[0] = 1; + indices[1] = 1; DstSwapper(dstSwapper).batchProcessTx(1, indices, bridgeId, txData); + indices[0] = 0; indices[1] = 1; DstSwapper(dstSwapper).batchProcessTx(1, indices, bridgeId, txData); @@ -466,7 +575,7 @@ contract DstSwapperTest is ProtocolActions { DstSwapper(dstSwapper).batchProcessTx(1, indices, bridgeId, txData); /// @dev no funds in multi-tx processor at this point; should revert - vm.expectRevert(abi.encodeWithSelector(Error.FAILED_TO_EXECUTE_TXDATA.selector, native)); + vm.expectRevert(Error.INSUFFICIENT_BALANCE.selector); DstSwapper(dstSwapper).batchProcessTx(2, indices, bridgeId, txData); } @@ -512,7 +621,7 @@ contract DstSwapperTest is ProtocolActions { DstSwapper(dstSwapper).batchProcessTx(1, indices, bridgeId, txData); } - function test_failed_INVALID_SWAP_OUTPUT() public { + function test_failed_ZERO_AMOUNT() public { address payable dstSwapper = payable(getContract(ETH, "DstSwapper")); address payable coreStateRegistry = payable(getContract(ETH, "CoreStateRegistry")); @@ -524,8 +633,8 @@ contract DstSwapperTest is ProtocolActions { bytes memory txData = _buildLiqBridgeTxDataDstSwap(1, getContract(ETH, "WETH"), getContract(ETH, "DAI"), dstSwapper, ETH, 0, 0); /// @dev txData with amount 0 should revert - vm.expectRevert(Error.INVALID_SWAP_OUTPUT.selector); - DstSwapper(dstSwapper).processTx(1, 0, 1, txData); + vm.expectRevert(Error.ZERO_AMOUNT.selector); + DstSwapper(dstSwapper).processTx(1, 1, txData); } function test_failed_MAX_SLIPPAGE_INVARIANT_BROKEN() public { @@ -555,6 +664,7 @@ contract DstSwapperTest is ProtocolActions { 1, superformId, 1_798_823_082_965_464_723_525, + 1_798_823_082_965_464_723_525, 0, liq, true, @@ -576,7 +686,7 @@ contract DstSwapperTest is ProtocolActions { ); /// @dev txData with amount 0 should revert vm.expectRevert(Error.SLIPPAGE_OUT_OF_BOUNDS.selector); - DstSwapper(dstSwapper).processTx(1, 0, 1, txData); + DstSwapper(dstSwapper).processTx(1, 1, txData); } function test_processFailedTx_invalidUserCall() public { @@ -603,10 +713,13 @@ contract DstSwapperTest is ProtocolActions { SuperRegistry(getContract(OP, "SuperRegistry")).setRequiredMessagingQuorum(ETH, 0); uint256[] memory finalAmounts = new uint256[](1); - finalAmounts[0] = 2e18; + + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(OP, "WETH"); + vm.expectRevert(Error.INVALID_DST_SWAPPER_FAILED_SWAP.selector); - CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, finalAmounts); + CoreStateRegistry(coreStateRegistry).updateDepositPayload(1, bridgedTokens, finalAmounts); } function _simulateSingleVaultExistingPayload( @@ -639,7 +752,9 @@ contract DstSwapperTest is ProtocolActions { abi.encode( new uint8[](0), abi.encode( - InitSingleVaultData(1, superformId, amount, 0, liq, true, false, receiverAddress, bytes("")) + InitSingleVaultData( + 1, superformId, amount, amount, 0, liq, true, false, receiverAddress, bytes("") + ) ) ) ) @@ -676,7 +791,9 @@ contract DstSwapperTest is ProtocolActions { ), abi.encode( new uint8[](0), - abi.encode(InitSingleVaultData(1, superformId, 1e18, 1000, liq, true, false, users[0], bytes(""))) + abi.encode( + InitSingleVaultData(1, superformId, 1e18, 1e18, 1000, liq, true, false, users[0], bytes("")) + ) ) ) ); @@ -709,6 +826,10 @@ contract DstSwapperTest is ProtocolActions { amounts[0] = 1e18; amounts[1] = 1e18; + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 1e18; + outputAmounts[1] = 1e18; + bool[] memory hasDstSwaps = new bool[](2); hasDstSwaps[0] = true; hasDstSwaps[1] = true; @@ -717,6 +838,63 @@ contract DstSwapperTest is ProtocolActions { amounts[0] = 1000; amounts[1] = 1000; + LiqRequest[] memory liq = new LiqRequest[](2); + liq[0] = LiqRequest("", getContract(OP, "DAI"), interimToken_, 1, OP, 0); + liq[1] = LiqRequest("", getContract(OP, "DAI"), interimToken_, 1, OP, 0); + + InitMultiVaultData memory initMultiVaultData = InitMultiVaultData( + 1, superformIds, amounts, outputAmounts, maxSlippages, liq, hasDstSwaps, new bool[](2), users[0], bytes("") + ); + + uint8[] memory ambIds = new uint8[](1); + ambIds[0] = 1; + + bytes memory encodedData = abi.encode( + AMBMessage( + DataLib.packTxInfo(uint8(TransactionType.DEPOSIT), uint8(CallbackType.INIT), uint8(1), 1, users[0], ETH), + abi.encode(ambIds, abi.encode(initMultiVaultData)) + ) + ); + + CoreStateRegistry(coreStateRegistry).receivePayload(ETH, encodedData); + } + + function _simulatePartialMultiVaultExistingPayloadOnOP( + address payable coreStateRegistry, + address interimToken_ + ) + internal + returns (uint256[] memory superformIds) + { + /// simulate an existing payload in csr + address superform = getContract(OP, string.concat("WETH", "VaultMock", "Superform", "1")); + uint256 superformId1 = DataLib.packSuperform(superform, 1, OP); + uint256 superformId2 = DataLib.packSuperform( + getContract(OP, string.concat("WETH", "VaultMockRevertDeposit", "Superform", "1")), 1, OP + ); + + vm.prank(getContract(OP, "LayerzeroImplementation")); + + superformIds = new uint256[](2); + superformIds[0] = superformId1; + superformIds[1] = superformId2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1e18; + amounts[1] = 1e18; + + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 1e18; + outputAmounts[1] = 1e18; + + bool[] memory hasDstSwaps = new bool[](2); + hasDstSwaps[0] = false; + hasDstSwaps[1] = true; + + uint256[] memory maxSlippages = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 1000; + LiqRequest[] memory liq = new LiqRequest[](2); liq[0] = LiqRequest("", getContract(OP, "DAI"), interimToken_, 1, OP, 0); liq[1] = LiqRequest("", getContract(OP, "DAI"), interimToken_, 1, OP, 0); @@ -734,6 +912,7 @@ contract DstSwapperTest is ProtocolActions { 1, superformIds, amounts, + outputAmounts, maxSlippages, liq, hasDstSwaps, @@ -767,6 +946,10 @@ contract DstSwapperTest is ProtocolActions { amounts[0] = amount; amounts[1] = amount; + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = amount; + outputAmounts[1] = amount; + bool[] memory hasDstSwaps = new bool[](2); hasDstSwaps[0] = true; hasDstSwaps[1] = true; @@ -778,30 +961,28 @@ contract DstSwapperTest is ProtocolActions { uint8[] memory ambIds_ = new uint8[](1); ambIds_[0] = 1; - vm.prank(getContract(ETH, "LayerzeroImplementation")); - CoreStateRegistry(coreStateRegistry).receivePayload( - POLY, - abi.encode( - AMBMessage( - DataLib.packTxInfo(1, 0, 1, 1, address(420), uint64(137)), - abi.encode( - ambIds_, - abi.encode( - InitMultiVaultData( - 1, - superformIds, - amounts, - new uint256[](2), - liq, - hasDstSwaps, - new bool[](2), - receiverAddress, - bytes("") - ) - ) - ) - ) + + InitMultiVaultData memory initMultiVaultData = InitMultiVaultData( + 1, + superformIds, + amounts, + outputAmounts, + new uint256[](2), + liq, + hasDstSwaps, + new bool[](2), + receiverAddress, + bytes("") + ); + + bytes memory encodedData = abi.encode( + AMBMessage( + DataLib.packTxInfo(1, 0, 1, 1, address(420), uint64(137)), + abi.encode(ambIds_, abi.encode(initMultiVaultData)) ) ); + + vm.prank(getContract(ETH, "LayerzeroImplementation")); + CoreStateRegistry(coreStateRegistry).receivePayload(POLY, encodedData); } } diff --git a/test/unit/crosschain-liquidity/socket/SocketOneInchValidator.t.sol b/test/unit/crosschain-liquidity/socket/SocketOneInchValidator.t.sol index 313981498..efa6f73f5 100644 --- a/test/unit/crosschain-liquidity/socket/SocketOneInchValidator.t.sol +++ b/test/unit/crosschain-liquidity/socket/SocketOneInchValidator.t.sol @@ -130,7 +130,7 @@ contract SocketOneInchValidatorTest is ProtocolActions { } function test_validateTxData_reverts() public { - vm.expectRevert(Error.INVALID_ACTION.selector); + vm.expectRevert(Error.INVALID_TXDATA_CHAIN_ID.selector); SocketOneInchValidator(getContract(BSC, "SocketOneInchValidator")).validateTxData( IBridgeValidator.ValidateTxDataArgs( _buildDummyTxDataUnitTests( diff --git a/test/unit/emergency/EmergencyQueue.t.sol b/test/unit/emergency/EmergencyQueue.t.sol index 9124f8934..743e67389 100644 --- a/test/unit/emergency/EmergencyQueue.t.sol +++ b/test/unit/emergency/EmergencyQueue.t.sol @@ -55,15 +55,15 @@ contract EmergencyQueueTest is ProtocolActions { InitSingleVaultData( 1, _getTestSuperformId(), - 1e18, // good hacker tries to take only 1e18 + 1e18, // good hacker tries to take only 1e18, + 1e18, 1000, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), false, false, mrimperfect, "" - ), - mrperfect + ) ); } @@ -86,15 +86,15 @@ contract EmergencyQueueTest is ProtocolActions { InitSingleVaultData( 1, superformId, - 1e18, // good hacker tries to take only 1e18 + 1e18, // good hacker tries to take only 1e18, + 1e18, 1000, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), false, false, mrimperfect, "" - ), - mrperfect + ) ); } @@ -118,14 +118,14 @@ contract EmergencyQueueTest is ProtocolActions { 1, superformId, 1e18, // good hacker tries to take only 1e18 + 1e18, 1000, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), false, false, mrimperfect, "" - ), - mrperfect + ) ); } @@ -324,7 +324,7 @@ contract EmergencyQueueTest is ProtocolActions { assertEq(balanceBefore + 1e18, balanceAfter); } - function test_emergencyWithdraw() public { + function test_emergencyWithdraw_INSUFFICIENT_BALANCE() public { /// user deposits successfully to a form _successfulDepositXChain(1, "VaultMock", 0, mrperfect, false); @@ -340,7 +340,26 @@ contract EmergencyQueueTest is ProtocolActions { vm.prank(emergencyQueue); vm.expectRevert(Error.INSUFFICIENT_BALANCE.selector); - IBaseForm(superform).emergencyWithdraw(address(0), address(0), 10e20); + IBaseForm(superform).emergencyWithdraw(address(0x1), 10e20); + } + + function test_emergencyWithdraw_ZEROADDRESS() public { + /// user deposits successfully to a form + _successfulDepositXChain(1, "VaultMock", 0, mrperfect, false); + + /// processing the queued withdrawal and assert + vm.selectFork(FORKS[ARBI]); + + /// @dev deployer has emergency admin role + address emergencyQueue = getContract(ARBI, "EmergencyQueue"); + + address superform = getContract( + ARBI, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ); + + vm.prank(emergencyQueue); + vm.expectRevert(Error.ZERO_ADDRESS.selector); + IBaseForm(superform).emergencyWithdraw(address(0), 10e20); } function test_emergencyQueueProcessingXChainMultiVault() public { @@ -467,12 +486,14 @@ contract EmergencyQueueTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( _getTestSuperformId(), 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, mrimperfect, + mrimperfect, "" ); @@ -500,6 +521,10 @@ contract EmergencyQueueTest is ProtocolActions { amounts[0] = 0.9e18; amounts[1] = 0.9e18; + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 0.9e18; + outputAmounts[1] = 0.9e18; + uint256[] memory maxSlippages = new uint256[](2); maxSlippages[0] = 100; maxSlippages[1] = 100; @@ -509,7 +534,17 @@ contract EmergencyQueueTest is ProtocolActions { liqRequests[1] = liqRequests[0]; MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqRequests, "", new bool[](2), new bool[](2), mrimperfect, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqRequests, + "", + new bool[](2), + new bool[](2), + mrimperfect, + mrimperfect, + "" ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -538,12 +573,14 @@ contract EmergencyQueueTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 1000, LiqRequest("", address(0), address(0), 1, ETH, 0), "", false, false, mrimperfect, + mrimperfect, "" ); @@ -613,6 +650,10 @@ contract EmergencyQueueTest is ProtocolActions { amounts[0] = 0.9e18; amounts[1] = 0.9e18; + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 0.9e18; + outputAmounts[1] = 0.9e18; + uint256[] memory slippages = new uint256[](2); slippages[0] = 100; slippages[1] = 100; @@ -622,7 +663,17 @@ contract EmergencyQueueTest is ProtocolActions { liqRequests[1] = liqRequests[0]; MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, slippages, liqRequests, "", new bool[](2), new bool[](2), mrimperfect, "" + superformIds, + amounts, + outputAmounts, + slippages, + liqRequests, + "", + new bool[](2), + new bool[](2), + mrimperfect, + mrimperfect, + "" ); uint8[] memory ambIds = new uint8[](2); @@ -704,7 +755,7 @@ contract EmergencyQueueTest is ProtocolActions { uint256 superformId = _getTestSuperformId(); SingleVaultSFData memory data = SingleVaultSFData( - superformId, 2e18, 100, LiqRequest("", dai, address(0), 1, 1, 0), "", false, false, mrperfect, "" + superformId, 2e18, 2e18, 100, LiqRequest("", dai, address(0), 1, 1, 0), "", false, false, mrperfect, mrperfect, "" ); SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq(data); @@ -760,6 +811,7 @@ contract EmergencyQueueTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 2e18, + 2e18, 1000, LiqRequest( _buildLiqBridgeTxData(liqBridgeTxDataArgs, false), getContract(ETH, "DAI"), address(0), 1, ARBI, 0 @@ -768,6 +820,7 @@ contract EmergencyQueueTest is ProtocolActions { false, false, mrimperfect, + mrimperfect, "" ); @@ -808,7 +861,12 @@ contract EmergencyQueueTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 2e18; - CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload(payloadId, amounts); + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(ARBI, "DAI"); + + CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload( + payloadId, bridgedTokens, amounts + ); uint256 nativeAmount = PaymentHelper(getContract(ARBI, "PaymentHelper")).estimateAckCost(1); diff --git a/test/unit/libraries/ArrayCastLib.t.sol b/test/unit/libraries/ArrayCastLib.t.sol index ed6de2662..08d21e299 100644 --- a/test/unit/libraries/ArrayCastLib.t.sol +++ b/test/unit/libraries/ArrayCastLib.t.sol @@ -46,9 +46,12 @@ contract ArrayCastLibTest is Test { function test_castToMultiVaultData() external { InitSingleVaultData memory data = InitSingleVaultData( - 1, 1, 1e18, 100, LiqRequest(bytes(""), address(0), address(0), 1, 1, 0), false, false, address(0), "" + 1, 1, 1e18, 1e18, 100, LiqRequest(bytes(""), address(0), address(0), 1, 1, 0), true, true, address(0), "" ); InitMultiVaultData memory castedValue = arrayCastLib.castToMultiVaultData(data); assertEq(castedValue.superformIds.length, 1); + + assertEq(castedValue.hasDstSwaps[0], data.hasDstSwap); + assertEq(castedValue.retain4626s[0], data.retain4626); } } diff --git a/test/unit/payments/PayMaster.t.sol b/test/unit/payments/PayMaster.t.sol index 7960f5b9a..ddcc92137 100644 --- a/test/unit/payments/PayMaster.t.sol +++ b/test/unit/payments/PayMaster.t.sol @@ -90,22 +90,47 @@ contract PayMasterTest is ProtocolActions { /// @dev admin tries withdraw more than balance (check if handled gracefully) vm.expectRevert(Error.FAILED_TO_SEND_NATIVE.selector); - PayMaster(payable(feeCollector)).withdrawTo(keccak256("CORE_REGISTRY_PROCESSOR"), 2 wei); + PayMaster(payable(feeCollector)).withdrawNativeTo(keccak256("CORE_REGISTRY_PROCESSOR"), 2 wei); /// @dev admin tries withdraw if processor address is zero (check if handled gracefully) superRegistry.setAddress(keccak256("CORE_REGISTRY_PROCESSOR"), address(0), ETH); vm.expectRevert(Error.ZERO_ADDRESS.selector); - PayMaster(payable(feeCollector)).withdrawTo(keccak256("CORE_REGISTRY_PROCESSOR"), 1 wei); + PayMaster(payable(feeCollector)).withdrawNativeTo(keccak256("CORE_REGISTRY_PROCESSOR"), 1 wei); superRegistry.setAddress(keccak256("CORE_REGISTRY_PROCESSOR"), txProcessorETH, ETH); /// @dev admin moves the payment from fee collector to tx processor - PayMaster(payable(feeCollector)).withdrawTo(keccak256("CORE_REGISTRY_PROCESSOR"), 1 wei); + PayMaster(payable(feeCollector)).withdrawNativeTo(keccak256("CORE_REGISTRY_PROCESSOR"), 1 wei); assertEq(feeCollector.balance, 0); assertEq(txProcessorETH.balance, 1 wei); } + function test_withdrawNativeToSuperformReceiver() public { + vm.selectFork(FORKS[ETH]); + vm.startPrank(deployer); + + address feeCollector = getContract(ETH, "PayMaster"); + address token = getContract(ETH, "DAI"); + /// @dev transfer 10 dai to paymaster + deal(token, feeCollector, 10e18); + + /// @dev admin tries withdraw 0 + vm.expectRevert(Error.ZERO_INPUT_VALUE.selector); + PayMaster(payable(feeCollector)).withdrawTo(keccak256("SUPERFORM_RECEIVER"), token, 0); + + /// @dev admin tries withdraw token address 0 + vm.expectRevert(Error.ZERO_ADDRESS.selector); + PayMaster(payable(feeCollector)).withdrawTo(keccak256("SUPERFORM_RECEIVER"), address(0), 10e18); + + /// @dev admin tries withdraw token above balance + vm.expectRevert(Error.INSUFFICIENT_BALANCE.selector); + PayMaster(payable(feeCollector)).withdrawTo(keccak256("SUPERFORM_RECEIVER"), token, 11e18); + + /// @dev admin tries withdraw + PayMaster(payable(feeCollector)).withdrawTo(keccak256("SUPERFORM_RECEIVER"), token, 10e18); + } + function test_withdrawNativeToTxUpdater() public { vm.selectFork(FORKS[ETH]); vm.startPrank(deployer); @@ -118,18 +143,18 @@ contract PayMasterTest is ProtocolActions { /// @dev admin tries withdraw more than balance (check if handled gracefully) vm.expectRevert(Error.FAILED_TO_SEND_NATIVE.selector); - PayMaster(payable(feeCollector)).withdrawTo(keccak256("CORE_REGISTRY_UPDATER"), 2 wei); + PayMaster(payable(feeCollector)).withdrawNativeTo(keccak256("CORE_REGISTRY_UPDATER"), 2 wei); /// @dev admin tries withdraw if updater address is zero (check if handled gracefully) superRegistry.setAddress(keccak256("CORE_REGISTRY_UPDATER"), address(0), ETH); vm.expectRevert(Error.ZERO_ADDRESS.selector); - PayMaster(payable(feeCollector)).withdrawTo(keccak256("CORE_REGISTRY_UPDATER"), 1 wei); + PayMaster(payable(feeCollector)).withdrawNativeTo(keccak256("CORE_REGISTRY_UPDATER"), 1 wei); superRegistry.setAddress(keccak256("CORE_REGISTRY_UPDATER"), txUpdaterETH, ETH); /// @dev admin moves the payment from fee collector to tx updater - PayMaster(payable(feeCollector)).withdrawTo(keccak256("CORE_REGISTRY_UPDATER"), 1 wei); + PayMaster(payable(feeCollector)).withdrawNativeTo(keccak256("CORE_REGISTRY_UPDATER"), 1 wei); assertEq(feeCollector.balance, 0); assertEq(txUpdaterETH.balance, 1 wei); } @@ -147,7 +172,7 @@ contract PayMasterTest is ProtocolActions { /// @dev admin tries withdraw more than balance (check if handled gracefully) vm.expectRevert(Error.FAILED_TO_SEND_NATIVE.selector); - PayMaster(payable(feeCollector)).withdrawTo(keccak256("KEEPER_MOCK"), 1 wei); + PayMaster(payable(feeCollector)).withdrawNativeTo(keccak256("KEEPER_MOCK"), 1 wei); } function test_rebalanceToCoreStateRegistryTxProcessor() public { @@ -300,9 +325,11 @@ contract PayMasterTest is ProtocolActions { bytes memory data = abi.encode(messageId, destination, 1_500_000); + uint256 fees = HyperlaneImplementation(hyperlane).igp().quoteGasPayment(destination, 1_500_000); + vm.prank(deployer); vm.deal(feeCollector, 10 ether); - PayMaster(payable(feeCollector)).treatAMB(2, 10 ether, data); + PayMaster(payable(feeCollector)).treatAMB(2, fees, data); } function _successfulDeposit() internal { @@ -319,12 +346,14 @@ contract PayMasterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest(bytes(""), getContract(ETH, "DAI"), address(0), 1, ETH, 0), bytes(""), false, false, receiverAddress, + receiverAddress, bytes("") ); diff --git a/test/unit/payments/PaymentHelper.t.sol b/test/unit/payments/PaymentHelper.t.sol index 5508f8bda..8f6ebbf72 100644 --- a/test/unit/payments/PaymentHelper.t.sol +++ b/test/unit/payments/PaymentHelper.t.sol @@ -11,11 +11,30 @@ contract MockGasPriceOracle { { return (0, 28 gwei, block.timestamp, block.timestamp, 28 gwei); } + + function decimals() external pure returns (uint8) { + return 8; + } +} + +contract MalFunctioningPriceOracle { + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) + { + return (0, 0, block.timestamp, block.timestamp, 0); + } + + function decimals() external pure returns (uint8) { + return 8; + } } contract PaymentHelperTest is ProtocolActions { PaymentHelper public paymentHelper; MockGasPriceOracle public mockGasPriceOracle; + MalFunctioningPriceOracle public malFunctioningOracle; address native = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address receiverAddress = address(444); @@ -26,11 +45,12 @@ contract PaymentHelperTest is ProtocolActions { vm.selectFork(FORKS[ETH]); paymentHelper = PaymentHelper(getContract(ETH, "PaymentHelper")); mockGasPriceOracle = new MockGasPriceOracle(); + malFunctioningOracle = new MalFunctioningPriceOracle(); } function test_getGasPrice_chainlink_malfunction() public { vm.prank(deployer); - paymentHelper.updateRemoteChain(1, 2, abi.encode(address(0x222))); + paymentHelper.updateRemoteChain(1, 2, abi.encode(address(malFunctioningOracle))); address gasPriceOracle = address(paymentHelper.gasPriceOracle(ETH)); vm.mockCall( @@ -48,11 +68,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(emptyBytes, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -64,7 +86,7 @@ contract PaymentHelperTest is ProtocolActions { function test_getGasPrice_chainlink_incomplete_round() public { vm.prank(deployer); - paymentHelper.updateRemoteChain(1, 2, abi.encode(address(0x222))); + paymentHelper.updateRemoteChain(1, 2, abi.encode(address(malFunctioningOracle))); address gasPriceOracle = address(paymentHelper.gasPriceOracle(ETH)); @@ -83,11 +105,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(emptyBytes, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -108,11 +132,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(emptyBytes, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -128,11 +154,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(emptyBytes, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -148,11 +176,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(emptyBytes, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -182,11 +212,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock uint256MemoryArray, uint256MemoryArray, + uint256MemoryArray, liqRequestMemoryArray, emptyBytes, new bool[](1), new bool[](1), receiverAddress, + receiverAddress, emptyBytes ) ), @@ -202,11 +234,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock uint256MemoryArray, uint256MemoryArray, + uint256MemoryArray, liqRequestMemoryArray, emptyBytes, new bool[](1), new bool[](1), receiverAddress, + receiverAddress, emptyBytes ) ), @@ -253,11 +287,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(txData, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -302,11 +338,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(txData, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -341,11 +379,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(txData, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -413,11 +453,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(txData, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -471,11 +513,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(txData, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -520,11 +564,13 @@ contract PaymentHelperTest is ProtocolActions { /// timelock 420, 420, + 420, LiqRequest(txData, address(0), address(0), 1, ETH, 420), emptyBytes, false, false, receiverAddress, + receiverAddress, emptyBytes ) ), @@ -539,17 +585,17 @@ contract PaymentHelperTest is ProtocolActions { /// set config type: 1 vm.prank(deployer); - paymentHelper.updateRemoteChain(1, 1, abi.encode(address(420))); + paymentHelper.updateRemoteChain(1, 1, abi.encode(address(mockGasPriceOracle))); address result1 = address(paymentHelper.nativeFeedOracle(1)); - assertEq(result1, address(420)); + assertEq(result1, address(mockGasPriceOracle)); /// set config type: 2 vm.prank(deployer); - paymentHelper.updateRemoteChain(1, 2, abi.encode(address(421))); + paymentHelper.updateRemoteChain(1, 2, abi.encode(address(mockGasPriceOracle))); address result2 = address(paymentHelper.gasPriceOracle(1)); - assertEq(result2, address(421)); + assertEq(result2, address(mockGasPriceOracle)); /// set config type: 3 vm.prank(deployer); @@ -619,7 +665,7 @@ contract PaymentHelperTest is ProtocolActions { vm.prank(deployer); paymentHelper.addRemoteChain( 420, - IPaymentHelper.PaymentHelperConfig(address(420), address(421), 422, 423, 424, 425, 426, 427, 428, 429, 430) + IPaymentHelper.PaymentHelperConfig(address(0), address(0), 422, 423, 424, 425, 426, 427, 428, 429, 430, 431) ); } @@ -628,17 +674,17 @@ contract PaymentHelperTest is ProtocolActions { /// set config type: 1 vm.prank(deployer); - paymentHelper.updateRemoteChain(420, 1, abi.encode(address(420))); + paymentHelper.updateRemoteChain(420, 1, abi.encode(address(mockGasPriceOracle))); address result1 = address(paymentHelper.nativeFeedOracle(420)); - assertEq(result1, address(420)); + assertEq(result1, address(mockGasPriceOracle)); /// set config type: 2 vm.prank(deployer); - paymentHelper.updateRemoteChain(420, 2, abi.encode(address(421))); + paymentHelper.updateRemoteChain(420, 2, abi.encode(address(mockGasPriceOracle))); address result2 = address(paymentHelper.gasPriceOracle(420)); - assertEq(result2, address(421)); + assertEq(result2, address(mockGasPriceOracle)); /// set config type: 3 vm.prank(deployer); @@ -702,6 +748,13 @@ contract PaymentHelperTest is ProtocolActions { uint256 result11 = paymentHelper.timelockCost(1); assertEq(result11, 430); + + /// set config type: 12 + vm.prank(deployer); + paymentHelper.updateRemoteChain(1, 12, abi.encode(431)); + + uint256 result12 = paymentHelper.emergencyCost(1); + assertEq(result12, 431); } function _generateTimelockSuperformPackWithShift() internal pure returns (uint256 superformId_) { diff --git a/test/unit/roles/SuperRBAC.t.sol b/test/unit/roles/SuperRBAC.t.sol index 8bff8f248..3c8bc0b71 100644 --- a/test/unit/roles/SuperRBAC.t.sol +++ b/test/unit/roles/SuperRBAC.t.sol @@ -87,12 +87,7 @@ contract SuperRBACTest is BaseSetup { function test_revokePaymentAdminRole() public { _revokeAndCheck( - superRBAC.PAYMENT_ADMIN_ROLE(), - superRegistry.PAYMENT_ADMIN(), - deployer, - "", - generateBroadcastParams(5, 1), - 0 + superRBAC.PAYMENT_ADMIN_ROLE(), superRegistry.PAYMENT_ADMIN(), deployer, "", generateBroadcastParams(0), 0 ); } @@ -137,7 +132,7 @@ contract SuperRBACTest is BaseSetup { superRegistry.CORE_REGISTRY_PROCESSOR(), deployer, "", - generateBroadcastParams(5, 1), + generateBroadcastParams(0), 0 ); } @@ -156,7 +151,7 @@ contract SuperRBACTest is BaseSetup { superRegistry.BROADCAST_REGISTRY_PROCESSOR(), deployer, "", - generateBroadcastParams(5, 1), + generateBroadcastParams(0), 0 ); } @@ -175,7 +170,7 @@ contract SuperRBACTest is BaseSetup { superRegistry.TIMELOCK_REGISTRY_PROCESSOR(), deployer, "", - generateBroadcastParams(5, 1), + generateBroadcastParams(0), 0 ); } @@ -194,7 +189,7 @@ contract SuperRBACTest is BaseSetup { superRegistry.CORE_REGISTRY_UPDATER(), deployer, "", - generateBroadcastParams(5, 1), + generateBroadcastParams(0), 0 ); } @@ -205,7 +200,7 @@ contract SuperRBACTest is BaseSetup { superRegistry.CORE_REGISTRY_RESCUER(), deployer, "", - generateBroadcastParams(5, 1), + generateBroadcastParams(0), 0 ); } @@ -216,7 +211,7 @@ contract SuperRBACTest is BaseSetup { superRegistry.CORE_REGISTRY_DISPUTER(), deployer, "", - generateBroadcastParams(5, 1), + generateBroadcastParams(0), 0 ); } @@ -227,7 +222,7 @@ contract SuperRBACTest is BaseSetup { superRegistry.DST_SWAPPER_PROCESSOR(), deployer, "", - generateBroadcastParams(5, 1), + generateBroadcastParams(0), 0 ); } @@ -278,22 +273,22 @@ contract SuperRBACTest is BaseSetup { vm.expectRevert(Error.CANNOT_REVOKE_NON_BROADCASTABLE_ROLES.selector); /// @dev setting the status as false in chain id = ETH superRBAC.revokeRoleSuperBroadcast{ value: 1 ether }( - keccak256("BROADCASTER_ROLE"), generateBroadcastParams(5, 1), id + keccak256("BROADCASTER_ROLE"), generateBroadcastParams(0), id ); vm.expectRevert(Error.CANNOT_REVOKE_NON_BROADCASTABLE_ROLES.selector); /// @dev setting the status as false in chain id = ETH superRBAC.revokeRoleSuperBroadcast{ value: 1 ether }( - keccak256("PROTOCOL_ADMIN_ROLE"), generateBroadcastParams(5, 1), id + keccak256("PROTOCOL_ADMIN_ROLE"), generateBroadcastParams(0), id ); vm.expectRevert(Error.CANNOT_REVOKE_NON_BROADCASTABLE_ROLES.selector); /// @dev setting the status as false in chain id = ETH superRBAC.revokeRoleSuperBroadcast{ value: 1 ether }( - keccak256("EMERGENCY_ADMIN_ROLE"), generateBroadcastParams(5, 1), id + keccak256("EMERGENCY_ADMIN_ROLE"), generateBroadcastParams(0), id ); vm.expectRevert(Error.CANNOT_REVOKE_NON_BROADCASTABLE_ROLES.selector); /// @dev setting the status as false in chain id = ETH superRBAC.revokeRoleSuperBroadcast{ value: 1 ether }( - keccak256("WORMHOLE_VAA_RELAYER_ROLE"), generateBroadcastParams(5, 1), id + keccak256("WORMHOLE_VAA_RELAYER_ROLE"), generateBroadcastParams(0), id ); } diff --git a/test/unit/super-positions/super-positions.t.sol b/test/unit/super-positions/super-positions.t.sol index a593b5d52..c2cce26dc 100644 --- a/test/unit/super-positions/super-positions.t.sol +++ b/test/unit/super-positions/super-positions.t.sol @@ -19,6 +19,8 @@ contract SuperPositionsTest is BaseSetup { address vault; uint32 formImplementationId = 4; + address receiverAddress = deployer; + function setUp() public override { super.setUp(); vm.selectFork(FORKS[ETH]); @@ -30,7 +32,7 @@ contract SuperPositionsTest is BaseSetup { vault = getContract(ETH, VAULT_NAMES[0][0]); vm.prank(deployer); SuperformFactory(getContract(ETH, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); } @@ -77,7 +79,7 @@ contract SuperPositionsTest is BaseSetup { function test_revert_stateSync_InvalidPayloadStatus() public { uint256 txInfo = DataLib.packTxInfo(0, 2, 0, 1, address(0), ETH); vm.prank(getContract(ETH, "SuperformRouter")); - SuperPositions(address(superPositions)).updateTxHistory(0, txInfo); + SuperPositions(address(superPositions)).updateTxHistory(0, txInfo, receiverAddress); ReturnSingleData memory maliciousReturnData = ReturnSingleData(0, 1, 100); AMBMessage memory maliciousMessage = AMBMessage(txInfo, abi.encode(maliciousReturnData)); @@ -87,24 +89,24 @@ contract SuperPositionsTest is BaseSetup { superPositions.stateSync(maliciousMessage); } - function test_revert_stateSync_NotMinterStateRegistry() public { + function test_revert_stateSync_INVALID_REGISTRY_ID() public { uint256 txInfo = DataLib.packTxInfo(0, 2, 0, 1, address(0), ETH); ReturnSingleData memory maliciousReturnData = ReturnSingleData(0, 1, 100); AMBMessage memory maliciousMessage = AMBMessage(txInfo, abi.encode(maliciousReturnData)); vm.broadcast(getContract(ETH, "SuperformRouter")); - vm.expectRevert(Error.NOT_MINTER_STATE_REGISTRY_ROLE.selector); + vm.expectRevert(Error.INVALID_REGISTRY_ID.selector); superPositions.stateSync(maliciousMessage); } function test_revert_stateSync_NotMinterStateRegistry_InvalidRegistryId() public { uint256 txInfo = DataLib.packTxInfo(0, 2, 0, 1, address(0), ETH); address superform = getContract( - ARBI, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[2])) ); - uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[0], ARBI); + uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[2], ETH); ReturnSingleData memory maliciousReturnData = ReturnSingleData(0, superformId, 100); AMBMessage memory maliciousMessage = AMBMessage(txInfo, abi.encode(maliciousReturnData)); @@ -114,6 +116,23 @@ contract SuperPositionsTest is BaseSetup { superPositions.stateSync(maliciousMessage); } + function test_revert_stateSync_InvalidFormRegistryId() public { + uint256 txInfo = DataLib.packTxInfo(0, 2, 0, 1, address(0), ETH); + address superform = getContract( + ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ); + + /// non existent form implementation id so the get form state registry id returns 0 + uint256 superformId = DataLib.packSuperform(superform, 5, ETH); + + ReturnSingleData memory maliciousReturnData = ReturnSingleData(0, superformId, 100); + AMBMessage memory maliciousMessage = AMBMessage(txInfo, abi.encode(maliciousReturnData)); + + vm.broadcast(getContract(ETH, "TimelockStateRegistry")); + vm.expectRevert(Error.INVALID_FORM_REGISTRY_ID.selector); + superPositions.stateSync(maliciousMessage); + } + function test_revert_stateSync_InvalidPayload_CallbackType() public { /// @dev CallbackType = 0 (INIT) uint256 txInfo = DataLib.packTxInfo(0, 0, 0, 1, address(0), ETH); @@ -129,7 +148,7 @@ contract SuperPositionsTest is BaseSetup { /// @dev multi = 1 uint256 txInfo = DataLib.packTxInfo(0, 2, 1, 1, address(0), ETH); vm.prank(getContract(ETH, "SuperformRouter")); - SuperPositions(address(superPositions)).updateTxHistory(0, 1); + SuperPositions(address(superPositions)).updateTxHistory(0, 1, receiverAddress); ReturnSingleData memory maliciousReturnData = ReturnSingleData(0, 1, 100); AMBMessage memory maliciousMessage = AMBMessage(txInfo, abi.encode(maliciousReturnData)); @@ -139,25 +158,11 @@ contract SuperPositionsTest is BaseSetup { superPositions.stateSync(maliciousMessage); } - function test_revert_stateSync_SrcSenderMismatch() public { - /// @dev returnDataSrcSender = address(0x1) - uint256 txInfo = DataLib.packTxInfo(0, 2, 0, 1, address(0x1), ETH); - vm.prank(getContract(ETH, "SuperformRouter")); - SuperPositions(address(superPositions)).updateTxHistory(0, 1); - - ReturnSingleData memory maliciousReturnData = ReturnSingleData(0, 1, 100); - AMBMessage memory maliciousMessage = AMBMessage(txInfo, abi.encode(maliciousReturnData)); - - vm.broadcast(getContract(ETH, "CoreStateRegistry")); - vm.expectRevert(Error.SRC_SENDER_MISMATCH.selector); - superPositions.stateSync(maliciousMessage); - } - function test_revert_stateSync_SrcTxTypeMismatch() public { /// @dev TxType = 1 uint256 txInfo = DataLib.packTxInfo(1, 2, 0, 1, address(0), ETH); vm.prank(getContract(ETH, "SuperformRouter")); - SuperPositions(address(superPositions)).updateTxHistory(0, txInfo); + SuperPositions(address(superPositions)).updateTxHistory(0, txInfo, receiverAddress); txInfo = DataLib.packTxInfo(0, 2, 0, 1, address(0), ETH); ReturnSingleData memory maliciousReturnData = ReturnSingleData(0, 1, 100); @@ -189,7 +194,7 @@ contract SuperPositionsTest is BaseSetup { function test_revert_stateMultiSync_InvalidPayloadStatus() public { uint256 txInfo = DataLib.packTxInfo(0, 2, 1, 1, address(0), ETH); vm.prank(getContract(ETH, "SuperformRouter")); - SuperPositions(address(superPositions)).updateTxHistory(0, txInfo); + SuperPositions(address(superPositions)).updateTxHistory(0, txInfo, receiverAddress); uint256[] memory x = new uint256[](1); x[0] = 100; @@ -219,7 +224,7 @@ contract SuperPositionsTest is BaseSetup { function test_revert_stateMultiSync_InvalidPayload_Multi() public { uint256 txInfo = DataLib.packTxInfo(0, 2, 0, 1, address(0), ETH); vm.prank(getContract(ETH, "SuperformRouter")); - SuperPositions(address(superPositions)).updateTxHistory(0, 1); + SuperPositions(address(superPositions)).updateTxHistory(0, 1, receiverAddress); uint256[] memory x = new uint256[](1); x[0] = 100; @@ -232,26 +237,10 @@ contract SuperPositionsTest is BaseSetup { superPositions.stateMultiSync(maliciousMessage); } - function test_revert_stateMultiSync_SrcSenderMismatch() public { - uint256 txInfo = DataLib.packTxInfo(0, 2, 1, 1, address(0x1), ETH); - vm.prank(getContract(ETH, "SuperformRouter")); - SuperPositions(address(superPositions)).updateTxHistory(0, 1); - - uint256[] memory x = new uint256[](1); - x[0] = 100; - - ReturnMultiData memory maliciousReturnData = ReturnMultiData(0, x, x); - AMBMessage memory maliciousMessage = AMBMessage(txInfo, abi.encode(maliciousReturnData)); - - vm.broadcast(getContract(ETH, "CoreStateRegistry")); - vm.expectRevert(Error.SRC_SENDER_MISMATCH.selector); - superPositions.stateMultiSync(maliciousMessage); - } - function test_revert_stateMultiSync_SrcTxTypeMismatch() public { uint256 txInfo = DataLib.packTxInfo(1, 2, 1, 1, address(0), ETH); vm.prank(getContract(ETH, "SuperformRouter")); - SuperPositions(address(superPositions)).updateTxHistory(0, txInfo); + SuperPositions(address(superPositions)).updateTxHistory(0, txInfo, receiverAddress); txInfo = DataLib.packTxInfo(0, 2, 1, 1, address(0), ETH); uint256[] memory x = new uint256[](1); x[0] = 100; diff --git a/test/unit/super-registry/SuperRegistry.t.sol b/test/unit/super-registry/SuperRegistry.t.sol index 9e0b65a22..abd625027 100644 --- a/test/unit/super-registry/SuperRegistry.t.sol +++ b/test/unit/super-registry/SuperRegistry.t.sol @@ -143,7 +143,7 @@ contract SuperRegistryTest is BaseSetup { address[] memory bridgeAddress = new address[](3); address[] memory bridgeValidator = new address[](3); - bridgeId[0] = 5; + bridgeId[0] = 8; bridgeAddress[0] = address(0x1); bridgeValidator[0] = address(0x2); bridgeId[1] = 6; @@ -322,18 +322,18 @@ contract SuperRegistryTest is BaseSetup { superRegistry.setStateRegistryAddress(registryId, registryAddress); } - function test_setVaultLimitPerTx() public { + function test_setVaultLimitPerDestination() public { vm.prank(deployer); - superRegistry.setVaultLimitPerTx(1, 100); - assertEq(superRegistry.getVaultLimitPerTx(1), 100); + superRegistry.setVaultLimitPerDestination(1, 100); + assertEq(superRegistry.getVaultLimitPerDestination(1), 100); vm.prank(deployer); vm.expectRevert(Error.ZERO_INPUT_VALUE.selector); - superRegistry.setVaultLimitPerTx(1, 0); + superRegistry.setVaultLimitPerDestination(1, 0); vm.prank(address(420)); - vm.expectRevert(Error.NOT_PROTOCOL_ADMIN.selector); - superRegistry.setVaultLimitPerTx(1, 100); + vm.expectRevert(Error.NOT_EMERGENCY_ADMIN.selector); + superRegistry.setVaultLimitPerDestination(1, 100); } function test_setRequiredMessagingQuorum_and_revert_invalidCaller() public { @@ -349,7 +349,7 @@ contract SuperRegistryTest is BaseSetup { function test_set_delay() public { vm.prank(deployer); vm.expectRevert(Error.INVALID_TIMELOCK_DELAY.selector); - superRegistry.setDelay(30 minutes); + superRegistry.setDelay(5 minutes); vm.prank(deployer); vm.expectRevert(Error.INVALID_TIMELOCK_DELAY.selector); diff --git a/test/unit/superform-factory/superform-factory.addImplementation.sol b/test/unit/superform-factory/superform-factory.addImplementation.sol index 38041a1d0..807164f1f 100644 --- a/test/unit/superform-factory/superform-factory.addImplementation.sol +++ b/test/unit/superform-factory/superform-factory.addImplementation.sol @@ -12,7 +12,9 @@ contract SuperformFactoryAddImplementationTest is BaseSetup { /// @param formImplementation is the address of the new form implementation /// @param formImplementationId is the id of the formImplementation - event FormImplementationAdded(address indexed formImplementation, uint256 indexed formImplementationId); + event FormImplementationAdded( + address indexed formImplementation, uint256 indexed formImplementationId, uint8 indexed formStateRegistryId + ); function setUp() public override { super.setUp(); @@ -23,15 +25,28 @@ contract SuperformFactoryAddImplementationTest is BaseSetup { vm.selectFork(FORKS[chainId]); address formImplementation = address(new ERC4626Form(getContract(chainId, "SuperRegistry"))); - uint32 formImplementationId = 0; + uint32 formImplementationId = 44; vm.startPrank(deployer); /// @dev Event With Implementation vm.expectEmit(true, true, true, true); - emit FormImplementationAdded(formImplementation, formImplementationId); + emit FormImplementationAdded(formImplementation, formImplementationId, 1); + SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( + formImplementation, formImplementationId, 1 + ); + } + /// testing adding form implementation with state registry id 0 + function test_revert_addForm_InvalidFormStateRegistryId() public { + vm.selectFork(FORKS[chainId]); + + address formImplementation = address(new ERC4626Form(getContract(chainId, "SuperRegistry"))); + uint32 formImplementationId = 44; + + vm.startPrank(deployer); + vm.expectRevert(Error.INVALID_FORM_REGISTRY_ID.selector); SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 0 ); } @@ -42,19 +57,22 @@ contract SuperformFactoryAddImplementationTest is BaseSetup { address formImplementation1 = address(new ERC4626Form(getContract(chainId, "SuperRegistry"))); address formImplementation2 = address(new ERC4626Form(getContract(chainId, "SuperRegistry"))); - uint32 formImplementationId = 0; + uint32 formImplementationId = 44; vm.startPrank(deployer); SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation1, formImplementationId + formImplementation1, formImplementationId, 1 ); address imp = SuperformFactory(getContract(chainId, "SuperformFactory")).getFormImplementation(formImplementationId); assertEq(imp, formImplementation1); - vm.expectRevert(Error.FORM_IMPLEMENTATION_ID_ALREADY_EXISTS.selector); + vm.expectRevert(Error.FORM_IMPLEMENTATION_ALREADY_EXISTS.selector); SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation2, formImplementationId + formImplementation2, formImplementationId, 1 ); + + vm.expectRevert(Error.FORM_IMPLEMENTATION_ID_ALREADY_EXISTS.selector); + SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation(formImplementation1, 555, 1); } /// @dev Testing adding form with form address 0 @@ -67,7 +85,7 @@ contract SuperformFactoryAddImplementationTest is BaseSetup { vm.prank(deployer); vm.expectRevert(Error.ZERO_ADDRESS.selector); - SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation(form, formId); + SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation(form, formId, 2); } /// @dev Testing adding becon with wrong form @@ -80,6 +98,6 @@ contract SuperformFactoryAddImplementationTest is BaseSetup { vm.prank(deployer); vm.expectRevert(Error.ERC165_UNSUPPORTED.selector); - SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation(form, formId); + SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation(form, formId, 2); } } diff --git a/test/unit/superform-factory/superform-factory.changeFormPauseStatus.t.sol b/test/unit/superform-factory/superform-factory.changeFormPauseStatus.t.sol index e2a870e59..702867f2c 100644 --- a/test/unit/superform-factory/superform-factory.changeFormPauseStatus.t.sol +++ b/test/unit/superform-factory/superform-factory.changeFormPauseStatus.t.sol @@ -28,11 +28,11 @@ contract SuperformFactoryChangePauseTest is BaseSetup { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation1, formImplementationId + formImplementation1, formImplementationId, 1 ); SuperformFactory(getContract(chainId, "SuperformFactory")).changeFormImplementationPauseStatus( - formImplementationId, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(5, 1) + formImplementationId, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(0) ); bool status = SuperformFactory(payable(getContract(chainId, "SuperformFactory"))).isFormImplementationPaused( @@ -55,12 +55,12 @@ contract SuperformFactoryChangePauseTest is BaseSetup { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation1, formImplementationId + formImplementation1, formImplementationId, 1 ); - SuperformFactory(getContract(chainId, "SuperformFactory")).changeFormImplementationPauseStatus{ - value: 800 * 10 ** 18 - }(formImplementationId, ISuperformFactory.PauseStatus.PAUSED, ""); + SuperformFactory(getContract(chainId, "SuperformFactory")).changeFormImplementationPauseStatus( + formImplementationId, ISuperformFactory.PauseStatus.PAUSED, "" + ); bool status = SuperformFactory(payable(getContract(chainId, "SuperformFactory"))).isFormImplementationPaused( formImplementationId @@ -83,13 +83,35 @@ contract SuperformFactoryChangePauseTest is BaseSetup { /// @dev Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation1, formImplementationId + formImplementation1, formImplementationId, 1 ); /// @dev Invalid Form Implementation For Pausing vm.expectRevert(Error.INVALID_FORM_ID.selector); SuperformFactory(getContract(chainId, "SuperformFactory")).changeFormImplementationPauseStatus{ value: 800 * 10 ** 18 - }(formImplementationId_invalid, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(5, 2)); + }(formImplementationId_invalid, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(0)); + } + + function test_changeFormImplementationPauseZeroMsgValueNotSent() public { + vm.startPrank(deployer); + + vm.selectFork(FORKS[chainId]); + + address superRegistry = getContract(chainId, "SuperRegistry"); + + /// @dev Deploying Forms + address formImplementation1 = address(new ERC4626Form(superRegistry)); + uint32 formImplementationId = 0; + + // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested + SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( + formImplementation1, formImplementationId, 1 + ); + + vm.expectRevert(Error.MSG_VALUE_NOT_ZERO.selector); + SuperformFactory(getContract(chainId, "SuperformFactory")).changeFormImplementationPauseStatus{ + value: 800 * 10 ** 18 + }(formImplementationId, ISuperformFactory.PauseStatus.PAUSED, ""); } } diff --git a/test/unit/superform-factory/superform-factory.createSuperforms.t.sol b/test/unit/superform-factory/superform-factory.createSuperforms.t.sol index 4b86f8511..133e723e1 100644 --- a/test/unit/superform-factory/superform-factory.createSuperforms.t.sol +++ b/test/unit/superform-factory/superform-factory.createSuperforms.t.sol @@ -61,12 +61,6 @@ contract SuperformFactoryCreateSuperformTest is BaseSetup { vm.selectFork(FORKS[chainId]); - /// @dev testing the getAllSuperforms function - (vars.superformIds_, vars.superforms_) = - SuperformFactory(getContract(chainId, "SuperformFactory")).getAllSuperforms(); - - assertEq(vars.superformIds_.length, vars.superforms_.length); - /// @dev Testing Coss Chain Superform Deployments vars.transformedChainIds_ = new uint256[](vars.chainIds_.length); @@ -97,22 +91,15 @@ contract SuperformFactoryCreateSuperformTest is BaseSetup { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); uint256 totalSuperformsBefore = SuperformFactory(getContract(chainId, "SuperformFactory")).getSuperformCount(); /// @dev Creating superform using form - (uint256 superformIdCreated, address superformCreated) = + (uint256 superformIdCreated,) = SuperformFactory(getContract(chainId, "SuperformFactory")).createSuperform(formImplementationId, vault); - (uint256[] memory superformIds_, address[] memory superforms_) = - SuperformFactory(getContract(chainId, "SuperformFactory")).getAllSuperformsFromVault(vault); - - assertEq(superformIdCreated, superformIds_[superformIds_.length - 1]); - - assertEq(superformCreated, superforms_[superforms_.length - 1]); - uint256 totalSuperformsAfter = SuperformFactory(getContract(chainId, "SuperformFactory")).getSuperformCount(); assertEq(totalSuperformsAfter, totalSuperformsBefore + 1); @@ -137,7 +124,7 @@ contract SuperformFactoryCreateSuperformTest is BaseSetup { /// Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation1, formImplementationId + formImplementation1, formImplementationId, 1 ); /// @dev Creating superform using form @@ -158,7 +145,7 @@ contract SuperformFactoryCreateSuperformTest is BaseSetup { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using form @@ -183,7 +170,7 @@ contract SuperformFactoryCreateSuperformTest is BaseSetup { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested vm.expectRevert(Error.FORM_INTERFACE_UNSUPPORTED.selector); SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); } diff --git a/test/unit/superform-factory/superform-factory.stateSync.sol b/test/unit/superform-factory/superform-factory.stateSync.sol index ed91f6faf..e71f1942f 100644 --- a/test/unit/superform-factory/superform-factory.stateSync.sol +++ b/test/unit/superform-factory/superform-factory.stateSync.sol @@ -18,7 +18,7 @@ contract SuperformFactoryStateSyncTest is BaseSetup { vm.recordLogs(); SuperformFactory(getContract(ETH, "SuperformFactory")).changeFormImplementationPauseStatus( - formImplementationId, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(5, 1) + formImplementationId, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(0) ); _broadcastPayloadHelper(ETH, vm.getRecordedLogs()); @@ -70,7 +70,7 @@ contract SuperformFactoryStateSyncTest is BaseSetup { /// @dev checks if proof for this next one is diff vm.recordLogs(); SuperformFactory(getContract(ETH, "SuperformFactory")).changeFormImplementationPauseStatus( - formImplementationId, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(5, 1) + formImplementationId, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(0) ); _broadcastPayloadHelper(ETH, vm.getRecordedLogs()); diff --git a/test/unit/superform-forms/superform-form.ERC4626Form.t.sol b/test/unit/superform-forms/superform-form.ERC4626Form.t.sol index e45fc4d20..e672947e3 100644 --- a/test/unit/superform-forms/superform-form.ERC4626Form.t.sol +++ b/test/unit/superform-forms/superform-form.ERC4626Form.t.sol @@ -19,7 +19,7 @@ import "src/types/DataTypes.sol"; contract SuperformERC4626FormTest is ProtocolActions { uint64 internal chainId = ETH; - address refundAddress = address(444); + address receiverAddress = address(444); function setUp() public override { super.setUp(); @@ -43,7 +43,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -74,7 +74,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -104,7 +104,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -135,7 +135,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -165,7 +165,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -195,7 +195,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -225,7 +225,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -255,7 +255,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -285,7 +285,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -315,7 +315,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -345,7 +345,7 @@ contract SuperformERC4626FormTest is ProtocolActions { // Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested SuperformFactory(getContract(chainId, "SuperformFactory")).addFormImplementation( - formImplementation, formImplementationId + formImplementation, formImplementationId, 1 ); /// @dev Creating superform using formImplementationId and vault @@ -371,19 +371,21 @@ contract SuperformERC4626FormTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, - refundAddress, + receiverAddress, + receiverAddress, "" ); SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq(data); /// @dev no approval before call - vm.expectRevert(Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE.selector); + vm.expectRevert(Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT.selector); SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); } @@ -409,12 +411,14 @@ contract SuperformERC4626FormTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 2e18, + 2e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, - refundAddress, + receiverAddress, + receiverAddress, "" ); @@ -424,7 +428,7 @@ contract SuperformERC4626FormTest is ProtocolActions { MockERC20(getContract(ETH, "DAI")).transfer(superform, 3e18); MockERC20(getContract(ETH, "DAI")).approve(superform, 1e18); - vm.expectRevert(Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE.selector); + vm.expectRevert(Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT.selector); SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); } @@ -465,12 +469,14 @@ contract SuperformERC4626FormTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 2e18, + 2e18, 100, LiqRequest(_buildLiqBridgeTxData(liqBridgeTxDataArgs, true), getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, - refundAddress, + receiverAddress, + receiverAddress, "" ); @@ -484,7 +490,7 @@ contract SuperformERC4626FormTest is ProtocolActions { MockERC20(getContract(ETH, "DAI")).approve(router, 1e18); MockERC20(getContract(ETH, "DAI")).approve(router, 1e18); - vm.expectRevert(Error.DIRECT_DEPOSIT_INVALID_DATA.selector); + vm.expectRevert(Error.DIRECT_DEPOSIT_SWAP_FAILED.selector); SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); } @@ -493,7 +499,7 @@ contract SuperformERC4626FormTest is ProtocolActions { /// scenario: user could hack the funds from the form vm.selectFork(FORKS[ETH]); - vm.startPrank(deployer); + vm.startPrank(receiverAddress); address superform = getContract( ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) @@ -503,13 +509,15 @@ contract SuperformERC4626FormTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, - SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(deployer, superformId), + SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(receiverAddress, superformId), + SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(receiverAddress, superformId), 100, - LiqRequest(_buildMaliciousTxData(1, DAI, superform, ETH, 2e18, deployer), DAI, address(0), 1, ETH, 0), + LiqRequest(_buildMaliciousTxData(1, DAI, superform, ETH, 2e18, receiverAddress), DAI, address(0), 1, ETH, 0), "", false, false, - refundAddress, + receiverAddress, + receiverAddress, "" ); @@ -548,11 +556,12 @@ contract SuperformERC4626FormTest is ProtocolActions { 1, superformId, 1e18, + 1e18, 100, LiqRequest(bytes(""), getContract(ETH, "DAI"), address(0), 1, ARBI, 0), false, false, - refundAddress, + receiverAddress, "" ); @@ -583,11 +592,12 @@ contract SuperformERC4626FormTest is ProtocolActions { 1, 1, 1e18, + 1e18, 100, LiqRequest(bytes(""), getContract(ETH, "DAI"), address(0), 1, ARBI, 0), false, false, - refundAddress, + receiverAddress, "" ); @@ -600,7 +610,6 @@ contract SuperformERC4626FormTest is ProtocolActions { _successfulDeposit(false); vm.selectFork(FORKS[ETH]); - vm.startPrank(deployer); address superform = getContract( ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) @@ -608,10 +617,10 @@ contract SuperformERC4626FormTest is ProtocolActions { uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[0], ETH); - uint256 amount = SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(deployer, superformId); + uint256 amount = SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(receiverAddress, superformId); + vm.prank(deployer); MockERC20(getContract(ETH, "DAI")).transfer(superform, 1e18); - vm.stopPrank(); /// @dev simulating withdrawals with malicious tx data vm.startPrank(getContract(ETH, "CoreStateRegistry")); @@ -620,9 +629,10 @@ contract SuperformERC4626FormTest is ProtocolActions { 1, superformId, amount, + amount, 100, LiqRequest( - _buildMaliciousTxData(1, getContract(ETH, "DAI"), superform, ARBI, 2e18, deployer), + _buildMaliciousTxData(1, getContract(ETH, "DAI"), superform, ARBI, 2e18, receiverAddress), getContract(ETH, "DAI"), address(0), 1, @@ -631,88 +641,12 @@ contract SuperformERC4626FormTest is ProtocolActions { ), false, false, - refundAddress, - "" - ); - - vm.expectRevert(Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST.selector); - IBaseForm(superform).xChainWithdrawFromVault(data, deployer, ARBI); - } - - function test_superformDirectWithdrawWithInvalidLiqDataToken() public { - /// @dev prank deposits (just mint super-shares) - _successfulDeposit(false); - - vm.selectFork(FORKS[ETH]); - vm.startPrank(deployer); - - address superform = getContract( - ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) - ); - - uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[0], ETH); - - MockERC20(getContract(ETH, "DAI")).transfer(superform, 1e18); - - SingleVaultSFData memory data = SingleVaultSFData( - superformId, - SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(deployer, superformId), - 100, - LiqRequest("0x2222", getContract(ETH, "WETH"), address(0), 1, ETH, 0), - "", - false, - false, - refundAddress, - "" - ); - - SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq(data); - - SuperPositions(getContract(ETH, "SuperPositions")).increaseAllowance( - getContract(ETH, "SuperformRouter"), superformId, 1e18 - ); - vm.expectRevert(Error.DIRECT_WITHDRAW_INVALID_TOKEN.selector); - SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultWithdraw(req); - - vm.stopPrank(); - } - - function test_superformXChainWithInvalidLiqDataToken() public { - /// @dev prank deposits (just mint super-shares) - _successfulDeposit(false); - - vm.selectFork(FORKS[ETH]); - vm.startPrank(deployer); - - address superform = getContract( - ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) - ); - - uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[0], ETH); - - MockERC20(getContract(ETH, "DAI")).transfer(superform, 1e18); - vm.stopPrank(); - - bytes memory invalidTxData = abi.encode(1); - - InitSingleVaultData memory data = InitSingleVaultData( - 1, - superformId, - 0.9e18, - 100, - LiqRequest(invalidTxData, getContract(ETH, "WETH"), address(0), 1, ARBI, 0), - false, - false, - refundAddress, + receiverAddress, "" ); - vm.startPrank(getContract(ETH, "CoreStateRegistry")); - vm.expectRevert(Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST.selector); IBaseForm(superform).xChainWithdrawFromVault(data, deployer, ARBI); - - vm.stopPrank(); } function test_revert_baseForm_notSuperRegistry() public { @@ -737,7 +671,7 @@ contract SuperformERC4626FormTest is ProtocolActions { VaultMock vault = new VaultMock(asset, "Mock Vault", "Mock"); /// @dev Deploying Forms Using AddImplementation. Not Testing Reverts As Already Tested - superformFactory.addFormImplementation(formImplementation, formImplementationId); + superformFactory.addFormImplementation(formImplementation, formImplementationId, 1); /// @dev should revert as superRegistry coming from SuperformFactory does not /// match the one set in the ERC4626Form @@ -763,6 +697,9 @@ contract SuperformERC4626FormTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 1000; @@ -771,7 +708,17 @@ contract SuperformERC4626FormTest is ProtocolActions { liqReqs[0] = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReqs, "", new bool[](1), new bool[](1), refundAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReqs, + "", + new bool[](1), + new bool[](1), + receiverAddress, + receiverAddress, + "" ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -802,6 +749,9 @@ contract SuperformERC4626FormTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 1000; @@ -821,7 +771,17 @@ contract SuperformERC4626FormTest is ProtocolActions { ); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReqs, "", new bool[](1), new bool[](1), refundAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReqs, + "", + new bool[](1), + new bool[](1), + receiverAddress, + receiverAddress, + "" ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -848,12 +808,14 @@ contract SuperformERC4626FormTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, - refundAddress, + receiverAddress, + receiverAddress, "" ); @@ -868,12 +830,13 @@ contract SuperformERC4626FormTest is ProtocolActions { ); /// @dev approves before call MockERC20(getContract(ETH, "DAI")).approve(router, 1e18); - vm.expectRevert(Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE.selector); + vm.expectRevert(Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT.selector); SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest( _buildDummyTxDataUnitTests( @@ -898,17 +861,120 @@ contract SuperformERC4626FormTest is ProtocolActions { "", false, false, - refundAddress, + receiverAddress, + receiverAddress, "" ); req = SingleDirectSingleVaultStateReq(data); MockERC20(getContract(ETH, "DAI")).approve(router, 1e18); - vm.expectRevert(Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE.selector); + vm.expectRevert(Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT.selector); SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); vm.clearMockedCalls(); } + function test_maliciousBridge_protectionAgainstTokenDrain() public { + vm.selectFork(FORKS[ETH]); + vm.startPrank(deployer); + + uint256 daiAmount = 10 * 1e18; // 10 DAI + address superform = getContract( + ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ); + uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[0], ETH); + + /// @dev uses id 4 for bridge "LifiMockRugpull" + LiqBridgeTxDataArgs memory liqBridgeTxDataArgs = LiqBridgeTxDataArgs( + 4, + getContract(ETH, "DAI"), + getContract(ETH, "DAI"), + getContract(ETH, "DAI"), + superform, + ETH, + ETH, + ETH, + false, + superform, + uint256(ETH), + daiAmount, + false, + 0, + 1, + 1, + 1 + ); + + SingleVaultSFData memory data = SingleVaultSFData( + superformId, + daiAmount, + daiAmount, + 100, + LiqRequest(_buildLiqBridgeTxData(liqBridgeTxDataArgs, true), getContract(ETH, "DAI"), address(0), 4, ETH, 0), + "", + false, + false, + receiverAddress, + receiverAddress, + "" + ); + + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq(data); + + address router = getContract(ETH, "SuperformRouter"); + + /// Make Superform's initial balance to 10 DAI + MockERC20(getContract(ETH, "DAI")).transfer(superform, daiAmount); + + /// Single deposit 10 DAI to the Superform + MockERC20(getContract(ETH, "DAI")).approve(router, daiAmount); + SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); + + /// Bridge tries to drain Superform's tokens and it fails + vm.expectRevert(); + LiFiMockRugpull(payable(getContract(ETH, "LiFiMockRugpull"))).pullTokens(getContract(ETH, "DAI"), superform); + } + + function test_blacklistedSelectors_reverts() public { + vm.selectFork(FORKS[ETH]); + vm.startPrank(deployer); + + uint256 daiAmount = 10 * 1e18; // 10 DAI + address superform = getContract( + ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ); + uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[0], ETH); + bytes32 txId = keccak256("blacklisted"); + bytes memory blacklistedTxData = abi.encodeWithSelector( + LiFiMockBlacklisted.startBridgeTokensViaCBridgeNativeMin.selector, txId, address(0x1), 5, 5, 100 + ); + + SingleVaultSFData memory data = SingleVaultSFData( + superformId, + daiAmount, + daiAmount, + 100, + LiqRequest(blacklistedTxData, getContract(ETH, "DAI"), address(0), 5, ETH, 0), + "", + false, + false, + receiverAddress, + receiverAddress, + "" + ); + + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq(data); + + address router = getContract(ETH, "SuperformRouter"); + + /// Make Superform's initial balance to 10 DAI + MockERC20(getContract(ETH, "DAI")).transfer(superform, daiAmount); + + /// Single deposit 10 DAI to the Superform + MockERC20(getContract(ETH, "DAI")).approve(router, daiAmount); + vm.expectRevert(Error.BLACKLISTED_SELECTOR.selector); + SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); + } + /*/////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -927,12 +993,14 @@ contract SuperformERC4626FormTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, retain4626, - refundAddress, + receiverAddress, + receiverAddress, "" ); @@ -943,6 +1011,7 @@ contract SuperformERC4626FormTest is ProtocolActions { /// @dev approves before call MockERC20(getContract(ETH, "DAI")).approve(router, 1e18); SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); + vm.stopPrank(); } function _buildMaliciousTxData( diff --git a/test/unit/superform-forms/superform-form.ERC4626KYCDao.t.sol b/test/unit/superform-forms/superform-form.ERC4626KYCDao.t.sol index a459c5283..f7514d1b1 100644 --- a/test/unit/superform-forms/superform-form.ERC4626KYCDao.t.sol +++ b/test/unit/superform-forms/superform-form.ERC4626KYCDao.t.sol @@ -7,7 +7,7 @@ import "test/utils/ProtocolActions.sol"; contract SuperformERC4626KYCDaoFormTest is BaseSetup { uint64 internal chainId = ETH; - address refundAddress = address(444); + address receiverAddress = address(444); function setUp() public override { super.setUp(); @@ -28,12 +28,14 @@ contract SuperformERC4626KYCDaoFormTest is BaseSetup { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, - refundAddress, + receiverAddress, + receiverAddress, "" ); @@ -67,11 +69,12 @@ contract SuperformERC4626KYCDaoFormTest is BaseSetup { 1, superformId, 1e18, + 1e18, 100, LiqRequest(bytes(""), getContract(ETH, "DAI"), address(0), 1, ARBI, 0), false, false, - refundAddress, + receiverAddress, "" ); @@ -95,7 +98,7 @@ contract SuperformERC4626KYCDaoFormTest is BaseSetup { ETH, string.concat("DAI", "kycDAO4626", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[2])) ); - IBaseForm(superform).forwardDustToPaymaster(); + IBaseForm(superform).forwardDustToPaymaster(getContract(ETH, "DAI")); } function test_emergencyWithdraw() public { @@ -110,6 +113,6 @@ contract SuperformERC4626KYCDaoFormTest is BaseSetup { vm.prank(getContract(ETH, "EmergencyQueue")); - IBaseForm(superform).emergencyWithdraw(users[0], refundAddress, 1e18); + IBaseForm(superform).emergencyWithdraw(receiverAddress, 1e18); } } diff --git a/test/unit/superform-forms/superform-form.ERC4626Timelock.t.sol b/test/unit/superform-forms/superform-form.ERC4626Timelock.t.sol index 6ff33dad9..bd1b1bfd9 100644 --- a/test/unit/superform-forms/superform-form.ERC4626Timelock.t.sol +++ b/test/unit/superform-forms/superform-form.ERC4626Timelock.t.sol @@ -10,11 +10,13 @@ import { DataLib } from "src/libraries/DataLib.sol"; import { SuperformRouter } from "src/SuperformRouter.sol"; import { IBaseForm } from "src/interfaces/IBaseForm.sol"; import { ERC4626TimelockForm } from "src/forms/ERC4626TimelockForm.sol"; +import { IERC4626 } from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; import "src/types/DataTypes.sol"; +import "forge-std/console.sol"; contract SuperformERC4626TimelockFormTest is ProtocolActions { uint64 internal chainId = ETH; - address refundAddress = address(444); + address receiverAddress = address(444); function setUp() public override { super.setUp(); @@ -40,11 +42,12 @@ contract SuperformERC4626TimelockFormTest is ProtocolActions { 1, superformId, 1e18, + 1e18, 100, LiqRequest(bytes(""), getContract(ETH, "DAI"), address(0), 1, ARBI, 0), false, false, - refundAddress, + receiverAddress, "" ); @@ -55,7 +58,7 @@ contract SuperformERC4626TimelockFormTest is ProtocolActions { vm.prank(getContract(ETH, "TimelockStateRegistry")); vm.expectRevert(Error.WITHDRAW_TX_DATA_NOT_UPDATED.selector); ERC4626TimelockForm(payable(superform)).withdrawAfterCoolDown( - TimelockPayload(1, deployer, ETH, block.timestamp, data, TimelockStatus.PENDING) + TimelockPayload(1, ETH, block.timestamp, data, TimelockStatus.PENDING) ); } @@ -81,11 +84,12 @@ contract SuperformERC4626TimelockFormTest is ProtocolActions { 1, superformId, 1e18, + 1e18, 100, LiqRequest(invalidNonEmptyTxData, address(0), address(0), 1, ETH, 0), false, false, - refundAddress, + receiverAddress, "" ); @@ -96,7 +100,7 @@ contract SuperformERC4626TimelockFormTest is ProtocolActions { vm.prank(getContract(ETH, "TimelockStateRegistry")); vm.expectRevert(Error.WITHDRAW_TOKEN_NOT_UPDATED.selector); ERC4626TimelockForm(payable(superform)).withdrawAfterCoolDown( - TimelockPayload(1, deployer, ETH, block.timestamp, data, TimelockStatus.PENDING) + TimelockPayload(1, ETH, block.timestamp, data, TimelockStatus.PENDING) ); } @@ -120,11 +124,12 @@ contract SuperformERC4626TimelockFormTest is ProtocolActions { 1, superformId, 1e18, + 1e18, 100, LiqRequest("", address(0), address(0), 1, ETH, 0), false, false, - refundAddress, + receiverAddress, "" ); @@ -133,7 +138,7 @@ contract SuperformERC4626TimelockFormTest is ProtocolActions { vm.prank(getContract(ETH, "TimelockStateRegistry")); ERC4626TimelockForm(payable(superform)).withdrawAfterCoolDown( - TimelockPayload(1, deployer, ETH, block.timestamp, data, TimelockStatus.PENDING) + TimelockPayload(1, ETH, block.timestamp, data, TimelockStatus.PENDING) ); } @@ -178,23 +183,24 @@ contract SuperformERC4626TimelockFormTest is ProtocolActions { 1, superformId, 1e18, + 1e18, 100, LiqRequest( _buildLiqBridgeTxData(liqBridgeTxDataArgs, false), getContract(ETH, "DAI"), address(0), 1, ETH, 0 ), false, false, - refundAddress, + receiverAddress, "" ); vm.prank(getContract(ETH, "CoreStateRegistry")); IBaseForm(superform).xChainWithdrawFromVault(data, deployer, ARBI); - vm.expectRevert(Error.DIRECT_WITHDRAW_INVALID_LIQ_REQUEST.selector); + vm.expectRevert(Error.XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST.selector); vm.prank(getContract(ETH, "TimelockStateRegistry")); ERC4626TimelockForm(payable(superform)).withdrawAfterCoolDown( - TimelockPayload(1, deployer, ETH, block.timestamp, data, TimelockStatus.PENDING) + TimelockPayload(1, ETH, block.timestamp, data, TimelockStatus.PENDING) ); } @@ -216,12 +222,14 @@ contract SuperformERC4626TimelockFormTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest(bytes(""), getContract(ETH, "DAI"), address(0), 1, ETH, 0), bytes(""), false, false, - refundAddress, + receiverAddress, + receiverAddress, bytes("") ); diff --git a/test/unit/superform-forms/superform-form.forwardDust.t.sol b/test/unit/superform-forms/superform-form.forwardDust.t.sol index 56f244f18..95121ca4f 100644 --- a/test/unit/superform-forms/superform-form.forwardDust.t.sol +++ b/test/unit/superform-forms/superform-form.forwardDust.t.sol @@ -15,7 +15,7 @@ import "src/types/DataTypes.sol"; contract ForwardDustFormTest is ProtocolActions { uint64 internal chainId = ETH; - address refundAddress = address(444); + address receiverAddress = address(444); function setUp() public override { super.setUp(); @@ -26,7 +26,7 @@ contract ForwardDustFormTest is ProtocolActions { uint256 balanceBefore = MockERC20(getContract(ARBI, "WETH")).balanceOf(superform); assertGt(balanceBefore, 0); - IBaseForm(superform).forwardDustToPaymaster(); + IBaseForm(superform).forwardDustToPaymaster(getContract(ARBI, "WETH")); uint256 balanceAfter = MockERC20(getContract(ARBI, "WETH")).balanceOf(superform); assertEq(balanceAfter, 0); @@ -37,7 +37,7 @@ contract ForwardDustFormTest is ProtocolActions { uint256 balanceBefore = MockERC20(getContract(ARBI, "WETH")).balanceOf(superform); assertEq(balanceBefore, 0); - IBaseForm(superform).forwardDustToPaymaster(); + IBaseForm(superform).forwardDustToPaymaster(getContract(ARBI, "WETH")); uint256 balanceAfter = MockERC20(getContract(ARBI, "WETH")).balanceOf(superform); assertEq(balanceAfter, 0); @@ -48,12 +48,27 @@ contract ForwardDustFormTest is ProtocolActions { uint256 balanceBefore = MockERC20(getContract(ARBI, "WETH")).balanceOf(superform); assertGt(balanceBefore, 0); - IBaseForm(superform).forwardDustToPaymaster(); + IBaseForm(superform).forwardDustToPaymaster(getContract(ARBI, "WETH")); uint256 balanceAfter = MockERC20(getContract(ARBI, "WETH")).balanceOf(superform); assertEq(balanceAfter, 0); } + function test_forwardDustToPaymaster_arbitraryToken_4626revert() public { + address superform = _successfulDepositWithdraw("VaultMock", 0, 1e18, 0, false, deployer); + + address arbitraryToken = getContract(ARBI, "DAI"); + deal(arbitraryToken, superform, 10e18); + + IBaseForm(superform).forwardDustToPaymaster(arbitraryToken); + address vaultAddress = IBaseForm(superform).getVaultAddress(); + vm.expectRevert(Error.CANNOT_FORWARD_4646_TOKEN.selector); + IBaseForm(superform).forwardDustToPaymaster(vaultAddress); + + vm.expectRevert(Error.ZERO_ADDRESS.selector); + IBaseForm(superform).forwardDustToPaymaster(address(0)); + } + function _successfulDepositWithdraw( string memory vaultKind_, uint256 formImplementationId_, @@ -82,12 +97,14 @@ contract ForwardDustFormTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, amountToDeposit_, + IBaseForm(superform).previewDepositTo(amountToDeposit_), 100, LiqRequest("", getContract(ARBI, "WETH"), address(0), 1, ARBI, 0), "", false, false, - refundAddress, + receiverAddress, + receiverAddress, "" ); @@ -103,12 +120,14 @@ contract ForwardDustFormTest is ProtocolActions { vm.stopPrank(); - uint256 superPositionBalance = SuperPositions(getContract(ARBI, "SuperPositions")).balanceOf(user, superformId); + uint256 superPositionBalance = + SuperPositions(getContract(ARBI, "SuperPositions")).balanceOf(receiverAddress, superformId); InitSingleVaultData memory data2 = InitSingleVaultData( 1, superformId, spAmountToRedeem_ == 0 ? superPositionBalance : spAmountToRedeem_, + spAmountToRedeem_ == 0 ? superPositionBalance : spAmountToRedeem_, 100, LiqRequest( _buildDummyTxDataUnitTests( @@ -119,13 +138,13 @@ contract ForwardDustFormTest is ProtocolActions { superform, ARBI, ETH, - nasty_ ? 0.2e18 : IBaseForm(superform).previewRedeemFrom(superPositionBalance), // nastiness + nasty_ ? 0.99e18 : IBaseForm(superform).previewRedeemFrom(superPositionBalance), // nastiness // here - refundAddress, + receiverAddress, false ) ), - getContract(ARBI, "WETH"), + getContract(ETH, "WETH"), address(0), 1, ETH, @@ -133,19 +152,18 @@ contract ForwardDustFormTest is ProtocolActions { ), false, false, - refundAddress, + receiverAddress, "" ); vm.selectFork(FORKS[ARBI]); if (formImplementationId_ != 1) { vm.prank(getContract(ARBI, "CoreStateRegistry")); - IBaseForm(superform).xChainWithdrawFromVault(data2, user, ETH); } else { vm.prank(getContract(ARBI, "TimelockStateRegistry")); IERC4626TimelockForm(superform).withdrawAfterCoolDown( - TimelockPayload(1, user, ETH, block.timestamp, data2, TimelockStatus.PENDING) + TimelockPayload(1, ETH, block.timestamp, data2, TimelockStatus.PENDING) ); } } diff --git a/test/unit/superform-router/SuperformRouter.AA.t.sol b/test/unit/superform-router/SuperformRouter.AA.t.sol index f221dfbc7..2e1784569 100644 --- a/test/unit/superform-router/SuperformRouter.AA.t.sol +++ b/test/unit/superform-router/SuperformRouter.AA.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.23; import { Error } from "src/libraries/Error.sol"; import "test/utils/ProtocolActions.sol"; +import { IERC1155Errors } from "openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol"; import { ERC1155Holder } from "openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol"; @@ -25,12 +26,44 @@ contract SmartContractWallet is ERC1155Holder { function singleXChainSingleVaultWithdraw(SingleXChainSingleVaultStateReq memory req) external payable { router.singleXChainSingleVaultWithdraw{ value: msg.value }(req); } + + function singleDirectSingleVaultDeposit(SingleDirectSingleVaultStateReq memory req) external payable { + MockERC20(dai).approve(address(router), req.superformData.amount); + + router.singleDirectSingleVaultDeposit{ value: msg.value }(req); + } + + function singleDirectSingleVaultWithdraw(SingleDirectSingleVaultStateReq memory req) external payable { + router.singleDirectSingleVaultWithdraw{ value: msg.value }(req); + } +} + +contract SmartContractWalletNotHolder { + SuperformRouter immutable router; + address immutable dai; + + constructor(SuperformRouter router_, address dai_) { + router = router_; + dai = dai_; + } + + receive() external payable { } + + function singleXChainSingleVaultDeposit(SingleXChainSingleVaultStateReq memory req) external payable { + MockERC20(dai).approve(address(router), req.superformData.amount); + router.singleXChainSingleVaultDeposit{ value: msg.value }(req); + } + + function singleXChainSingleVaultWithdraw(SingleXChainSingleVaultStateReq memory req) external payable { + router.singleXChainSingleVaultWithdraw{ value: msg.value }(req); + } } contract SuperformRouterAATest is ProtocolActions { address receiverAddress = address(444); SmartContractWallet walletSource; SmartContractWallet walletDestination; + SmartContractWalletNotHolder walletSourceInvalid; function setUp() public override { super.setUp(); @@ -39,6 +72,9 @@ contract SuperformRouterAATest is ProtocolActions { walletSource = new SmartContractWallet( SuperformRouter(payable(getContract(ETH, "SuperformRouter"))), getContract(ETH, "DAI") ); + walletSourceInvalid = new SmartContractWalletNotHolder( + SuperformRouter(payable(getContract(ETH, "SuperformRouter"))), getContract(ETH, "DAI") + ); vm.selectFork(FORKS[ARBI]); walletDestination = new SmartContractWallet( @@ -47,52 +83,174 @@ contract SuperformRouterAATest is ProtocolActions { } function test_depositWithSmartContractWallet() public { - _xChainDeposit_SmartContractWallet(false, true, "VaultMock", 0); + _xChainDeposit_SmartContractWallet(false, true, 0, "VaultMock", 0); } function test_depositWithSmartContractWallet_revertsReceive4626_noReceiveAddress() public { - _xChainDeposit_SmartContractWallet(false, false, "VaultMock", 0); + _xChainDeposit_SmartContractWallet(false, false, 0, "VaultMock", 0); + } + + function test_depositWithSmartContractWallet_InvalidHolder() public { + _xChainDeposit_SmartContractWallet(true, true, 1, "VaultMock", 0); } - function test_depositWithSmartContractWallet_receive4626_HasReceiveAddress() public { - _xChainDeposit_SmartContractWallet(true, true, "VaultMock", 0); + function test_depositWithSmartContractWallet_revertsReceive4626_noReceiveAddressSP() public { + _xChainDeposit_SmartContractWallet(false, false, 2, "VaultMock", 0); } - function test_withdrawWithSmartContractWallet() public { - _xChainDeposit_SmartContractWallet(false, true, "VaultMock", 0); + function test_withdrawWithSmartContractWallet_deposit_Withdraw() public { + _xChainDeposit_SmartContractWallet(false, true, 0, "VaultMock", 0); - _xChainWithdraw_SmartContractWallet(ETH, address(walletDestination), false, "VaultMock", 0); + _xChainWithdraw_SmartContractWallet(ETH, address(walletDestination), false, "VaultMock", 0, false); } function test_withdrawWithSmartContractWallet_3rdChainId() public { - _xChainDeposit_SmartContractWallet(false, true, "VaultMock", 0); + _xChainDeposit_SmartContractWallet(false, true, 0, "VaultMock", 0); vm.selectFork(FORKS[AVAX]); SmartContractWallet walletDestinationAVAX = new SmartContractWallet( SuperformRouter(payable(getContract(AVAX, "SuperformRouter"))), getContract(AVAX, "DAI") ); - _xChainWithdraw_SmartContractWallet(AVAX, address(walletDestinationAVAX), false, "VaultMock", 0); + _xChainWithdraw_SmartContractWallet(AVAX, address(walletDestinationAVAX), false, "VaultMock", 0, false); } function test_withdrawWithSmartContractWallet_timelock() public { - _xChainDeposit_SmartContractWallet(false, true, "ERC4626TimelockMock", 1); + _xChainDeposit_SmartContractWallet(false, true, 0, "ERC4626TimelockMock", 1); - _xChainWithdraw_SmartContractWallet(ETH, address(walletDestination), false, "ERC4626TimelockMock", 1); + _xChainWithdraw_SmartContractWallet(ETH, address(walletDestination), false, "ERC4626TimelockMock", 1, false); } function test_withdrawWithSmartContractWallet_3rdChainId_timelock() public { - _xChainDeposit_SmartContractWallet(false, true, "ERC4626TimelockMock", 1); + _xChainDeposit_SmartContractWallet(false, true, 0, "ERC4626TimelockMock", 1); + vm.selectFork(FORKS[AVAX]); + SmartContractWallet walletDestinationAVAX = new SmartContractWallet( + SuperformRouter(payable(getContract(AVAX, "SuperformRouter"))), getContract(AVAX, "DAI") + ); + + _xChainWithdraw_SmartContractWallet( + AVAX, address(walletDestinationAVAX), false, "ERC4626TimelockMock", 1, false + ); + } + + function test_withdrawWithSmartContractWallet_retain4626() public { + _xChainDeposit_SmartContractWallet(false, true, 0, "VaultMock", 0); + + _xChainWithdraw_SmartContractWallet(ARBI, address(walletDestination), false, "VaultMock", 0, true); + } + + function test_withdrawWithSmartContractWallet_3rdChainId_retain4626() public { + _xChainDeposit_SmartContractWallet(false, true, 0, "VaultMock", 0); + vm.selectFork(FORKS[AVAX]); + SmartContractWallet walletDestinationAVAX = new SmartContractWallet( + SuperformRouter(payable(getContract(AVAX, "SuperformRouter"))), getContract(AVAX, "DAI") + ); + + _xChainWithdraw_SmartContractWallet(ARBI, address(walletDestinationAVAX), false, "VaultMock", 0, true); + } + + function test_withdrawWithSmartContractWallet_timelock_retain4626() public { + _xChainDeposit_SmartContractWallet(false, true, 0, "ERC4626TimelockMock", 1); + + _xChainWithdraw_SmartContractWallet(ARBI, address(walletDestination), false, "ERC4626TimelockMock", 1, true); + } + + function test_withdrawWithSmartContractWallet_3rdChainId_timelock_retain4626() public { + _xChainDeposit_SmartContractWallet(false, true, 0, "ERC4626TimelockMock", 1); vm.selectFork(FORKS[AVAX]); SmartContractWallet walletDestinationAVAX = new SmartContractWallet( SuperformRouter(payable(getContract(AVAX, "SuperformRouter"))), getContract(AVAX, "DAI") ); - _xChainWithdraw_SmartContractWallet(AVAX, address(walletDestinationAVAX), false, "ERC4626TimelockMock", 1); + _xChainWithdraw_SmartContractWallet(ARBI, address(walletDestinationAVAX), false, "ERC4626TimelockMock", 1, true); + } + + function test_direct_withdrawWithSmartContractWallet_retain4626() public { + _directDeposit_SmartContractWallet(false, true, 0, "VaultMock", 0); + + _directWithdraw_SmartContractWallet(ARBI, address(walletDestination), false, "VaultMock", 0, true); + } + + function test_direct_withdrawWithSmartContractWallet_timelock_retain4626() public { + _directDeposit_SmartContractWallet(false, true, 0, "ERC4626TimelockMock", 1); + + _directWithdraw_SmartContractWallet(ARBI, address(walletDestination), false, "ERC4626TimelockMock", 1, true); + } + + function _directDeposit_SmartContractWallet( + bool receive4626_, + bool receiveAddress_, + uint256 receiveAddressSP_, + string memory vaultKind, + uint256 formImplId + ) + internal + { + address superform = getContract( + ARBI, string.concat("DAI", vaultKind, "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[formImplId])) + ); + + uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[formImplId], ARBI); + vm.startPrank(deployer); + + vm.selectFork(FORKS[ARBI]); + + address sourceReceiverOfSP; + + if (receiveAddressSP_ == 0) { + sourceReceiverOfSP = address(walletDestination); + } else if (receiveAddressSP_ == 1) { + sourceReceiverOfSP = address(0); + } + + SingleVaultSFData memory data = SingleVaultSFData( + superformId, + 1e18, + 1e18, + 10_000, + /// @dev invalid slippage + LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ARBI, 0), + "", + false, + receive4626_, + receiveAddress_ ? address(walletDestination) : address(0), + sourceReceiverOfSP, + "" + ); + + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq(data); + + vm.deal(address(walletDestination), 2 ether); + deal(getContract(ARBI, "DAI"), address(walletDestination), 1e18); + + /// @dev approves before call + MockERC20(getContract(ARBI, "DAI")).approve(address(walletDestination), 1e18); + vm.stopPrank(); + + vm.recordLogs(); + + vm.prank(deployer); + + if (!receiveAddress_) { + vm.expectRevert(Error.INVALID_SUPERFORMS_DATA.selector); + /// @dev msg sender is wallet, tx origin is deployer + walletDestination.singleDirectSingleVaultDeposit{ value: 2 ether }(req); + return; + } + /// @dev msg sender is wallet, tx origin is deployer + walletDestination.singleDirectSingleVaultDeposit{ value: 2 ether }(req); + + if (!receive4626_) { + assertGt( + SuperPositions(getContract(ARBI, "SuperPositions")).balanceOf(address(walletDestination), superformId), + 0 + ); + } } function _xChainDeposit_SmartContractWallet( bool receive4626_, bool receiveAddress_, + uint256 receiveAddressSP_, string memory vaultKind, uint256 formImplId ) @@ -129,9 +287,20 @@ contract SuperformRouterAATest is ProtocolActions { 1 ); + address sourceReceiverOfSP; + + if (receiveAddressSP_ == 0) { + sourceReceiverOfSP = address(walletSource); + } else if (receiveAddressSP_ == 1) { + sourceReceiverOfSP = address(walletSourceInvalid); + } else if (receiveAddressSP_ == 2) { + sourceReceiverOfSP = address(0); + } + SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 10_000, /// @dev invalid slippage LiqRequest( @@ -141,6 +310,7 @@ contract SuperformRouterAATest is ProtocolActions { false, receive4626_, receiveAddress_ ? address(walletDestination) : address(0), + sourceReceiverOfSP, "" ); @@ -152,7 +322,8 @@ contract SuperformRouterAATest is ProtocolActions { vm.deal(address(walletSource), 2 ether); deal(getContract(ETH, "DAI"), address(walletSource), 1e18); - + vm.deal(address(walletSourceInvalid), 2 ether); + deal(getContract(ETH, "DAI"), address(walletSourceInvalid), 1e18); /// @dev approves before call MockERC20(getContract(ETH, "DAI")).approve(address(walletSource), 1e18); vm.stopPrank(); @@ -166,6 +337,16 @@ contract SuperformRouterAATest is ProtocolActions { /// @dev msg sender is wallet, tx origin is deployer walletSource.singleXChainSingleVaultDeposit{ value: 2 ether }(req); return; + } else if (sourceReceiverOfSP == address(0)) { + vm.expectRevert(Error.INVALID_SUPERFORMS_DATA.selector); + /// @dev msg sender is wallet, tx origin is deployer + walletSource.singleXChainSingleVaultDeposit{ value: 2 ether }(req); + return; + } else if (sourceReceiverOfSP == address(walletSourceInvalid)) { + vm.expectRevert(abi.encodeWithSelector(IERC1155Errors.ERC1155InvalidReceiver.selector, sourceReceiverOfSP)); + /// @dev msg sender is an invalid contract + walletSourceInvalid.singleXChainSingleVaultDeposit{ value: 2 ether }(req); + return; } /// @dev msg sender is wallet, tx origin is deployer walletSource.singleXChainSingleVaultDeposit{ value: 2 ether }(req); @@ -192,13 +373,19 @@ contract SuperformRouterAATest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; - CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload(1, amounts); + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(ARBI, "DAI"); - uint256 nativeAmount = PaymentHelper(getContract(ARBI, "PaymentHelper")).estimateAckCost(1); + CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload( + 1, bridgedTokens, amounts + ); vm.recordLogs(); - vm.prank(deployer); - CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).processPayload{ value: nativeAmount }(1); + vm.startPrank(deployer); + CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).processPayload{ + value: PaymentHelper(getContract(ARBI, "PaymentHelper")).estimateAckCost(1) + }(1); + vm.stopPrank(); if (!receive4626_) { logs = vm.getRecordedLogs(); @@ -228,59 +415,155 @@ contract SuperformRouterAATest is ProtocolActions { } } - function _xChainWithdraw_SmartContractWallet( + struct XChainWithdrawsInternalVars { + address superform; + uint256 superformId; + } + + function _directWithdraw_SmartContractWallet( uint64 liqDstChainId_, address scWalletAtLiqDst_, bool sameChainTxData_, string memory vaultKind, - uint256 formImplId + uint256 formImplId, + bool receive4626_ ) internal { /// scenario: user deposits with his own collateral and has approved enough tokens - vm.selectFork(FORKS[ETH]); - - address superformRouter = getContract(ETH, "SuperformRouter"); - - address superform = getContract( + vm.selectFork(FORKS[ARBI]); + XChainWithdrawsInternalVars memory v; + v.superform = getContract( ARBI, string.concat("DAI", vaultKind, "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[formImplId])) ); - uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[formImplId], ARBI); + v.superformId = DataLib.packSuperform(v.superform, FORM_IMPLEMENTATION_IDS[formImplId], ARBI); - bytes memory txData = _buildLiqBridgeTxData( - LiqBridgeTxDataArgs( - 1, - getContract(ARBI, "DAI"), + SingleVaultSFData memory data = SingleVaultSFData( + v.superformId, + 1e18, + 1e18, + 1000, + LiqRequest( + _buildLiqBridgeTxData( + LiqBridgeTxDataArgs( + 1, + getContract(ARBI, "DAI"), + getContract(ARBI, "DAI"), + getContract(liqDstChainId_, "DAI"), + v.superform, + ARBI, + ARBI, + liqDstChainId_, + false, + scWalletAtLiqDst_, + uint256(liqDstChainId_), + 1e18, + true, + /// @dev placeholder value, not used + 0, + 1, + 1, + 1 + ), + sameChainTxData_ + ), getContract(ARBI, "DAI"), - getContract(liqDstChainId_, "DAI"), - superform, - ARBI, - ETH, - liqDstChainId_, - false, - scWalletAtLiqDst_, - uint256(liqDstChainId_), - 1e18, - true, - /// @dev placeholder value, not used - 0, + address(0), 1, - 1, - 1 + liqDstChainId_, + 0 ), - sameChainTxData_ + "", + false, + receive4626_, + liqDstChainId_ != ETH ? scWalletAtLiqDst_ : address(walletDestination), + address(walletDestination), + "" + ); + + console.log("receiverAddress", data.receiverAddress); + + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq(data); + + /// @dev approves before call + vm.prank(address(walletDestination)); + SuperPositions(getContract(ARBI, "SuperPositions")).increaseAllowance( + getContract(ARBI, "SuperformRouter"), v.superformId, 1e18 + ); + vm.recordLogs(); + + vm.prank(deployer); + vm.deal(deployer, 2 ether); + walletDestination.singleDirectSingleVaultWithdraw{ value: 2 ether }(req); + + if (receive4626_) { + console.log("scWalletAtLiqDst_", scWalletAtLiqDst_); + + assertGt(IERC4626(IBaseForm(v.superform).getVaultAddress()).balanceOf(scWalletAtLiqDst_), 0); + } + } + + function _xChainWithdraw_SmartContractWallet( + uint64 liqDstChainId_, + address scWalletAtLiqDst_, + bool sameChainTxData_, + string memory vaultKind, + uint256 formImplId, + bool receive4626_ + ) + internal + { + /// scenario: user deposits with his own collateral and has approved enough tokens + vm.selectFork(FORKS[ETH]); + XChainWithdrawsInternalVars memory v; + v.superform = getContract( + ARBI, string.concat("DAI", vaultKind, "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[formImplId])) ); + v.superformId = DataLib.packSuperform(v.superform, FORM_IMPLEMENTATION_IDS[formImplId], ARBI); + SingleVaultSFData memory data = SingleVaultSFData( - superformId, + v.superformId, + 1e18, 1e18, 1000, - LiqRequest(txData, getContract(ARBI, "DAI"), address(0), 1, liqDstChainId_, 0), + LiqRequest( + _buildLiqBridgeTxData( + LiqBridgeTxDataArgs( + 1, + getContract(ARBI, "DAI"), + getContract(ARBI, "DAI"), + getContract(liqDstChainId_, "DAI"), + v.superform, + ARBI, + ETH, + liqDstChainId_, + false, + scWalletAtLiqDst_, + uint256(liqDstChainId_), + 1e18, + true, + /// @dev placeholder value, not used + 0, + 1, + 1, + 1 + ), + sameChainTxData_ + ), + getContract(ARBI, "DAI"), + address(0), + 1, + liqDstChainId_, + 0 + ), "", false, - false, + receive4626_, liqDstChainId_ != ETH ? scWalletAtLiqDst_ : address(walletDestination), + address(0), + /// this is a withdraw so it doesn't matter "" ); @@ -288,11 +571,15 @@ contract SuperformRouterAATest is ProtocolActions { ambIds_[0] = 1; ambIds_[1] = 2; + console.log("receiverAddress", data.receiverAddress); + SingleXChainSingleVaultStateReq memory req = SingleXChainSingleVaultStateReq(ambIds_, ARBI, data); /// @dev approves before call vm.prank(address(walletSource)); - SuperPositions(getContract(ETH, "SuperPositions")).increaseAllowance(superformRouter, superformId, 1e18); + SuperPositions(getContract(ETH, "SuperPositions")).increaseAllowance( + getContract(ETH, "SuperformRouter"), v.superformId, 1e18 + ); vm.recordLogs(); vm.prank(deployer); @@ -320,14 +607,20 @@ contract SuperformRouterAATest is ProtocolActions { vm.prank(deployer); CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).processPayload(2); - if (formImplId == 1) { + if (formImplId == 1 && !receive4626_) { vm.warp(block.timestamp + (86_400 * 5)); vm.prank(deployer); TimelockStateRegistry(getContract(ARBI, "TimelockStateRegistry")).finalizePayload{ value: 2 ether }(1, ""); } + + if (receive4626_) { + console.log("scWalletAtLiqDst_", scWalletAtLiqDst_); + + assertGt(IERC4626(IBaseForm(v.superform).getVaultAddress()).balanceOf(scWalletAtLiqDst_), 0); + } vm.selectFork(FORKS[ETH]); - assertEq(SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(address(walletSource), superformId), 0); + assertEq(SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(address(walletSource), v.superformId), 0); } } diff --git a/test/unit/superform-router/SuperformRouter.t.sol b/test/unit/superform-router/SuperformRouter.t.sol index 98afd6120..9fd65c6d3 100644 --- a/test/unit/superform-router/SuperformRouter.t.sol +++ b/test/unit/superform-router/SuperformRouter.t.sol @@ -11,6 +11,7 @@ contract SuperformRouterTest is ProtocolActions { address superformRouter; uint256[] superformIds; uint256[] amounts; + uint256[] outputAmounts; uint256[] maxSlippages; bool[] hasDstSwaps; bool[] retain4626s; @@ -47,12 +48,14 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, receiverAddress, + receiverAddress, "" ); @@ -80,12 +83,14 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, receiverAddress, + receiverAddress, "" ); @@ -115,12 +120,14 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, receiverAddress, + receiverAddress, "" ); @@ -153,6 +160,9 @@ contract SuperformRouterTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 100; @@ -167,7 +177,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -199,6 +219,9 @@ contract SuperformRouterTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 100; @@ -214,8 +237,9 @@ contract SuperformRouterTest is ProtocolActions { LiqRequest[] memory liqReq = new LiqRequest[](1); liqReq[0] = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); - MultiVaultSFData memory data = - MultiVaultSFData(superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, address(0), ""); + MultiVaultSFData memory data = MultiVaultSFData( + superformIds, amounts, outputAmounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, address(0), address(0), "" + ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -248,6 +272,10 @@ contract SuperformRouterTest is ProtocolActions { amounts[0] = 1e18; amounts[1] = 1e18; + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 1e18; + outputAmounts[1] = 1e18; + uint256[] memory maxSlippages = new uint256[](2); maxSlippages[0] = 100; maxSlippages[1] = 100; @@ -265,7 +293,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[1] = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -274,7 +312,7 @@ contract SuperformRouterTest is ProtocolActions { /// @dev approves before call MockERC20(getContract(ETH, "DAI")).approve(router, 1e18); - SuperRegistry(getContract(ETH, "SuperRegistry")).setVaultLimitPerTx(ETH, 1); + SuperRegistry(getContract(ETH, "SuperRegistry")).setVaultLimitPerDestination(ETH, 1); vm.expectRevert(Error.INVALID_SUPERFORMS_DATA.selector); SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectMultiVaultDeposit(req); @@ -301,6 +339,9 @@ contract SuperformRouterTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 100; @@ -312,11 +353,21 @@ contract SuperformRouterTest is ProtocolActions { bool[] memory retain4626s = new bool[](1); LiqRequest[] memory liqReq = new LiqRequest[](2); - liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); - liqReq[1] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); + liqReq[0] = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); + liqReq[1] = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -344,12 +395,14 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 10_001, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, receiverAddress, + receiverAddress, "" ); @@ -386,6 +439,9 @@ contract SuperformRouterTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 100; @@ -401,7 +457,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[1] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -430,6 +496,8 @@ contract SuperformRouterTest is ProtocolActions { uint256 amount = 1e18; + uint256 outputAmount = 1e18; + uint256 maxSlippage = 100; uint8[] memory ambIds = new uint8[](1); @@ -437,8 +505,9 @@ contract SuperformRouterTest is ProtocolActions { LiqRequest memory liqReq = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); - SingleVaultSFData memory data = - SingleVaultSFData(superformId, amount, maxSlippage, liqReq, "", false, false, receiverAddress, ""); + SingleVaultSFData memory data = SingleVaultSFData( + superformId, amount, outputAmount, maxSlippage, liqReq, "", false, false, receiverAddress, receiverAddress, "" + ); SingleXChainSingleVaultStateReq memory req = SingleXChainSingleVaultStateReq(ambIds, ETH, data); @@ -466,6 +535,8 @@ contract SuperformRouterTest is ProtocolActions { uint256 amount = 1e18; + uint256 outputAmount = 1e18; + uint256 maxSlippage = 100; uint8[] memory ambIds = new uint8[](1); @@ -473,8 +544,9 @@ contract SuperformRouterTest is ProtocolActions { LiqRequest memory liqReq = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); - SingleVaultSFData memory data = - SingleVaultSFData(superformId, amount, maxSlippage, liqReq, "", false, false, receiverAddress, ""); + SingleVaultSFData memory data = SingleVaultSFData( + superformId, amount, outputAmount, maxSlippage, liqReq, "", false, false, receiverAddress, receiverAddress, "" + ); SingleXChainSingleVaultStateReq memory req = SingleXChainSingleVaultStateReq(ambIds, ARBI, data); @@ -505,6 +577,9 @@ contract SuperformRouterTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](0); uint8[] memory ambIds = new uint8[](1); @@ -518,7 +593,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -545,6 +630,9 @@ contract SuperformRouterTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](0); uint8[] memory ambIds = new uint8[](1); @@ -558,7 +646,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -587,6 +685,9 @@ contract SuperformRouterTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 100; @@ -601,7 +702,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -629,6 +740,9 @@ contract SuperformRouterTest is ProtocolActions { /// @dev 0 amounts length uint256[] memory amounts = new uint256[](0); + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 100; @@ -643,7 +757,72 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" + ); + + SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); + address superformRouter = getContract(ETH, "SuperformRouter"); + /// @dev approves before call + MockERC20(getContract(ETH, "DAI")).approve(superformRouter, 1e18); + + vm.expectRevert(Error.INVALID_SUPERFORMS_DATA.selector); + SuperformRouter(payable(superformRouter)).singleXChainMultiVaultDeposit(req); + } + + function test_depositWithWrongOutputAmountsLength() public { + vm.selectFork(FORKS[ETH]); + vm.startPrank(deployer); + + address superform = getContract( + ARBI, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ); + + uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[0], ARBI); + + uint256[] memory superformIds = new uint256[](1); + superformIds[0] = superformId; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1e18; + + /// @dev 0 amounts length + uint256[] memory outputAmounts = new uint256[](0); + + uint256[] memory maxSlippages = new uint256[](1); + maxSlippages[0] = 100; + + uint8[] memory ambIds = new uint8[](1); + ambIds[0] = 1; + + bool[] memory hasDstSwaps = new bool[](1); + + bool[] memory retain4626s = new bool[](1); + + LiqRequest[] memory liqReq = new LiqRequest[](1); + liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); + + MultiVaultSFData memory data = MultiVaultSFData( + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -673,6 +852,9 @@ contract SuperformRouterTest is ProtocolActions { amounts[1] = 1e18; /// @dev new amount + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 100; @@ -687,7 +869,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -715,6 +907,9 @@ contract SuperformRouterTest is ProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 1e18; + uint256[] memory outputAmounts = new uint256[](1); + outputAmounts[0] = 1e18; + uint256[] memory maxSlippages = new uint256[](1); maxSlippages[0] = 10_001; /// @dev invalid max slippage @@ -730,7 +925,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -783,8 +988,19 @@ contract SuperformRouterTest is ProtocolActions { liqReqs[1] = LiqRequest("", getContract(ETH, "WETH"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReqs, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + amounts, + maxSlippages, + liqReqs, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); + uint8[] memory ambIds = new uint8[](2); ambIds[0] = 1; ambIds[1] = 2; @@ -838,7 +1054,82 @@ contract SuperformRouterTest is ProtocolActions { liqReqs[1] = LiqRequest("", getContract(ETH, "WETH"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReqs, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + maxSlippages, + maxSlippages, + liqReqs, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" + ); + uint8[] memory ambIds = new uint8[](2); + ambIds[0] = 1; + ambIds[1] = 2; + + SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); + + /// @dev approves before call + MockERC20(getContract(ETH, "DAI")).approve(superformRouter, 1e18); + MockERC20(getContract(ETH, "WETH")).approve(superformRouter, 1e18); + + vm.expectRevert(Error.INVALID_SUPERFORMS_DATA.selector); + SuperformRouter(payable(superformRouter)).singleXChainMultiVaultWithdraw{ value: 2 ether }(req); + } + + function test_withdrawWithWrongOutputAmountsLength() public { + _successfulMultiVaultDeposit(); + + /// scenario: user deposits with his own token and has approved enough tokens + vm.selectFork(FORKS[ETH]); + vm.startPrank(deployer); + + address superformRouter = getContract(ETH, "SuperformRouter"); + + address superform1 = getContract( + ARBI, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ); + + address superform2 = getContract( + ARBI, string.concat("WETH", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ); + + uint256 superformId1 = DataLib.packSuperform(superform1, FORM_IMPLEMENTATION_IDS[0], ARBI); + uint256 superformId2 = DataLib.packSuperform(superform2, FORM_IMPLEMENTATION_IDS[0], ARBI); + + uint256[] memory superformIds = new uint256[](2); + superformIds[0] = superformId1; + superformIds[1] = superformId2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1e18; + amounts[1] = 1e18; + + uint256[] memory outputAmounts = new uint256[](0); + + bool[] memory hasDstSwaps = new bool[](2); + + bool[] memory retain4626s = new bool[](2); + + LiqRequest[] memory liqReqs = new LiqRequest[](2); + liqReqs[0] = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); + liqReqs[1] = LiqRequest("", getContract(ETH, "WETH"), address(0), 1, ETH, 0); + + MultiVaultSFData memory data = MultiVaultSFData( + superformIds, + amounts, + outputAmounts, + amounts, + liqReqs, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); uint8[] memory ambIds = new uint8[](2); ambIds[0] = 1; @@ -895,7 +1186,17 @@ contract SuperformRouterTest is ProtocolActions { liqReqs[1] = LiqRequest("", getContract(ETH, "WETH"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReqs, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + amounts, + maxSlippages, + liqReqs, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); uint8[] memory ambIds = new uint8[](2); ambIds[0] = 1; @@ -925,12 +1226,14 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, receiverAddress, + receiverAddress, "" ); @@ -960,13 +1263,15 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 0, - /// @dev 0 amount here and in the LiqRequest + /// @dev 0 amount here and in the LiqRequest, + 1e18, 100, LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0), "", false, false, receiverAddress, + receiverAddress, "" ); @@ -979,6 +1284,43 @@ contract SuperformRouterTest is ProtocolActions { SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); } + function test_depositWithZeroOutputAmount() public { + /// scenario: deposit to an invalid super form id (which doesn't exist on the chain) + vm.selectFork(FORKS[ETH]); + vm.startPrank(deployer); + + /// try depositing without approval + address superform = getContract( + ETH, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ); + + uint256 superformId = DataLib.packSuperform(superform, FORM_IMPLEMENTATION_IDS[0], ETH); + + SingleVaultSFData memory data = SingleVaultSFData( + superformId, + 1e18, + 0, + 100, + LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 1e18), + "", + false, + false, + receiverAddress, + receiverAddress, + "" + ); + + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq(data); + + address router = getContract(ETH, "SuperformRouter"); + + /// @dev approves before call + MockERC20(getContract(ETH, "DAI")).approve(router, 1e18); + + vm.expectRevert(Error.INVALID_SUPERFORMS_DATA.selector); + SuperformRouter(payable(getContract(ETH, "SuperformRouter"))).singleDirectSingleVaultDeposit(req); + } + function test_withdrawMultiVaultXChain_InvalidAction() public { /// scenario: withdraw from an paused form form id (which doesn't exist on the chain) vm.selectFork(FORKS[ETH]); @@ -1019,7 +1361,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + amounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ETH, data); @@ -1072,7 +1424,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + amounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -1137,7 +1499,17 @@ contract SuperformRouterTest is ProtocolActions { liqReq[0] = LiqRequest("", getContract(ARBI, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReq, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + amounts, + maxSlippages, + liqReq, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(ambIds, ARBI, data); @@ -1167,6 +1539,7 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest( _buildDummyTxDataUnitTests( @@ -1192,6 +1565,7 @@ contract SuperformRouterTest is ProtocolActions { false, false, receiverAddress, + receiverAddress, "" ); @@ -1234,6 +1608,10 @@ contract SuperformRouterTest is ProtocolActions { v.amounts[0] = 1e18; v.amounts[1] = 1e18; + v.outputAmounts = new uint256[](2); + v.outputAmounts[0] = 1e18; + v.outputAmounts[1] = 1e18; + v.maxSlippages = new uint256[](2); v.maxSlippages[0] = 1000; v.maxSlippages[1] = 1000; @@ -1296,7 +1674,17 @@ contract SuperformRouterTest is ProtocolActions { ); MultiVaultSFData memory data = MultiVaultSFData( - v.superformIds, v.amounts, v.maxSlippages, v.liqReqs, "", v.hasDstSwaps, v.retain4626s, receiverAddress, "" + v.superformIds, + v.amounts, + v.outputAmounts, + v.maxSlippages, + v.liqReqs, + "", + v.hasDstSwaps, + v.retain4626s, + receiverAddress, + receiverAddress, + "" ); v.ambIds = new uint8[](1); v.ambIds[0] = 1; @@ -1326,6 +1714,7 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 100, LiqRequest( _buildDummyTxDataUnitTests( @@ -1351,6 +1740,7 @@ contract SuperformRouterTest is ProtocolActions { false, false, receiverAddress, + receiverAddress, "" ); @@ -1381,6 +1771,7 @@ contract SuperformRouterTest is ProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 1e18, + 1e18, 10_001, /// @dev invalid slippage LiqRequest( @@ -1407,6 +1798,7 @@ contract SuperformRouterTest is ProtocolActions { false, false, receiverAddress, + receiverAddress, "" ); @@ -1449,6 +1841,10 @@ contract SuperformRouterTest is ProtocolActions { v.amounts[0] = 1e18; v.amounts[1] = 1e18; + v.outputAmounts = new uint256[](2); + v.outputAmounts[0] = 1e18; + v.outputAmounts[1] = 1e18; + v.maxSlippages = new uint256[](2); v.maxSlippages[0] = 1000; v.maxSlippages[1] = 1000; @@ -1511,7 +1907,17 @@ contract SuperformRouterTest is ProtocolActions { ); MultiVaultSFData memory data = MultiVaultSFData( - v.superformIds, v.amounts, v.maxSlippages, v.liqReqs, "", v.hasDstSwaps, v.retain4626s, receiverAddress, "" + v.superformIds, + v.amounts, + v.outputAmounts, + v.maxSlippages, + v.liqReqs, + "", + v.hasDstSwaps, + v.retain4626s, + receiverAddress, + receiverAddress, + "" ); v.ambIds = new uint8[](1); v.ambIds[0] = 1; @@ -1525,6 +1931,138 @@ contract SuperformRouterTest is ProtocolActions { vm.stopPrank(); } + function test_depositMultiVaultWithRepeatedInterimTokens() public { + MultiVaultDepositVars memory v; + + /// scenario: user deposits with his own token and has approved enough tokens + vm.selectFork(FORKS[ETH]); + vm.startPrank(deployer); + + v.superformRouter = getContract(ETH, "SuperformRouter"); + + v.superformIds = new uint256[](2); + v.superformIds[0] = DataLib.packSuperform( + getContract( + ARBI, string.concat("DAI", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ), + FORM_IMPLEMENTATION_IDS[0], + ARBI + ); + v.superformIds[1] = DataLib.packSuperform( + getContract( + ARBI, string.concat("WETH", "VaultMock", "Superform", Strings.toString(FORM_IMPLEMENTATION_IDS[0])) + ), + FORM_IMPLEMENTATION_IDS[0], + ARBI + ); + + v.amounts = new uint256[](2); + v.amounts[0] = 1e18; + v.amounts[1] = 1e18; + + v.outputAmounts = new uint256[](2); + v.outputAmounts[0] = 1e18; + v.outputAmounts[1] = 1e18; + + v.maxSlippages = new uint256[](2); + v.maxSlippages[0] = 1000; + v.maxSlippages[1] = 1000; + + v.hasDstSwaps = new bool[](2); + v.hasDstSwaps[0] = true; + v.hasDstSwaps[1] = true; + + v.retain4626s = new bool[](2); + + v.liqBridgeTxDataArgs = LiqBridgeTxDataArgs( + 1, + getContract(ETH, "DAI"), + getContract(ETH, "DAI"), + getContract(ARBI, "USDC"), + v.superformRouter, + ETH, + ARBI, + ARBI, + true, + getContract(ARBI, "DstSwapper"), + uint256(ARBI), + 1e18, + //1e18, + false, + /// @dev placeholder value, not used + 0, + 1, + 1, + 1 + ); + + v.liqReqs = new LiqRequest[](2); + v.liqReqs[0] = LiqRequest( + _buildLiqBridgeTxData(v.liqBridgeTxDataArgs, false), + getContract(ETH, "DAI"), + getContract(ARBI, "DAI"), + 1, + ARBI, + 0 + ); + + v.liqBridgeTxDataArgs = LiqBridgeTxDataArgs( + 1, + getContract(ETH, "DAI"), + getContract(ETH, "DAI"), + getContract(ARBI, "USDC"), + v.superformRouter, + ETH, + ARBI, + ARBI, + false, + getContract(ARBI, "DstSwapper"), + uint256(ARBI), + 1e18, + //1e18, + false, + /// @dev placeholder value, not used + 0, + 1, + 1, + 1 + ); + + v.liqReqs[1] = LiqRequest( + _buildLiqBridgeTxData(v.liqBridgeTxDataArgs, false), + getContract(ETH, "WETH"), + getContract(ARBI, "DAI"), + 1, + ARBI, + 0 + ); + + MultiVaultSFData memory data = MultiVaultSFData( + v.superformIds, + v.amounts, + v.outputAmounts, + v.maxSlippages, + v.liqReqs, + "", + v.hasDstSwaps, + v.retain4626s, + receiverAddress, + receiverAddress, + "" + ); + v.ambIds = new uint8[](1); + v.ambIds[0] = 1; + + SingleXChainMultiVaultStateReq memory req = SingleXChainMultiVaultStateReq(v.ambIds, ARBI, data); + + /// @dev approves before call + MockERC20(getContract(ETH, "DAI")).approve(v.superformRouter, 2e18); + + vm.expectRevert(Error.INVALID_SUPERFORMS_DATA.selector); + SuperformRouter(payable(v.superformRouter)).singleXChainMultiVaultDeposit{ value: 2 ether }(req); + vm.stopPrank(); + } + function test_multiVaultTokenForward_withPermit2_passing() public { MultiVaultDepositVars memory v; /// @dev in this test no tokens would be bridged (no txData) @@ -1553,6 +2091,10 @@ contract SuperformRouterTest is ProtocolActions { v.amounts[0] = 1e18; v.amounts[1] = 1e18; + v.outputAmounts = new uint256[](2); + v.outputAmounts[0] = 1e18; + v.outputAmounts[1] = 1e18; + v.maxSlippages = new uint256[](2); v.maxSlippages[0] = 1000; v.maxSlippages[1] = 1000; @@ -1632,6 +2174,7 @@ contract SuperformRouterTest is ProtocolActions { MultiVaultSFData( v.superformIds, v.amounts, + v.outputAmounts, v.maxSlippages, v.liqReqs, abi.encode( @@ -1640,6 +2183,7 @@ contract SuperformRouterTest is ProtocolActions { v.hasDstSwaps, v.retain4626s, receiverAddress, + receiverAddress, "" ) ) @@ -1675,6 +2219,10 @@ contract SuperformRouterTest is ProtocolActions { v.amounts[0] = 1e18; v.amounts[1] = 1e18; + v.outputAmounts = new uint256[](2); + v.outputAmounts[0] = 1e18; + v.outputAmounts[1] = 1e18; + v.maxSlippages = new uint256[](2); v.maxSlippages[0] = 1000; v.maxSlippages[1] = 1000; @@ -1756,12 +2304,14 @@ contract SuperformRouterTest is ProtocolActions { MultiVaultSFData( v.superformIds, v.amounts, + v.outputAmounts, v.maxSlippages, v.liqReqs, abi.encode(v.permit.nonce, v.permit.deadline, permitSigned), v.hasDstSwaps, v.retain4626s, receiverAddress, + receiverAddress, "" ) ) @@ -1797,6 +2347,10 @@ contract SuperformRouterTest is ProtocolActions { v.amounts[0] = 1e18; v.amounts[1] = 1e18; + v.outputAmounts = new uint256[](2); + v.outputAmounts[0] = 1e18; + v.outputAmounts[1] = 1e18; + v.maxSlippages = new uint256[](2); v.maxSlippages[0] = 1000; v.maxSlippages[1] = 1000; @@ -1872,12 +2426,14 @@ contract SuperformRouterTest is ProtocolActions { MultiVaultSFData( v.superformIds, v.amounts, + v.outputAmounts, v.maxSlippages, v.liqReqs, "", v.hasDstSwaps, v.retain4626s, receiverAddress, + receiverAddress, "" ) ) @@ -1885,7 +2441,7 @@ contract SuperformRouterTest is ProtocolActions { vm.stopPrank(); } - function test_multiVaultTokenForward_noTxData_withNormalApprove_DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE() public { + function test_multiVaultTokenForward_noTxData_withNormalApprove_INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT() public { MultiVaultDepositVars memory v; /// @dev in this test no tokens would be bridged (no txData) vm.selectFork(FORKS[ETH]); @@ -1913,6 +2469,10 @@ contract SuperformRouterTest is ProtocolActions { v.amounts[0] = 1e18; v.amounts[1] = 1e18; + v.outputAmounts = new uint256[](2); + v.outputAmounts[0] = 1e18; + v.outputAmounts[1] = 1e18; + v.maxSlippages = new uint256[](2); v.maxSlippages[0] = 1000; v.maxSlippages[1] = 1000; @@ -1980,7 +2540,7 @@ contract SuperformRouterTest is ProtocolActions { v.ambIds = new uint8[](1); v.ambIds[0] = 1; - vm.expectRevert(Error.DIRECT_DEPOSIT_INSUFFICIENT_ALLOWANCE.selector); + vm.expectRevert(Error.INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT.selector); SuperformRouter(payable(v.superformRouter)).singleXChainMultiVaultDeposit{ value: 2 ether }( SingleXChainMultiVaultStateReq( v.ambIds, @@ -1988,12 +2548,14 @@ contract SuperformRouterTest is ProtocolActions { MultiVaultSFData( v.superformIds, v.amounts, + v.outputAmounts, v.maxSlippages, v.liqReqs, "", v.hasDstSwaps, v.retain4626s, receiverAddress, + receiverAddress, "" ) ) @@ -2029,6 +2591,10 @@ contract SuperformRouterTest is ProtocolActions { v.amounts[0] = 1e18; v.amounts[1] = 1e18; + v.outputAmounts = new uint256[](2); + v.outputAmounts[0] = 1e18; + v.outputAmounts[1] = 1e18; + v.maxSlippages = new uint256[](2); v.maxSlippages[0] = 1000; v.maxSlippages[1] = 1000; @@ -2062,12 +2628,14 @@ contract SuperformRouterTest is ProtocolActions { MultiVaultSFData( v.superformIds, v.amounts, + v.outputAmounts, v.maxSlippages, v.liqReqs, v.permit2Data, v.hasDstSwaps, v.retain4626s, receiverAddress, + receiverAddress, "" ) ) @@ -2100,6 +2668,10 @@ contract SuperformRouterTest is ProtocolActions { amounts[0] = 1e18; amounts[1] = 1e18; + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 1e18; + outputAmounts[1] = 1e18; + uint256[] memory maxSlippages = new uint256[](2); maxSlippages[0] = 1000; maxSlippages[1] = 1000; @@ -2110,7 +2682,17 @@ contract SuperformRouterTest is ProtocolActions { liqReqs[1] = LiqRequest("", getContract(ETH, "DAI"), address(0), 1, ETH, 0); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReqs, "", new bool[](2), new bool[](2), receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReqs, + "", + new bool[](2), + new bool[](2), + receiverAddress, + receiverAddress, + "" ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -2146,6 +2728,10 @@ contract SuperformRouterTest is ProtocolActions { amounts[0] = 1e18; amounts[1] = 1e18; + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 1e18; + outputAmounts[1] = 1e18; + uint256[] memory maxSlippages = new uint256[](2); maxSlippages[0] = 1000; maxSlippages[1] = 1000; @@ -2160,7 +2746,17 @@ contract SuperformRouterTest is ProtocolActions { receive4626[1] = true; MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReqs, "", new bool[](2), receive4626, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReqs, + "", + new bool[](2), + receive4626, + receiverAddress, + receiverAddress, + "" ); SingleDirectMultiVaultStateReq memory req = SingleDirectMultiVaultStateReq(data); @@ -2220,33 +2816,49 @@ contract SuperformRouterTest is ProtocolActions { assertEq(MockERC20(getContract(ARBI, "DAI")).balanceOf(getContract(ARBI, "DstSwapper")), 1e18); } - function test_negativeDstSwapSlippageAndUpdateSwappedAmount() public { - /// case: where bridge 3 DAI, dst swapper swapped 3 DAI, updater updates 3 DAI - /// outcome: deposit goes through depositing 2 DAI and 1 DAI remains on CSR - uint256 superformId = _simulateXChainDepositWithNegativeSlippage(true, true, true); + function test_negativeDstSwapSlippageAndUpdateSwappedAmount_RevertsInvalidKeeperCall() public { + /// case: where bridge 3 DAI, dst swapper swapped 3 DAI (capped to 2) updater updates 3 DAI + /// outcome: deposit should revert on update + _simulateXChainDepositWithNegativeSlippage(true, true, true); + + /// @dev swapped tokens remain on DstSwapper + vm.selectFork(FORKS[ARBI]); + assertEq(MockERC20(getContract(ARBI, "DAI")).balanceOf(getContract(ARBI, "CoreStateRegistry")), 3e18); + } + + function test_negativeDstSwapSlippageAndUpdateSuperformDataAmount() public { + /// keeperUpdateExactAmount = false means keeper will update with the capped amount (2 DAI) + uint256 superformId = _simulateXChainDepositWithNegativeSlippage(true, true, false); /// @dev assert that the minted amount is the amount sent in superformData.amount vm.selectFork(FORKS[ETH]); assertEq(SuperPositions(getContract(ETH, "SuperPositions")).balanceOf(address(420), superformId), 2e18); - /// @dev swapped tokens live on CSR + /// @dev swapped tokens (remainder of negative slippage) remain on dstSwapper vm.selectFork(FORKS[ARBI]); assertEq(MockERC20(getContract(ARBI, "DAI")).balanceOf(getContract(ARBI, "CoreStateRegistry")), 1e18); } - function test_negativeDstSwapSlippageAndUpdateSuperformDataAmount() public { - /// case: where bridge 3 DAI, dst swapper swapped 3 DAI, updater updates 2 DAI - /// outcome: deposit should revert on update - _simulateXChainDepositWithNegativeSlippage(true, true, false); + function test_forwardDustToPaymaster_router() public { + vm.selectFork(FORKS[ETH]); + vm.startPrank(deployer); - /// @dev swapped tokens live on CSR forever - vm.selectFork(FORKS[ARBI]); - assertEq(MockERC20(getContract(ARBI, "DAI")).balanceOf(getContract(ARBI, "CoreStateRegistry")), 3e18); + address payable router = payable(getContract(ETH, "SuperformRouter")); + + address token = getContract(ETH, "DAI"); + /// @dev transfer 10 dai to router + deal(token, router, 10e18); + + vm.expectRevert(Error.ZERO_ADDRESS.selector); + SuperformRouter(router).forwardDustToPaymaster(address(0)); + + SuperformRouter(router).forwardDustToPaymaster(token); } struct SimulateUpdateTestLocalVars { SingleVaultSFData data; uint8[] ambIds; + address[] bridgedTokens; uint256[] amounts; uint256 nativeAmount; uint256 swapAmount; @@ -2280,6 +2892,7 @@ contract SuperformRouterTest is ProtocolActions { v.data = SingleVaultSFData( superformId, 2e18, + 2e18, 1000, LiqRequest( _buildLiqBridgeTxData( @@ -2306,7 +2919,7 @@ contract SuperformRouterTest is ProtocolActions { false ), getContract(ETH, "DAI"), - address(0), + getContract(ARBI, "DAI"), 1, ARBI, 0 @@ -2315,6 +2928,7 @@ contract SuperformRouterTest is ProtocolActions { hasDstSwap, false, address(420), + address(420), "" ); @@ -2322,7 +2936,6 @@ contract SuperformRouterTest is ProtocolActions { v.ambIds[0] = 1; v.ambIds[1] = 2; - v.data.liqRequest.interimToken = getContract(ARBI, "DAI"); SingleXChainSingleVaultStateReq memory req = SingleXChainSingleVaultStateReq(v.ambIds, ARBI, v.data); /// @dev approves before call @@ -2356,14 +2969,16 @@ contract SuperformRouterTest is ProtocolActions { vm.startPrank(deployer); v.amounts = new uint256[](1); - v.amounts[0] = keeperUpdateExactAmount ? 3e18 : 2e18; + v.amounts[0] = keeperUpdateExactAmount ? 3e18 : 2e18; // false -± 2e18 + + v.bridgedTokens = new address[](1); + v.bridgedTokens[0] = getContract(ARBI, "DAI"); - v.swapAmount = swapperSwapExactBridgeAmount ? 3e18 : 2e18; + v.swapAmount = swapperSwapExactBridgeAmount ? 3e18 : 2e18; // true -± 3e18 if (hasDstSwap) { DstSwapper(payable(getContract(ARBI, "DstSwapper"))).processTx( 1, - 0, 1, _buildLiqBridgeTxDataDstSwap( 1, @@ -2377,11 +2992,15 @@ contract SuperformRouterTest is ProtocolActions { ); } - if (hasDstSwap && !keeperUpdateExactAmount && swapperSwapExactBridgeAmount) { + if (hasDstSwap && keeperUpdateExactAmount && swapperSwapExactBridgeAmount) { vm.expectRevert(Error.INVALID_DST_SWAPPER_FAILED_SWAP.selector); - CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload(1, v.amounts); + CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload( + 1, v.bridgedTokens, v.amounts + ); } else { - CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload(1, v.amounts); + CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload( + 1, v.bridgedTokens, v.amounts + ); v.nativeAmount = PaymentHelper(getContract(ARBI, "PaymentHelper")).estimateAckCost(1); vm.recordLogs(); @@ -2441,6 +3060,10 @@ contract SuperformRouterTest is ProtocolActions { amounts[0] = 1e18; amounts[1] = 1e18; + uint256[] memory outputAmounts = new uint256[](2); + outputAmounts[0] = 1e18; + outputAmounts[1] = 1e18; + uint256[] memory maxSlippages = new uint256[](2); maxSlippages[0] = 1000; maxSlippages[1] = 1000; @@ -2503,7 +3126,17 @@ contract SuperformRouterTest is ProtocolActions { ); MultiVaultSFData memory data = MultiVaultSFData( - superformIds, amounts, maxSlippages, liqReqs, "", hasDstSwaps, retain4626s, receiverAddress, "" + superformIds, + amounts, + outputAmounts, + maxSlippages, + liqReqs, + "", + hasDstSwaps, + retain4626s, + receiverAddress, + receiverAddress, + "" ); uint8[] memory ambIds = new uint8[](2); ambIds[0] = 1; @@ -2573,7 +3206,7 @@ contract SuperformRouterTest is ProtocolActions { vm.recordLogs(); SuperformFactory(getContract(ARBI, "SuperformFactory")).changeFormImplementationPauseStatus{ value: 800 ether }( - formImplementationId, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(5, 1) + formImplementationId, ISuperformFactory.PauseStatus.PAUSED, generateBroadcastParams(0) ); _broadcastPayloadHelper(ARBI, vm.getRecordedLogs()); diff --git a/test/utils/AmbParams.sol b/test/utils/AmbParams.sol index 49407ca31..ba4180ec0 100644 --- a/test/utils/AmbParams.sol +++ b/test/utils/AmbParams.sol @@ -3,10 +3,10 @@ import { AMBExtraData, BroadCastAMBExtraData } from "src/types/DataTypes.sol"; pragma solidity ^0.8.23; -function generateBroadcastParams(uint256, uint256) pure returns (bytes memory) { +function generateBroadcastParams(uint256 gasFee_) pure returns (bytes memory) { uint8 ambId = 4; - uint256 gasFee = 0; + uint256 gasFee = gasFee_; bytes memory extraData; return abi.encode(ambId, abi.encode(gasFee, extraData)); diff --git a/test/utils/BaseSetup.sol b/test/utils/BaseSetup.sol index 5ba384dd0..942862b2a 100644 --- a/test/utils/BaseSetup.sol +++ b/test/utils/BaseSetup.sol @@ -18,6 +18,8 @@ import { Strings } from "openzeppelin-contracts/contracts/utils/Strings.sol"; import { LiFiMock } from "../mocks/LiFiMock.sol"; import { SocketMock } from "../mocks/SocketMock.sol"; import { SocketOneInchMock } from "../mocks/SocketOneInchMock.sol"; +import { LiFiMockRugpull } from "../mocks/LiFiMockRugpull.sol"; +import { LiFiMockBlacklisted } from "../mocks/LiFiMockBlacklisted.sol"; import { MockERC20 } from "../mocks/MockERC20.sol"; import { VaultMock } from "../mocks/VaultMock.sol"; @@ -67,7 +69,6 @@ import { PaymentHelper } from "src/payments/PaymentHelper.sol"; import { IPaymentHelper } from "src/interfaces/IPaymentHelper.sol"; import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; - import { Error } from "src/libraries/Error.sol"; import "src/types/DataTypes.sol"; import "./TestTypes.sol"; @@ -298,7 +299,7 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { mapping( uint64 chainId => mapping( - uint32 formBeaconId + uint32 formImplementationId => mapping(string underlying => mapping(uint256 vaultKindIndex => address realVault)) ) ) public REAL_VAULT_ADDRESS; @@ -491,6 +492,8 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { contracts[vars.chainId][bytes32(bytes("WormholeARImplementation"))] = vars.wormholeImplementation; WormholeARImplementation(vars.wormholeImplementation).setWormholeRelayer(wormholeRelayer); + /// set refund chain id to wormhole chain id + WormholeARImplementation(vars.wormholeImplementation).setRefundChainId(wormhole_chainIds[i]); /// @dev 6.5- deploy Wormhole Specialized Relayer Implementation vars.wormholeSRImplementation = address(new WormholeSRImplementation{ salt: salt }(vars.superRegistryC)); @@ -522,6 +525,17 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { contracts[vars.chainId][bytes32(bytes("SocketOneInchMock"))] = vars.socketOneInch; vm.allowCheatcodes(vars.socketOneInch); + /// @dev 7.1.4 deploy LiFiMockRugpull. This mock tests a behaviour where the bridge is malicious and tries + /// to steal tokens + vars.liFiMockRugpull = address(new LiFiMockRugpull{ salt: salt }()); + contracts[vars.chainId][bytes32(bytes("LiFiMockRugpull"))] = vars.liFiMockRugpull; + vm.allowCheatcodes(vars.liFiMockRugpull); + + /// @dev 7.1.5 deploy LiFiMockBlacklisted. This mock tests the behaviour of blacklisted selectors + vars.liFiMockBlacklisted = address(new LiFiMockBlacklisted{ salt: salt }()); + contracts[vars.chainId][bytes32(bytes("LiFiMockBlacklisted"))] = vars.liFiMockBlacklisted; + vm.allowCheatcodes(vars.liFiMockBlacklisted); + /// @dev 7.2.1- deploy lifi validator vars.lifiValidator = address(new LiFiValidator{ salt: salt }(vars.superRegistry)); contracts[vars.chainId][bytes32(bytes("LiFiValidator"))] = vars.lifiValidator; @@ -541,10 +555,14 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { bridgeAddresses.push(vars.lifiRouter); bridgeAddresses.push(vars.socketRouter); bridgeAddresses.push(vars.socketOneInch); + bridgeAddresses.push(vars.liFiMockRugpull); + bridgeAddresses.push(vars.liFiMockBlacklisted); bridgeValidators.push(vars.lifiValidator); bridgeValidators.push(vars.socketValidator); bridgeValidators.push(vars.socketOneInchValidator); + bridgeValidators.push(vars.lifiValidator); + bridgeValidators.push(vars.lifiValidator); /// @dev 8.1 - Deploy UNDERLYING_TOKENS and VAULTS for (uint256 j = 0; j < UNDERLYING_TOKENS.length; ++j) { @@ -629,11 +647,13 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { contracts[vars.chainId][bytes32(bytes("ERC4626KYCDaoForm"))] = vars.kycDao4626Form; /// @dev 11 - Add newly deployed form implementations to Factory - ISuperformFactory(vars.factory).addFormImplementation(vars.erc4626Form, FORM_IMPLEMENTATION_IDS[0]); + ISuperformFactory(vars.factory).addFormImplementation(vars.erc4626Form, FORM_IMPLEMENTATION_IDS[0], 1); - ISuperformFactory(vars.factory).addFormImplementation(vars.erc4626TimelockForm, FORM_IMPLEMENTATION_IDS[1]); + ISuperformFactory(vars.factory).addFormImplementation( + vars.erc4626TimelockForm, FORM_IMPLEMENTATION_IDS[1], 2 + ); - ISuperformFactory(vars.factory).addFormImplementation(vars.kycDao4626Form, FORM_IMPLEMENTATION_IDS[2]); + ISuperformFactory(vars.factory).addFormImplementation(vars.kycDao4626Form, FORM_IMPLEMENTATION_IDS[2], 1); /// @dev 12 - Deploy SuperformRouter vars.superformRouter = address(new SuperformRouter{ salt: salt }(vars.superRegistry)); @@ -642,8 +662,12 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { vars.superRegistryC.setAddress(vars.superRegistryC.SUPERFORM_ROUTER(), vars.superformRouter, vars.chainId); /// @dev 13 - Deploy SuperPositions - vars.superPositions = - address(new SuperPositions{ salt: salt }("https://apiv2-dev.superform.xyz/", vars.superRegistry)); + vars.superPositions = address( + new SuperPositions{ salt: salt }( + "https://ipfs-gateway.superform.xyz/ipns/k51qzi5uqu5dg90fqdo9j63m556wlddeux4mlgyythp30zousgh3huhyzouyq8/JSON/", + vars.superRegistry + ) + ); contracts[vars.chainId][bytes32(bytes("SuperPositions"))] = vars.superPositions; vars.superRegistryC.setAddress(vars.superRegistryC.SUPER_POSITIONS(), vars.superPositions, vars.chainId); @@ -684,6 +708,8 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { vars.superRegistryC.setAddress(vars.superRegistryC.CORE_REGISTRY_RESCUER(), deployer, vars.chainId); vars.superRegistryC.setAddress(vars.superRegistryC.CORE_REGISTRY_DISPUTER(), deployer, vars.chainId); vars.superRegistryC.setAddress(vars.superRegistryC.DST_SWAPPER_PROCESSOR(), deployer, vars.chainId); + vars.superRegistryC.setAddress(vars.superRegistryC.SUPERFORM_RECEIVER(), deployer, vars.chainId); + vars.superRegistryC.setDelay(86_400); /// @dev 17 deploy emergency queue vars.emergencyQueue = address(new EmergencyQueue{ salt: salt }(vars.superRegistry)); @@ -709,7 +735,7 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { vars.superRegistry = getContract(vars.chainId, "SuperRegistry"); vars.paymentHelper = getContract(vars.chainId, "PaymentHelper"); vars.superRegistryC = SuperRegistry(payable(vars.superRegistry)); - vars.superRegistryC.setVaultLimitPerTx(vars.chainId, 5); + vars.superRegistryC.setVaultLimitPerDestination(vars.chainId, 5); /// @dev Set all trusted remotes for each chain, configure amb chains ids, setupQuorum for all chains as 1 /// and setup PaymentHelper @@ -765,7 +791,7 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { ); vars.superRegistryC.setRequiredMessagingQuorum(vars.dstChainId, 1); - vars.superRegistryC.setVaultLimitPerTx(vars.dstChainId, 5); + vars.superRegistryC.setVaultLimitPerDestination(vars.dstChainId, 5); /// swap gas cost: 50000 /// update gas cost: 40000 @@ -786,12 +812,13 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { 28 gwei, 750, 10_000, + 10_000, 10_000 ) ); - /// @dev 0.01 ether is just a mock value. Wormhole fees are currently 0 + /// @dev 0.01 ether is just a mock value. Wormhole fees are currently 0 on mainnet PaymentHelper(payable(vars.paymentHelper)).updateRegisterAERC20Params( - 0.01 ether, generateBroadcastParams(5, 1) + generateBroadcastParams(0.01 ether) ); vars.superRegistryC.setAddress( @@ -884,6 +911,7 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { vars.superRegistryC.setAddress( vars.superRegistryC.DST_SWAPPER_PROCESSOR(), deployer, vars.dstChainId ); + vars.superRegistryC.setAddress(vars.superRegistryC.SUPERFORM_RECEIVER(), deployer, vars.dstChainId); } else { /// ack gas cost: 40000 /// timelock step form cost: 50000 @@ -893,6 +921,7 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { ); PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 10, abi.encode(40_000)); PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 11, abi.encode(50_000)); + PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain(vars.chainId, 12, abi.encode(10_000)); PaymentHelper(payable(vars.paymentHelper)).updateRemoteChain( vars.chainId, 8, abi.encode(50 * 10 ** 9 wei) ); @@ -919,7 +948,7 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { if (FORM_IMPLEMENTATION_IDS[j] == 3) { /// mint a kycDAO Nft to the newly kycDAO superform - KYCDaoNFTMock(getContract(chainIds[i], "KYCDAOMock")).mint(vars.superform); + ERC4626KYCDaoForm(vars.superform).mintKYC(1); } contracts[chainIds[i]][bytes32( @@ -1092,9 +1121,13 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { /// 1 is lifi /// 2 is socket /// 3 is socket one inch impl + /// 4 is lifi rugpull + /// 5 is lifi blacklist bridgeIds.push(1); bridgeIds.push(2); bridgeIds.push(3); + bridgeIds.push(4); + bridgeIds.push(5); /// @dev setup users userKeys.push(1); @@ -1170,7 +1203,7 @@ abstract contract BaseSetup is DSTest, StdInvariant, Test { mapping( uint64 chainId => mapping( - uint32 formBeaconId + uint32 formImplementationId => mapping(string underlying => mapping(uint256 vaultKindIndex => address realVault)) ) ) storage existingVaults = REAL_VAULT_ADDRESS; diff --git a/test/utils/CommonProtocolActions.sol b/test/utils/CommonProtocolActions.sol index 36222cd6a..2fad234d5 100644 --- a/test/utils/CommonProtocolActions.sol +++ b/test/utils/CommonProtocolActions.sol @@ -45,7 +45,9 @@ abstract contract CommonProtocolActions is BaseSetup { view returns (bytes memory txData) { - if (args.liqBridgeKind == 1) { + /// @dev note: 4 is added here to test a bridge acting maliciously (check + /// test_maliciousBridge_protectionAgainstTokenDrain) + if (args.liqBridgeKind == 1 || args.liqBridgeKind == 4) { if (!sameChain) { ILiFi.BridgeData memory bridgeData; LibSwap.SwapData[] memory swapData = new LibSwap.SwapData[](1); @@ -296,9 +298,7 @@ abstract contract CommonProtocolActions is BaseSetup { address(0), /// @dev callTo (approveTo) sendingTokenDst_, - /// @dev in dst swap, assumes a swap between same token - FIXME receivingTokenDst_, - /// @dev in dst swap, assumes a swap between same token - FIXME amount_, /// @dev _buildLiqBridgeTxDataDstSwap() will only be called when DstSwap is true /// @dev and dstswap means cross-chain (last arg) diff --git a/test/utils/InvariantProtocolActions.sol b/test/utils/InvariantProtocolActions.sol index 87dda7fa9..145c504b3 100644 --- a/test/utils/InvariantProtocolActions.sol +++ b/test/utils/InvariantProtocolActions.sol @@ -228,6 +228,7 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { vars.underlyingDstToken, vars.targetSuperformIds, vars.amounts, + vars.amounts, vars.liqBridges, vars.receive4626, 1000, @@ -272,8 +273,10 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { vars.toDst[0], vars.underlyingSrcToken[0], vars.underlyingDstToken[0], + action.dstSwap ? getContract(vars.DST_CHAINS[i], UNDERLYING_TOKENS[0]) : address(0), vars.targetSuperformIds[0], finalAmount, + finalAmount, vars.liqBridges[0], vars.receive4626[0], 1000, @@ -704,49 +707,15 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { ); if (action.testType == TestType.Pass) { - if (action.dstSwap) { - /// @dev calling state variables again to obtain fresh memory values corresponding to - /// DST - (,, vars.underlyingDstToken,) = _targetVaults( - vars.CHAIN_0, - vars.DST_CHAINS[i], - vars.targetVaults[i], - vars.targetFormKinds[i], - vars.targetUnderlyings[i] - ); - vars.liqBridges = vars.targetLiqBridges[i]; - - /// @dev dst swap is performed to ensure tokens reach CoreStateRegistry on deposits - if (action.multiVaults) { - vars.amounts = vars.targetAmounts[i]; - _batchProcessDstSwap( - vars.liqBridges, - vars.CHAIN_0, - aV[i].toChainId, - vars.underlyingDstToken, - vars.multiVaultsPayloadArg.amounts, - action.slippage - ); - } else { - _processDstSwap( - vars.liqBridges[0], - vars.CHAIN_0, - aV[i].toChainId, - vars.underlyingDstToken[0], - vars.singleVaultsPayloadArg.amount, - action.slippage - ); - } - } - /// @dev this is the step where the amounts are updated taking into account the final /// slippage if (action.multiVaults) { - _updateMultiVaultDepositPayload(vars.multiVaultsPayloadArg); + _updateMultiVaultDepositPayload(vars.multiVaultsPayloadArg, vars.targetUnderlyings[i]); } else if (singleSuperformsData.length > 0) { - _updateSingleVaultDepositPayload(vars.singleVaultsPayloadArg); + _updateSingleVaultDepositPayload( + vars.singleVaultsPayloadArg, vars.targetUnderlyings[i][0] + ); } - vm.recordLogs(); /// @dev payload processing. This performs the action down to the form level and builds any /// acknowledgement data needed to bring it back to source @@ -761,9 +730,11 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { } else if (action.testType == TestType.RevertProcessPayload) { /// @dev this logic is essentially repeated from above if (action.multiVaults) { - _updateMultiVaultDepositPayload(vars.multiVaultsPayloadArg); + _updateMultiVaultDepositPayload(vars.multiVaultsPayloadArg, vars.targetUnderlyings[i]); } else if (singleSuperformsData.length > 0) { - _updateSingleVaultDepositPayload(vars.singleVaultsPayloadArg); + _updateSingleVaultDepositPayload( + vars.singleVaultsPayloadArg, vars.targetUnderlyings[i][0] + ); } /// @dev process payload will revert in here success = _processPayload( @@ -778,9 +749,13 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { ) { /// @dev branch used just for reverts of updatePayload (process payload is not even called) if (action.multiVaults) { - success = _updateMultiVaultDepositPayload(vars.multiVaultsPayloadArg); + success = _updateMultiVaultDepositPayload( + vars.multiVaultsPayloadArg, vars.targetUnderlyings[i] + ); } else { - success = _updateSingleVaultDepositPayload(vars.singleVaultsPayloadArg); + success = _updateSingleVaultDepositPayload( + vars.singleVaultsPayloadArg, vars.targetUnderlyings[i][0] + ); } if (!success) { @@ -874,9 +849,16 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { if (len == 0) revert LEN_MISMATCH(); uint256[] memory finalAmounts = new uint256[](len); uint256[] memory maxSlippageTemp = new uint256[](len); + address uniqueInterimToken; + for (uint256 i = 0; i < len; ++i) { finalAmounts[i] = args.amounts[i]; + if (i < 3 && args.dstSwap && args.action != Actions.Withdraw) { + /// @dev hack to support unique interim tokens -assuming dst swap scenario cases have less than 3 vaults + uniqueInterimToken = getContract(args.toChainId, UNDERLYING_TOKENS[i]); + } + /// @dev FOR TESTING AND MAINNET:: in sameChain actions, slippage is encoded in the request with the amount /// (extracted from bridge api) if ( @@ -899,8 +881,10 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { args.toDst[i], args.underlyingTokens[i], args.underlyingTokensDst[i], + uniqueInterimToken, args.superformIds[i], finalAmounts[i], + finalAmounts[i], args.liqBridges[i], args.receive4626[i], args.maxSlippage, @@ -922,6 +906,7 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { } liqRequests[i] = superformData.liqRequest; + if (args.dstSwap && args.action != Actions.Withdraw) liqRequests[i].interimToken = uniqueInterimToken; maxSlippageTemp[i] = args.maxSlippage; v.totalAmount += finalAmounts[i]; @@ -950,12 +935,15 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { superformsData = MultiVaultSFData( args.superformIds, finalAmounts, + args.outputAmounts, maxSlippageTemp, liqRequests, v.permit2data, hasDstSwap, args.receive4626, users[args.user], + users[args.user], + /// @dev repeat user for receiverAddressSP - not testing AA here abi.encode(false) ); } @@ -970,8 +958,13 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { uint256 decimal1; uint256 decimal2; uint256 decimal3; + uint256 decimal4; uint256 amountTemp; uint256 amount; + int256 USDPerUnderlyingOrInterimTokenDst; + int256 USDPerUnderlyingTokenDst; + int256 USDPerExternalToken; + int256 USDPerUnderlyingToken; LiqRequest liqReq; } @@ -988,17 +981,34 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { v.from = args.fromSrc; /// @dev build permit2 calldata vm.selectFork(FORKS[args.toChainId]); - v.decimal2 = args.underlyingTokenDst != NATIVE_TOKEN ? MockERC20(args.underlyingTokenDst).decimals() : 18; + /// @dev decimals of interimToken in case it exists (dstSwaps), otherwise decimals of final token + /// (underlyingToken) + /// @dev hack for when args.dstSwap == true + if (args.uniqueInterimToken != address(0)) { + v.decimal2 = args.uniqueInterimToken != NATIVE_TOKEN ? MockERC20(args.uniqueInterimToken).decimals() : 18; - (, int256 USDPerUnderlyingTokenDst,,,) = - AggregatorV3Interface(tokenPriceFeeds[args.toChainId][args.underlyingTokenDst]).latestRoundData(); + (, v.USDPerUnderlyingOrInterimTokenDst,,,) = + AggregatorV3Interface(tokenPriceFeeds[args.toChainId][args.uniqueInterimToken]).latestRoundData(); + + v.decimal4 = args.underlyingTokenDst != NATIVE_TOKEN ? MockERC20(args.underlyingTokenDst).decimals() : 18; + + (, v.USDPerUnderlyingTokenDst,,,) = + AggregatorV3Interface(tokenPriceFeeds[args.toChainId][args.underlyingTokenDst]).latestRoundData(); + } else { + v.decimal2 = args.underlyingTokenDst != NATIVE_TOKEN ? MockERC20(args.underlyingTokenDst).decimals() : 18; + + (, v.USDPerUnderlyingOrInterimTokenDst,,,) = + AggregatorV3Interface(tokenPriceFeeds[args.toChainId][args.underlyingTokenDst]).latestRoundData(); + } vm.selectFork(FORKS[args.srcChainId]); + /// @dev decimals of externalToken v.decimal1 = args.externalToken != NATIVE_TOKEN ? MockERC20(args.externalToken).decimals() : 18; + /// @dev decimals of underlyingToken on source v.decimal3 = args.underlyingToken != NATIVE_TOKEN ? MockERC20(args.underlyingToken).decimals() : 18; - (, int256 USDPerExternalToken,,,) = + (, v.USDPerExternalToken,,,) = AggregatorV3Interface(tokenPriceFeeds[args.srcChainId][args.externalToken]).latestRoundData(); - (, int256 USDPerUnderlyingToken,,,) = + (, v.USDPerUnderlyingToken,,,) = AggregatorV3Interface(tokenPriceFeeds[args.srcChainId][args.underlyingToken]).latestRoundData(); /// @dev this is to attach v.amount pre dst slippage with the correct decimals to avoid intermediary truncation @@ -1018,7 +1028,7 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { args.liqBridge, args.externalToken, args.underlyingToken, - args.underlyingTokenDst, + args.uniqueInterimToken != address(0) ? args.uniqueInterimToken : args.underlyingTokenDst, v.from, args.srcChainId, args.toChainId, @@ -1030,9 +1040,9 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { //v.amount, false, args.slippage, - uint256(USDPerExternalToken), - uint256(USDPerUnderlyingTokenDst), - uint256(USDPerUnderlyingToken) + uint256(v.USDPerExternalToken), + uint256(v.USDPerUnderlyingOrInterimTokenDst), + uint256(v.USDPerUnderlyingToken) ); v.txData = _buildLiqBridgeTxData(liqBridgeTxDataArgs, args.srcChainId == args.toChainId); @@ -1081,6 +1091,11 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { } } + /// @dev the next steps are to create the user intent amount that goes in the state request. + /// @dev the values here have to be calculated in terms of decimal differences and slippage in the different + /// stages + /// @dev this calculation would be done automatically by Superform Protocol API on mainnet + /// @dev for e.g. externalToken = DAI, underlyingTokenDst = USDC, daiAmount = 100 /// => usdcAmount = ((USDPerDai / 10e18) / (USDPerUsdc / 10e6)) * daiAmount console.log("test amount pre-swap", args.amount); @@ -1095,51 +1110,58 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { /// @dev decimal1 = decimals of args.externalToken (src chain), decimal2 = decimals of args.underlyingToken /// (src chain) if (decimal1 > decimal2) { - args.amount = (args.amount * uint256(USDPerExternalToken)) - / (uint256(USDPerUnderlyingToken) * 10 ** (decimal1 - decimal2)); + args.amount = (args.amount * uint256(v.USDPerExternalToken)) + / (uint256(v.USDPerUnderlyingToken) * 10 ** (decimal1 - decimal2)); } else { - args.amount = ((args.amount * uint256(USDPerExternalToken)) * 10 ** (decimal2 - decimal1)) - / uint256(USDPerUnderlyingToken); + args.amount = ((args.amount * uint256(v.USDPerExternalToken)) * 10 ** (decimal2 - decimal1)) + / uint256(v.USDPerUnderlyingToken); } console.log("test amount post-swap", args.amount); } + /// @dev applying only bridge slippage here as dstSwap slippage is applied in _updateSingleVaultDepositPayload() + /// and _updateMultiVaultDepositPayload() int256 slippage = args.slippage; if (args.srcChainId == args.toChainId) slippage = 0; + /// @dev REMOVE THIS LINE IF THEORY IS CORRECT (this is full amount) + // else if (args.dstSwap) slippage = (slippage * int256(100 - MULTI_TX_SLIPPAGE_SHARE)) / 100; - /// @dev applying 100% x-chain slippage at once i.e. bridge + dstSwap slippage (as opposed to 2 steps in - /// LiFiMock) coz this code will only be executed once (as opposed to twice in LiFiMock, once for bridge and - /// other for dstSwap) args.amount = (args.amount * uint256(10_000 - slippage)) / 10_000; console.log("test amount pre-bridge, post-slippage", v.amount); /// @dev if args.externalToken == args.underlyingToken, USDPerExternalToken == USDPerUnderlyingToken /// @dev v.decimal3 = decimals of args.underlyingToken (args.externalToken too if above holds true) (src chain), - /// v.decimal2 = decimals of args.underlyingTokenDst (dst chain) + /// v.decimal2 = decimals of args.underlyingTokenDst (dst chain) - interimToken in case of dstSwap if (v.decimal3 > v.decimal2) { - v.amount = (args.amount * uint256(USDPerUnderlyingToken)) - / (uint256(USDPerUnderlyingTokenDst) * 10 ** (v.decimal3 - v.decimal2)); + v.amount = (args.amount * uint256(v.USDPerUnderlyingToken)) + / (uint256(v.USDPerUnderlyingOrInterimTokenDst) * 10 ** (v.decimal3 - v.decimal2)); } else { - v.amount = (args.amount * uint256(USDPerUnderlyingToken) * 10 ** (v.decimal2 - v.decimal3)) - / uint256(USDPerUnderlyingTokenDst); + v.amount = (args.amount * uint256(v.USDPerUnderlyingToken) * 10 ** (v.decimal2 - v.decimal3)) + / uint256(v.USDPerUnderlyingOrInterimTokenDst); } + console.log("test amount post-bridge", v.amount); - vm.selectFork(v.initialFork); + vm.selectFork(FORKS[args.toChainId]); + (address superform,,) = DataLib.getSuperform(args.superformId); /// @dev extraData is unused here so false is encoded (it is currently used to send in the partialWithdraw /// vaults without resorting to extra args, just for withdraws) superformData = SingleVaultSFData( args.superformId, v.amount, + IBaseForm(superform).previewDepositTo(v.amount), args.maxSlippage, v.liqReq, v.permit2Calldata, args.dstSwap, args.receive4626, users[args.user], + users[args.user], + /// @dev repeat user for receiverAddressSP - not testing AA here abi.encode(false) ); + vm.selectFork(v.initialFork); } struct SingleVaultWithdrawLocalVars { @@ -1228,26 +1250,33 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { vars.liqReq = LiqRequest( vars.txData, /// @dev for certain test cases, insert txData as null here - args.underlyingTokenDst, + args.externalToken, address(0), args.liqBridge, args.liqDstChainId, 0 ); + vm.selectFork(FORKS[args.toChainId]); + (address superform,,) = DataLib.getSuperform(args.superformId); + /// @dev extraData is currently used to send in the partialWithdraw vaults without resorting to extra args, just /// for withdraws superformData = SingleVaultSFData( args.superformId, args.amount, + IBaseForm(superform).previewRedeemFrom(args.amount), args.maxSlippage, vars.liqReq, "", args.dstSwap, args.receive4626, users[args.user], + users[args.user], abi.encode(false) ); + + vm.selectFork(initialFork); } /*/////////////////////////////////////////////////////////////// @@ -1341,15 +1370,26 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { return superformIds_; } - function _updateMultiVaultDepositPayload(updateMultiVaultDepositPayloadArgs memory args) internal returns (bool) { + function _updateMultiVaultDepositPayload( + updateMultiVaultDepositPayloadArgs memory args, + uint256[] memory targetUnderlyings + ) + internal + returns (bool) + { uint256 initialFork = vm.activeFork(); vm.selectFork(FORKS[args.targetChainId]); uint256 len = args.amounts.length; uint256[] memory finalAmounts = new uint256[](len); + address[] memory bridgedTokens = new address[](len); int256 dstSwapSlippage; + for (uint256 i; i < len; ++i) { + bridgedTokens[i] = getContract(args.targetChainId, UNDERLYING_TOKENS[targetUnderlyings[i]]); + } + /// @dev slippage calculation for (uint256 i = 0; i < len; ++i) { finalAmounts[i] = args.amounts[i]; @@ -1368,7 +1408,7 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { if (args.testType == TestType.Pass || args.testType == TestType.RevertProcessPayload) { vm.prank(deployer); CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); /// @dev if scenario is meant to revert here (e.g invalid slippage) @@ -1378,7 +1418,7 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { /// @dev removed string here: come to this later CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); return false; @@ -1389,7 +1429,7 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { vm.expectRevert(errorMsg); CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); return false; @@ -1400,7 +1440,10 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { return true; } - function _updateSingleVaultDepositPayload(updateSingleVaultDepositPayloadArgs memory args) + function _updateSingleVaultDepositPayload( + updateSingleVaultDepositPayloadArgs memory args, + uint256 targetUnderlying + ) internal returns (bool) { @@ -1408,13 +1451,12 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { vm.selectFork(FORKS[args.targetChainId]); uint256 finalAmount; + address bridgedToken = getContract(args.targetChainId, UNDERLYING_TOKENS[targetUnderlying]); finalAmount = args.amount; int256 dstSwapSlippage; - finalAmount = (finalAmount * uint256(10_000 - args.slippage)) / 10_000; - if (args.isdstSwap) { dstSwapSlippage = (args.slippage * int256(MULTI_TX_SLIPPAGE_SHARE)) / 100; finalAmount = (finalAmount * uint256(10_000 - dstSwapSlippage)) / 10_000; @@ -1426,8 +1468,11 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { uint256[] memory finalAmounts = new uint256[](1); finalAmounts[0] = finalAmount; + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = bridgedToken; + CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); /// @dev if scenario is meant to revert here (e.g invalid slippage) } else if (args.testType == TestType.RevertUpdateStateSlippage) { @@ -1436,12 +1481,14 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { vm.expectRevert(args.revertError); /// @dev removed string here: come to this later - uint256[] memory finalAmounts = new uint256[](1); finalAmounts[0] = finalAmount; + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = bridgedToken; + CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); return false; @@ -1455,8 +1502,11 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { uint256[] memory finalAmounts = new uint256[](1); finalAmounts[0] = finalAmount; + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = bridgedToken; + CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); return false; @@ -1551,28 +1601,32 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { uint64, /*srcChainId_*/ uint64 targetChainId_, address underlyingTokenDst_, - uint256 amount_, - int256 slippage_ + int256 slippage_, + uint256 underlyingWithBridgeSlippage_ ) internal { uint256 initialFork = vm.activeFork(); vm.selectFork(FORKS[targetChainId_]); + /// @dev replace socket bridge with socket one inch impl for dst swap + if (liqBridgeKind_ == 2) { + liqBridgeKind_ = 3; + } + /// @dev liqData is rebuilt here to perform to send the tokens from dstSwapProcessor to CoreStateRegistry bytes memory txData = _buildLiqBridgeTxDataDstSwap( liqBridgeKind_, - underlyingTokenDst_, + getContract(targetChainId_, UNDERLYING_TOKENS[0]), underlyingTokenDst_, getContract(targetChainId_, "DstSwapper"), targetChainId_, - amount_, + underlyingWithBridgeSlippage_, slippage_ ); vm.prank(deployer); - - DstSwapper(payable(getContract(targetChainId_, "DstSwapper"))).processTx(1, 0, liqBridgeKind_, txData); + DstSwapper(payable(getContract(targetChainId_, "DstSwapper"))).processTx(1, liqBridgeKind_, txData); vm.selectFork(initialFork); } @@ -1581,8 +1635,8 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { uint64, /*srcChainId_*/ uint64 targetChainId_, address[] memory underlyingTokensDst_, - uint256[] memory amounts_, - int256 slippage_ + int256 slippage_, + uint256[] memory underlyingWithBridgeSlippages_ ) internal { @@ -1590,24 +1644,29 @@ abstract contract InvariantProtocolActions is CommonProtocolActions { vm.selectFork(FORKS[targetChainId_]); bytes[] memory txDatas = new bytes[](underlyingTokensDst_.length); + /// @dev replace socket bridge with socket one inch impl for dst swap + for (uint256 i; i < liqBridgeKinds_.length; ++i) { + if (liqBridgeKinds_[i] == 2) liqBridgeKinds_[i] = 3; + } + /// @dev liqData is rebuilt here to perform to send the tokens from dstSwapProcessor to CoreStateRegistry for (uint256 i = 0; i < underlyingTokensDst_.length; ++i) { txDatas[i] = _buildLiqBridgeTxDataDstSwap( liqBridgeKinds_[i], - underlyingTokensDst_[i], + getContract(targetChainId_, UNDERLYING_TOKENS[i]), underlyingTokensDst_[i], getContract(targetChainId_, "DstSwapper"), targetChainId_, - amounts_[i], + underlyingWithBridgeSlippages_[i], slippage_ ); } vm.prank(deployer); - uint256[] memory indices = new uint256[](amounts_.length); + uint256[] memory indices = new uint256[](underlyingWithBridgeSlippages_.length); - for (uint256 i; i < amounts_.length; ++i) { + for (uint256 i; i < underlyingWithBridgeSlippages_.length; ++i) { indices[i] = i; } diff --git a/test/utils/ProtocolActions.sol b/test/utils/ProtocolActions.sol index 743867d07..0c113f930 100644 --- a/test/utils/ProtocolActions.sol +++ b/test/utils/ProtocolActions.sol @@ -399,6 +399,8 @@ abstract contract ProtocolActions is CommonProtocolActions { vars.amounts = AMOUNTS[DST_CHAINS[i]][actionIndex]; + vars.outputAmounts = vars.amounts; + vars.liqBridges = LIQ_BRIDGES[DST_CHAINS[i]][actionIndex]; vars.receive4626 = RECEIVE_4626[DST_CHAINS[i]][actionIndex]; @@ -416,6 +418,7 @@ abstract contract ProtocolActions is CommonProtocolActions { vars.underlyingDstToken, vars.targetSuperformIds, vars.amounts, + vars.outputAmounts, vars.liqBridges, vars.receive4626, MAX_SLIPPAGE, @@ -460,8 +463,10 @@ abstract contract ProtocolActions is CommonProtocolActions { vars.toDst[0], vars.underlyingSrcToken[0], vars.underlyingDstToken[0], + action.dstSwap ? getContract(DST_CHAINS[i], UNDERLYING_TOKENS[0]) : address(0), vars.targetSuperformIds[0], finalAmount, + finalAmount, vars.liqBridges[0], vars.receive4626[0], MAX_SLIPPAGE, @@ -948,16 +953,17 @@ abstract contract ProtocolActions is CommonProtocolActions { _targetVaults(CHAIN_0, DST_CHAINS[i], actionIndex, i); vars.liqBridges = LIQ_BRIDGES[DST_CHAINS[i]][actionIndex]; + vars.amounts = AMOUNTS[DST_CHAINS[i]][actionIndex]; + + vars.underlyingWithBridgeSlippages = new uint256[](vars.amounts.length); /// @dev dst swap is performed to ensure tokens reach CoreStateRegistry on deposits if (action.multiVaults) { - vars.amounts = AMOUNTS[DST_CHAINS[i]][actionIndex]; - - vars.underlyingWith0Slippages = new uint256[](vars.amounts.length); for (uint256 j = 0; j < vars.amounts.length; ++j) { - vars.underlyingWith0Slippages[j] = _updateAmountWithPricedSwapsAndSlippage( + vars.underlyingWithBridgeSlippages[j] = _updateAmountWithPricedSwapsAndSlippage( AMOUNTS[DST_CHAINS[i]][actionIndex][j], - 0, - vars.underlyingDstToken[j], + vars.multiVaultsPayloadArg.slippage, + getContract(DST_CHAINS[i], UNDERLYING_TOKENS[j]), + /// @dev substituting this to become the interim token action.externalToken == 3 ? NATIVE_TOKEN : getContract(CHAIN_0, UNDERLYING_TOKENS[action.externalToken]), @@ -966,20 +972,20 @@ abstract contract ProtocolActions is CommonProtocolActions { DST_CHAINS[i] ); } + /// bridged amount with full slippage (inc. dstSwap slippage here) _batchProcessDstSwap( vars.liqBridges, CHAIN_0, aV[i].toChainId, vars.underlyingDstToken, - vars.multiVaultsPayloadArg.amounts, action.slippage, - vars.underlyingWith0Slippages + vars.underlyingWithBridgeSlippages ); } else { - vars.underlyingWith0Slippage = _updateAmountWithPricedSwapsAndSlippage( + vars.underlyingWithBridgeSlippage = _updateAmountWithPricedSwapsAndSlippage( AMOUNTS[DST_CHAINS[i]][actionIndex][0], - 0, - vars.underlyingDstToken[0], + vars.singleVaultsPayloadArg.slippage, + getContract(DST_CHAINS[i], UNDERLYING_TOKENS[0]), action.externalToken == 3 ? NATIVE_TOKEN : getContract(CHAIN_0, UNDERLYING_TOKENS[action.externalToken]), @@ -992,8 +998,8 @@ abstract contract ProtocolActions is CommonProtocolActions { CHAIN_0, aV[i].toChainId, vars.underlyingDstToken[0], - vars.singleVaultsPayloadArg.amount, - action.slippage + action.slippage, + vars.underlyingWithBridgeSlippage ); } } @@ -1001,9 +1007,13 @@ abstract contract ProtocolActions is CommonProtocolActions { /// @dev this is the step where the amounts are updated taking into account the final /// slippage if (action.multiVaults) { - _updateMultiVaultDepositPayload(vars.multiVaultsPayloadArg); + _updateMultiVaultDepositPayload( + vars.multiVaultsPayloadArg, vars.underlyingWithBridgeSlippages + ); } else if (singleSuperformsData.length > 0) { - _updateSingleVaultDepositPayload(vars.singleVaultsPayloadArg); + _updateSingleVaultDepositPayload( + vars.singleVaultsPayloadArg, vars.underlyingWithBridgeSlippage + ); } vm.recordLogs(); @@ -1018,9 +1028,13 @@ abstract contract ProtocolActions is CommonProtocolActions { } else if (action.testType == TestType.RevertProcessPayload) { /// @dev this logic is essentially repeated from above if (action.multiVaults) { - _updateMultiVaultDepositPayload(vars.multiVaultsPayloadArg); + _updateMultiVaultDepositPayload( + vars.multiVaultsPayloadArg, vars.underlyingWithBridgeSlippages + ); } else if (singleSuperformsData.length > 0) { - _updateSingleVaultDepositPayload(vars.singleVaultsPayloadArg); + _updateSingleVaultDepositPayload( + vars.singleVaultsPayloadArg, vars.underlyingWithBridgeSlippage + ); } /// @dev process payload will revert in here success = _processPayload(PAYLOAD_ID[aV[i].toChainId], aV[i].toChainId, action.testType); @@ -1033,9 +1047,13 @@ abstract contract ProtocolActions is CommonProtocolActions { ) { /// @dev branch used just for reverts of updatePayload (process payload is not even called) if (action.multiVaults) { - success = _updateMultiVaultDepositPayload(vars.multiVaultsPayloadArg); + success = _updateMultiVaultDepositPayload( + vars.multiVaultsPayloadArg, vars.underlyingWithBridgeSlippages + ); } else { - success = _updateSingleVaultDepositPayload(vars.singleVaultsPayloadArg); + success = _updateSingleVaultDepositPayload( + vars.singleVaultsPayloadArg, vars.underlyingWithBridgeSlippage + ); } if (!success) { @@ -1303,6 +1321,7 @@ abstract contract ProtocolActions is CommonProtocolActions { int256 slippage; } + /// this function calculates the bridged amount with full slippage (but maybe could include only bridge slippage) function _updateAmountWithPricedSwapsAndSlippage( uint256 amount_, int256 slippage_, @@ -1355,11 +1374,15 @@ abstract contract ProtocolActions is CommonProtocolActions { } v.slippage = slippage_; - if (srcChainId_ == dstChainId_) v.slippage = 0; + if (srcChainId_ == dstChainId_) { + v.slippage = 0; + } + /// @dev add just bridge slippage here (pre-dst swap) + else { + v.slippage = (v.slippage * int256(100 - MULTI_TX_SLIPPAGE_SHARE)) / 100; + console.log("applied slippage in pre dst swap"); + } - /// @dev applying 100% x-chain slippage at once i.e. bridge + dstSwap slippage (as opposed to 2 steps in - /// LiFiMock) coz this code will only be executed once (as opposed to twice in LiFiMock, once for bridge - /// and other for dstSwap) amount_ = (amount_ * uint256(10_000 - v.slippage)) / 10_000; console.log("test amount pre-bridge, post-slippage", amount_); @@ -1414,7 +1437,7 @@ abstract contract ProtocolActions is CommonProtocolActions { if (payloadId == 0) { payloadId = PAYLOAD_ID[DST_CHAINS[0]]; } - (v.rescueSuperformIds,) = CoreStateRegistry(v.coreStateRegistryDst).getFailedDeposits(payloadId); + (v.rescueSuperformIds,,) = CoreStateRegistry(v.coreStateRegistryDst).getFailedDeposits(payloadId); v.amounts = new uint256[](v.rescueSuperformIds.length); for (uint256 i = 0; i < v.rescueSuperformIds.length; ++i) { @@ -1518,8 +1541,13 @@ abstract contract ProtocolActions is CommonProtocolActions { if (len == 0) revert LEN_MISMATCH(); uint256[] memory finalAmounts = new uint256[](len); uint256[] memory maxSlippageTemp = new uint256[](len); + address uniqueInterimToken; for (uint256 i = 0; i < len; ++i) { finalAmounts[i] = args.amounts[i]; + if (i < 3 && args.dstSwap && args.action != Actions.Withdraw) { + /// @dev hack to support unique interim tokens -assuming dst swap scenario cases have less than 3 vaults + uniqueInterimToken = getContract(args.toChainId, UNDERLYING_TOKENS[i]); + } /// @dev FOR TESTING AND MAINNET:: in sameChain actions, slippage is encoded in the request with the amount /// (extracted from bridge api) @@ -1544,8 +1572,10 @@ abstract contract ProtocolActions is CommonProtocolActions { args.toDst[i], args.underlyingTokens[i], args.underlyingTokensDst[i], + uniqueInterimToken, args.superformIds[i], finalAmounts[i], + finalAmounts[i], args.liqBridges[i], args.receive4626[i], args.maxSlippage, @@ -1567,10 +1597,14 @@ abstract contract ProtocolActions is CommonProtocolActions { } liqRequests[i] = superformData.liqRequest; + if (args.dstSwap && args.action != Actions.Withdraw) liqRequests[i].interimToken = uniqueInterimToken; maxSlippageTemp[i] = args.maxSlippage; v.totalAmount += finalAmounts[i]; finalAmounts[i] = superformData.amount; + console.log("finalAmount", finalAmounts[i]); + args.outputAmounts[i] = superformData.outputAmount; + console.log("args.outputAmounts[i]", args.outputAmounts[i]); } if (action == Actions.DepositPermit2) { @@ -1595,12 +1629,15 @@ abstract contract ProtocolActions is CommonProtocolActions { superformsData = MultiVaultSFData( args.superformIds, finalAmounts, + args.outputAmounts, maxSlippageTemp, liqRequests, v.permit2data, hasDstSwap, args.receive4626, users[args.user], + users[args.user], + /// @dev repeat user for receiverAddressSP - not testing AA here abi.encode(args.partialWithdrawVaults) ); } @@ -1615,8 +1652,13 @@ abstract contract ProtocolActions is CommonProtocolActions { uint256 decimal1; uint256 decimal2; uint256 decimal3; + uint256 decimal4; uint256 amountTemp; uint256 amount; + int256 USDPerUnderlyingOrInterimTokenDst; + int256 USDPerUnderlyingTokenDst; + int256 USDPerExternalToken; + int256 USDPerUnderlyingToken; LiqRequest liqReq; } @@ -1634,16 +1676,35 @@ abstract contract ProtocolActions is CommonProtocolActions { /// @dev build permit2 calldata vm.selectFork(FORKS[args.toChainId]); - v.decimal2 = args.underlyingTokenDst != NATIVE_TOKEN ? MockERC20(args.underlyingTokenDst).decimals() : 18; - (, int256 USDPerUnderlyingTokenDst,,,) = - AggregatorV3Interface(tokenPriceFeeds[args.toChainId][args.underlyingTokenDst]).latestRoundData(); + + /// @dev decimals of interimToken in case it exists (dstSwaps), otherwise decimals of final token + /// (underlyingToken) + /// @dev hack for when args.dstSwap == true + if (args.uniqueInterimToken != address(0)) { + v.decimal2 = args.uniqueInterimToken != NATIVE_TOKEN ? MockERC20(args.uniqueInterimToken).decimals() : 18; + + (, v.USDPerUnderlyingOrInterimTokenDst,,,) = + AggregatorV3Interface(tokenPriceFeeds[args.toChainId][args.uniqueInterimToken]).latestRoundData(); + + v.decimal4 = args.underlyingTokenDst != NATIVE_TOKEN ? MockERC20(args.underlyingTokenDst).decimals() : 18; + + (, v.USDPerUnderlyingTokenDst,,,) = + AggregatorV3Interface(tokenPriceFeeds[args.toChainId][args.underlyingTokenDst]).latestRoundData(); + } else { + v.decimal2 = args.underlyingTokenDst != NATIVE_TOKEN ? MockERC20(args.underlyingTokenDst).decimals() : 18; + + (, v.USDPerUnderlyingOrInterimTokenDst,,,) = + AggregatorV3Interface(tokenPriceFeeds[args.toChainId][args.underlyingTokenDst]).latestRoundData(); + } vm.selectFork(FORKS[args.srcChainId]); + /// @dev decimals of externalToken v.decimal1 = args.externalToken != NATIVE_TOKEN ? MockERC20(args.externalToken).decimals() : 18; + /// @dev decimals of underlyingToken on source v.decimal3 = args.underlyingToken != NATIVE_TOKEN ? MockERC20(args.underlyingToken).decimals() : 18; - (, int256 USDPerExternalToken,,,) = + (, v.USDPerExternalToken,,,) = AggregatorV3Interface(tokenPriceFeeds[args.srcChainId][args.externalToken]).latestRoundData(); - (, int256 USDPerUnderlyingToken,,,) = + (, v.USDPerUnderlyingToken,,,) = AggregatorV3Interface(tokenPriceFeeds[args.srcChainId][args.underlyingToken]).latestRoundData(); /// @dev this is to attach v.amount pre dst slippage with the correct decimals to avoid intermediary truncation @@ -1662,7 +1723,7 @@ abstract contract ProtocolActions is CommonProtocolActions { args.liqBridge, args.externalToken, args.underlyingToken, - args.underlyingTokenDst, + args.uniqueInterimToken != address(0) ? args.uniqueInterimToken : args.underlyingTokenDst, v.from, args.srcChainId, args.toChainId, @@ -1674,9 +1735,9 @@ abstract contract ProtocolActions is CommonProtocolActions { //v.amount, false, args.slippage, - uint256(USDPerExternalToken), - uint256(USDPerUnderlyingTokenDst), - uint256(USDPerUnderlyingToken) + uint256(v.USDPerExternalToken), + uint256(v.USDPerUnderlyingOrInterimTokenDst), + uint256(v.USDPerUnderlyingToken) ); v.txData = _buildLiqBridgeTxData(liqBridgeTxDataArgs, args.srcChainId == args.toChainId); @@ -1696,12 +1757,10 @@ abstract contract ProtocolActions is CommonProtocolActions { } /// @dev the actual liq request struct inscription - /// @notice adding dummy liqRequestToken as interim token if there is dstSwap because we are not - /// @notice testing failed deposits here v.liqReq = LiqRequest( v.txData, liqRequestToken, - args.dstSwap ? args.underlyingTokenDst : address(0), + args.dstSwap ? args.uniqueInterimToken : address(0), args.liqBridge, args.toChainId, liqRequestToken == NATIVE_TOKEN ? args.amount : 0 @@ -1709,7 +1768,6 @@ abstract contract ProtocolActions is CommonProtocolActions { if (liqRequestToken != NATIVE_TOKEN) { /// @dev - APPROVE transfer to SuperformRouter (because of Socket) - if (action == Actions.DepositPermit2) { vm.prank(users[args.user]); MockERC20(liqRequestToken).approve(getContract(args.srcChainId, "CanonicalPermit2"), type(uint256).max); @@ -1725,6 +1783,11 @@ abstract contract ProtocolActions is CommonProtocolActions { } } + /// @dev the next steps are to create the user intent amount that goes in the state request. + /// @dev the values here have to be calculated in terms of decimal differences and slippage in the different + /// stages + /// @dev this calculation would be done automatically by Superform Protocol API on mainnet + /// @dev for e.g. externalToken = DAI, underlyingTokenDst = USDC, daiAmount = 100 /// => usdcAmount = ((USDPerDai / 10e18) / (USDPerUsdc / 10e6)) * daiAmount console.log("test amount pre-swap", args.amount); @@ -1739,11 +1802,11 @@ abstract contract ProtocolActions is CommonProtocolActions { /// @dev decimal1 = decimals of args.externalToken (src chain), decimal2 = decimals of args.underlyingToken /// (src chain) if (decimal1 > decimal2) { - args.amount = (args.amount * uint256(USDPerExternalToken)) - / (uint256(USDPerUnderlyingToken) * 10 ** (decimal1 - decimal2)); + args.amount = (args.amount * uint256(v.USDPerExternalToken)) + / (uint256(v.USDPerUnderlyingToken) * 10 ** (decimal1 - decimal2)); } else { - args.amount = ((args.amount * uint256(USDPerExternalToken)) * 10 ** (decimal2 - decimal1)) - / uint256(USDPerUnderlyingToken); + args.amount = ((args.amount * uint256(v.USDPerExternalToken)) * 10 ** (decimal2 - decimal1)) + / uint256(v.USDPerUnderlyingToken); } console.log("test amount post-swap", args.amount); } @@ -1752,38 +1815,58 @@ abstract contract ProtocolActions is CommonProtocolActions { /// and _updateMultiVaultDepositPayload() int256 slippage = args.slippage; if (args.srcChainId == args.toChainId) slippage = 0; - else if (args.dstSwap) slippage = (slippage * int256(100 - MULTI_TX_SLIPPAGE_SHARE)) / 100; + /// @dev REMOVE THIS LINE IF THEORY IS CORRECT (this is full amount) + // else if (args.dstSwap) slippage = (slippage * int256(100 - MULTI_TX_SLIPPAGE_SHARE)) / 100; args.amount = (args.amount * uint256(10_000 - slippage)) / 10_000; console.log("test amount pre-bridge, post-slippage", v.amount); /// @dev if args.externalToken == args.underlyingToken, USDPerExternalToken == USDPerUnderlyingToken /// @dev v.decimal3 = decimals of args.underlyingToken (args.externalToken too if above holds true) (src chain), - /// v.decimal2 = decimals of args.underlyingTokenDst (dst chain) + /// v.decimal2 = decimals of args.underlyingTokenDst (dst chain) - interimToken in case of dstSwap if (v.decimal3 > v.decimal2) { - v.amount = (args.amount * uint256(USDPerUnderlyingToken)) - / (uint256(USDPerUnderlyingTokenDst) * 10 ** (v.decimal3 - v.decimal2)); + v.amount = (args.amount * uint256(v.USDPerUnderlyingToken)) + / (uint256(v.USDPerUnderlyingOrInterimTokenDst) * 10 ** (v.decimal3 - v.decimal2)); } else { - v.amount = (args.amount * uint256(USDPerUnderlyingToken) * 10 ** (v.decimal2 - v.decimal3)) - / uint256(USDPerUnderlyingTokenDst); + v.amount = (args.amount * uint256(v.USDPerUnderlyingToken) * 10 ** (v.decimal2 - v.decimal3)) + / uint256(v.USDPerUnderlyingOrInterimTokenDst); } + console.log("test amount post-bridge", v.amount); - vm.selectFork(v.initialFork); + /// @dev extra step to convert interim token on dst to underlying token on dst (if there is a dst Swap) + if (args.uniqueInterimToken != address(0)) { + if (v.decimal2 > v.decimal4) { + v.amount = (v.amount * uint256(v.USDPerUnderlyingOrInterimTokenDst)) + / (uint256(v.USDPerUnderlyingTokenDst) * 10 ** (v.decimal2 - v.decimal4)); + } else { + v.amount = (v.amount * uint256(v.USDPerUnderlyingOrInterimTokenDst) * 10 ** (v.decimal4 - v.decimal2)) + / uint256(v.USDPerUnderlyingTokenDst); + } + } + + console.log("test amount post-dst swap --", v.amount); + + vm.selectFork(FORKS[args.toChainId]); + (address superform,,) = DataLib.getSuperform(args.superformId); /// @dev extraData is unused here so false is encoded (it is currently used to send in the partialWithdraw /// vaults without resorting to extra args, just for withdraws) superformData = SingleVaultSFData( args.superformId, v.amount, + IBaseForm(superform).previewDepositTo(v.amount), args.maxSlippage, v.liqReq, v.permit2Calldata, args.dstSwap, args.receive4626, users[args.user], + users[args.user], + /// @dev repeat user for receiverAddressSP - not testing AA here abi.encode(false) ); + vm.selectFork(v.initialFork); } struct SingleVaultWithdrawLocalVars { @@ -1875,26 +1958,35 @@ abstract contract ProtocolActions is CommonProtocolActions { vars.liqReq = LiqRequest( GENERATE_WITHDRAW_TX_DATA_ON_DST ? bytes("") : vars.txData, /// @dev for certain test cases, insert txData as null here - args.underlyingTokenDst, + args.externalToken, address(0), args.liqBridge, args.liqDstChainId, 0 ); + vm.selectFork(FORKS[args.toChainId]); + (address superform,,) = DataLib.getSuperform(args.superformId); + console.log("finalAMount", args.amount); + console.log("-- OA---", IBaseForm(superform).previewRedeemFrom(args.amount)); /// @dev extraData is currently used to send in the partialWithdraw vaults without resorting to extra args, just /// for withdraws superformData = SingleVaultSFData( args.superformId, args.amount, + IBaseForm(superform).previewRedeemFrom(args.amount), args.maxSlippage, vars.liqReq, "", args.dstSwap, args.receive4626, users[args.user], + users[args.user], + /// @dev repeat user for receiverAddressSP - not testing AA here abi.encode(args.partialWithdrawVault) ); + + vm.selectFork(initialFork); } /*/////////////////////////////////////////////////////////////// @@ -2081,24 +2173,38 @@ abstract contract ProtocolActions is CommonProtocolActions { } } - function _updateMultiVaultDepositPayload(updateMultiVaultDepositPayloadArgs memory args) internal returns (bool) { + function _updateMultiVaultDepositPayload( + updateMultiVaultDepositPayloadArgs memory args, + uint256[] memory finalAmountsThatReachedCSR + ) + internal + returns (bool) + { uint256 initialFork = vm.activeFork(); vm.selectFork(FORKS[args.targetChainId]); uint256 len = args.amounts.length; uint256[] memory finalAmounts = new uint256[](len); + address[] memory bridgedTokens = new address[](len); int256 dstSwapSlippage; + for (uint256 i; i < len; ++i) { + bridgedTokens[i] = + getContract(args.targetChainId, UNDERLYING_TOKENS[TARGET_UNDERLYINGS[args.targetChainId][0][i]]); + } + for (uint256 i = 0; i < len; ++i) { finalAmounts[i] = args.amounts[i]; if (args.slippage > 0) { - /// @dev bridge slippage is already applied in _buildSingleVaultDepositCallData() - // finalAmounts[i] = (finalAmounts[i] * uint256(10_000 - args.slippage)) / 10_000; - + /// @dev finalAmounts[i] has full slippage applied (final user expectedAmount) + uint256 amountPostDstSwap; if (args.isdstSwap) { dstSwapSlippage = (args.slippage * int256(MULTI_TX_SLIPPAGE_SHARE)) / 100; - finalAmounts[i] = (finalAmounts[i] * uint256(10_000 - dstSwapSlippage)) / 10_000; + amountPostDstSwap = (finalAmountsThatReachedCSR[i] * uint256(10_000 - dstSwapSlippage)) / 10_000; + if (amountPostDstSwap < finalAmounts[i]) { + finalAmounts[i] = amountPostDstSwap; + } } } } @@ -2107,7 +2213,7 @@ abstract contract ProtocolActions is CommonProtocolActions { if (args.testType == TestType.Pass || args.testType == TestType.RevertProcessPayload) { vm.prank(deployer); CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); /// @dev if scenario is meant to revert here (e.g invalid slippage) @@ -2117,7 +2223,7 @@ abstract contract ProtocolActions is CommonProtocolActions { /// @dev removed string here: come to this later CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); return false; @@ -2128,7 +2234,7 @@ abstract contract ProtocolActions is CommonProtocolActions { vm.expectRevert(errorMsg); CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); return false; @@ -2139,7 +2245,10 @@ abstract contract ProtocolActions is CommonProtocolActions { return true; } - function _updateSingleVaultDepositPayload(updateSingleVaultDepositPayloadArgs memory args) + function _updateSingleVaultDepositPayload( + updateSingleVaultDepositPayloadArgs memory args, + uint256 finalAmountsThatReachedCSR + ) internal returns (bool) { @@ -2147,16 +2256,20 @@ abstract contract ProtocolActions is CommonProtocolActions { vm.selectFork(FORKS[args.targetChainId]); uint256 finalAmount; + address bridgedToken = + getContract(args.targetChainId, UNDERLYING_TOKENS[TARGET_UNDERLYINGS[args.targetChainId][0][0]]); finalAmount = args.amount; - int256 dstSwapSlippage; - // finalAmount = (finalAmount * uint256(10_000 - args.slippage)) / 10_000; - if (args.isdstSwap) { dstSwapSlippage = (args.slippage * int256(MULTI_TX_SLIPPAGE_SHARE)) / 100; - finalAmount = (finalAmount * uint256(10_000 - dstSwapSlippage)) / 10_000; + uint256 amountPostDstSwap; + + amountPostDstSwap = (finalAmountsThatReachedCSR * uint256(10_000 - dstSwapSlippage)) / 10_000; + if (amountPostDstSwap < finalAmount) { + finalAmount = amountPostDstSwap; + } } /// @dev if test type is RevertProcessPayload, revert is further down the call chain @@ -2165,8 +2278,11 @@ abstract contract ProtocolActions is CommonProtocolActions { uint256[] memory finalAmounts = new uint256[](1); finalAmounts[0] = finalAmount; + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = bridgedToken; + CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); /// @dev if scenario is meant to revert here (e.g invalid slippage) } else if (args.testType == TestType.RevertUpdateStateSlippage) { @@ -2175,12 +2291,14 @@ abstract contract ProtocolActions is CommonProtocolActions { vm.expectRevert(args.revertError); /// @dev removed string here: come to this later - uint256[] memory finalAmounts = new uint256[](1); finalAmounts[0] = finalAmount; + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = bridgedToken; + CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); return false; @@ -2194,8 +2312,11 @@ abstract contract ProtocolActions is CommonProtocolActions { uint256[] memory finalAmounts = new uint256[](1); finalAmounts[0] = finalAmount; + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = bridgedToken; + CoreStateRegistry(payable(getContract(args.targetChainId, "CoreStateRegistry"))).updateDepositPayload( - args.payloadId, finalAmounts + args.payloadId, bridgedTokens, finalAmounts ); return false; @@ -2278,7 +2399,6 @@ abstract contract ProtocolActions is CommonProtocolActions { returns (bool) { uint256 initialFork = vm.activeFork(); - vm.selectFork(FORKS[targetChainId_]); /// @dev tries to increase quorum and check if quorum validations are good @@ -2319,8 +2439,8 @@ abstract contract ProtocolActions is CommonProtocolActions { uint64, /*srcChainId_*/ uint64 targetChainId_, address underlyingTokenDst_, - uint256 amount_, - int256 slippage_ + int256 slippage_, + uint256 underlyingWithBridgeSlippage_ ) internal { @@ -2335,16 +2455,16 @@ abstract contract ProtocolActions is CommonProtocolActions { /// @dev liqData is rebuilt here to perform to send the tokens from dstSwapProcessor to CoreStateRegistry bytes memory txData = _buildLiqBridgeTxDataDstSwap( liqBridgeKind_, - underlyingTokenDst_, + getContract(targetChainId_, UNDERLYING_TOKENS[0]), underlyingTokenDst_, getContract(targetChainId_, "DstSwapper"), targetChainId_, - amount_, + underlyingWithBridgeSlippage_, slippage_ ); vm.prank(deployer); - DstSwapper(payable(getContract(targetChainId_, "DstSwapper"))).processTx(1, 0, liqBridgeKind_, txData); + DstSwapper(payable(getContract(targetChainId_, "DstSwapper"))).processTx(1, liqBridgeKind_, txData); vm.selectFork(initialFork); } @@ -2353,9 +2473,8 @@ abstract contract ProtocolActions is CommonProtocolActions { uint64, /*srcChainId_*/ uint64 targetChainId_, address[] memory underlyingTokensDst_, - uint256[] memory amounts_, int256 slippage_, - uint256[] memory /*underlyingWith0Slippages*/ + uint256[] memory underlyingWithBridgeSlippages_ ) internal { @@ -2370,22 +2489,24 @@ abstract contract ProtocolActions is CommonProtocolActions { /// @dev liqData is rebuilt here to perform to send the tokens from dstSwapProcessor to CoreStateRegistry for (uint256 i = 0; i < underlyingTokensDst_.length; ++i) { + /// @dev hack: sending token corresponds to what was set in the buildMultiVault step. Only works if vaults + /// <= 3 txDatas[i] = _buildLiqBridgeTxDataDstSwap( liqBridgeKinds_[i], - underlyingTokensDst_[i], + getContract(targetChainId_, UNDERLYING_TOKENS[i]), underlyingTokensDst_[i], getContract(targetChainId_, "DstSwapper"), targetChainId_, - amounts_[i], + underlyingWithBridgeSlippages_[i], slippage_ ); } vm.prank(deployer); - uint256[] memory indices = new uint256[](amounts_.length); + uint256[] memory indices = new uint256[](underlyingWithBridgeSlippages_.length); - for (uint256 i; i < amounts_.length; ++i) { + for (uint256 i; i < underlyingWithBridgeSlippages_.length; ++i) { indices[i] = i; } @@ -3445,6 +3566,7 @@ abstract contract ProtocolActions is CommonProtocolActions { SingleVaultSFData memory data = SingleVaultSFData( superformId, 2e18, + 2e18, 1000, LiqRequest( _buildLiqBridgeTxData( @@ -3480,6 +3602,7 @@ abstract contract ProtocolActions is CommonProtocolActions { false, retain4626, mrperfect, + mrperfect, "" ); @@ -3487,8 +3610,6 @@ abstract contract ProtocolActions is CommonProtocolActions { ambIds[0] = 1; ambIds[1] = 2; - SingleXChainSingleVaultStateReq memory req = SingleXChainSingleVaultStateReq(ambIds, ARBI, data); - /// @dev approves before call vm.prank(mrperfect); MockERC20(getContract(ETH, "DAI")).approve(superformRouter, 2e18); @@ -3496,7 +3617,9 @@ abstract contract ProtocolActions is CommonProtocolActions { vm.prank(mrperfect); vm.deal(mrperfect, 2 ether); - SuperformRouter(payable(superformRouter)).singleXChainSingleVaultDeposit{ value: 2 ether }(req); + SuperformRouter(payable(superformRouter)).singleXChainSingleVaultDeposit{ value: 2 ether }( + SingleXChainSingleVaultStateReq(ambIds, ARBI, data) + ); Vm.Log[] memory logs = vm.getRecordedLogs(); @@ -3520,7 +3643,12 @@ abstract contract ProtocolActions is CommonProtocolActions { uint256[] memory amounts = new uint256[](1); amounts[0] = 2e18; - CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload(payloadId, amounts); + address[] memory bridgedTokens = new address[](1); + bridgedTokens[0] = getContract(ARBI, "DAI"); + + CoreStateRegistry(payable(getContract(ARBI, "CoreStateRegistry"))).updateDepositPayload( + payloadId, bridgedTokens, amounts + ); uint256 nativeAmount = PaymentHelper(getContract(ARBI, "PaymentHelper")).estimateAckCost(1); diff --git a/test/utils/TestTypes.sol b/test/utils/TestTypes.sol index c98186418..b4a80ce17 100644 --- a/test/utils/TestTypes.sol +++ b/test/utils/TestTypes.sol @@ -52,6 +52,7 @@ struct StagesLocalVars { address[] toDst; uint256[] targetSuperformIds; uint256[] amounts; + uint256[] outputAmounts; uint8[] liqBridges; bool[] receive4626; uint256 chain0Index; @@ -70,8 +71,10 @@ struct StagesLocalVars { uint8[] AMBs; uint64 CHAIN_0; uint64[] DST_CHAINS; - uint256 underlyingWith0Slippage; - uint256[] underlyingWith0Slippages; + uint256 underlyingWithBridgeSlippage; + uint256[] underlyingWithBridgeSlippages; + uint256[] amountsBeforeCSR; + uint256[] finalAmountsThatReachedCSR; } struct MessagingAssertVars { @@ -137,6 +140,8 @@ struct SetupVars { address lifiRouter; address socketRouter; address socketOneInch; + address liFiMockRugpull; + address liFiMockBlacklisted; address erc4626Form; address erc4626TimelockForm; address kycDao4626Form; @@ -187,8 +192,10 @@ struct SingleVaultCallDataArgs { address toDst; address underlyingToken; address underlyingTokenDst; + address uniqueInterimToken; uint256 superformId; uint256 amount; + uint256 outputAmount; uint8 liqBridge; bool receive4626; uint256 maxSlippage; @@ -212,6 +219,7 @@ struct MultiVaultCallDataArgs { address[] underlyingTokensDst; uint256[] superformIds; uint256[] amounts; + uint256[] outputAmounts; uint8[] liqBridges; bool[] receive4626; uint256 maxSlippage; diff --git a/utils/run_script_mainnet.sh b/utils/run_script_mainnet.sh index 5860b8be4..7acafaa8b 100755 --- a/utils/run_script_mainnet.sh +++ b/utils/run_script_mainnet.sh @@ -33,18 +33,18 @@ wait FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage2(uint256)" 2 --rpc-url $AVALANCHE_RPC_URL --broadcast --slow --sender 0x48aB8AdF869Ba9902Ad483FB1Ca2eFDAb6eabe92 -#wait +wait -#echo Running Stage 3: ... +echo Running Stage 3: ... -#FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 0 --rpc-url $BSC_RPC_URL --broadcast --slow --sender 0x48aB8AdF869Ba9902Ad483FB1Ca2eFDAb6eabe92 +FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 0 --rpc-url $BSC_RPC_URL --broadcast --slow --sender 0x48aB8AdF869Ba9902Ad483FB1Ca2eFDAb6eabe92 -#wait +wait -#FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 1 --rpc-url $POLYGON_RPC_URL --broadcast --slow --sender 0x48aB8AdF869Ba9902Ad483FB1Ca2eFDAb6eabe92 +FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 1 --rpc-url $POLYGON_RPC_URL --broadcast --slow --sender 0x48aB8AdF869Ba9902Ad483FB1Ca2eFDAb6eabe92 -#wait +wait -#FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 2 --rpc-url $AVALANCHE_RPC_URL --broadcast --slow --sender 0x48aB8AdF869Ba9902Ad483FB1Ca2eFDAb6eabe92 +FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 2 --rpc-url $AVALANCHE_RPC_URL --broadcast --slow --sender 0x48aB8AdF869Ba9902Ad483FB1Ca2eFDAb6eabe92 diff --git a/utils/run_script_mainnet_new_chain.sh b/utils/run_script_mainnet_new_chain.sh index 18ef8c537..7040ad7f0 100755 --- a/utils/run_script_mainnet_new_chain.sh +++ b/utils/run_script_mainnet_new_chain.sh @@ -3,6 +3,7 @@ # Read the RPC URL source .env +# Example here: deployed chains initially were POLY, AVAX and GNOSIS. Now adding BSC # Run the script echo Running Stage 1: ... diff --git a/utils/run_script_tenderly.sh b/utils/run_script_tenderly.sh index 020b2fe22..1ab854a65 100755 --- a/utils/run_script_tenderly.sh +++ b/utils/run_script_tenderly.sh @@ -1,50 +1,55 @@ #!/usr/bin/env bash -BSC_DEVNET=$(tenderly devnet spawn-rpc --project superform-v1-d4 --template bscdevnet --account superform --return-url) -POLYGON_DEVNET=$(tenderly devnet spawn-rpc --project superform-v1-d4 --template polygondevnet --account superform --return-url) -AVAX_DEVNET=$(tenderly devnet spawn-rpc --project superform-v1-d4 --template avaxdevnet --account superform --return-url) -BASE_DEVNET=$(tenderly devnet spawn-rpc --project superform-v1-d4 --template basedevnet --account superform --return-url) +# Read the RPC URL +source .env + +#BSC_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template bscdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +#POLYGON_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template polygondevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +#AVAX_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template avaxdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +ETHEREUM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template ethereumdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +OPTIMISM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template optimismdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +ARBITRUM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template arbitrumdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) # Run the script echo Running Stage 1: ... -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage1(uint256)" 0 --rpc-url $BSC_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage1(uint256,uint256)" 0 1337 --rpc-url $ETHEREUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage1(uint256)" 1 --rpc-url $POLYGON_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage1(uint256,uint256)" 1 1337 --rpc-url $OPTIMISM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage1(uint256)" 2 --rpc-url $BASE_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage1(uint256,uint256)" 2 1337 --rpc-url $ARBITRUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait echo Running Stage 2: ... -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage2(uint256)" 0 --rpc-url $BSC_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage2(uint256)" 0 --rpc-url $ETHEREUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage2(uint256)" 1 --rpc-url $POLYGON_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage2(uint256)" 1 --rpc-url $OPTIMISM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage2(uint256)" 2 --rpc-url $BASE_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage2(uint256)" 2 --rpc-url $ARBITRUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 -#echo Running Stage 3: ... +echo Running Stage 3: ... -#wait +wait -#FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 0 --rpc-url $BSC_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage3(uint256)" 0 --rpc-url $ETHEREUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 -#wait +wait -#FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 1 --rpc-url $POLYGON_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage3(uint256)" 1 --rpc-url $OPTIMISM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 -#wait +wait -#FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.s.sol:MainnetDeploy --sig "deployStage3(uint256)" 2 --rpc-url $AVAX_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage3(uint256)" 2 --rpc-url $ARBITRUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 diff --git a/utils/run_script_tenderly_new_chain.sh b/utils/run_script_tenderly_new_chain.sh index d307395f6..39d8994ba 100755 --- a/utils/run_script_tenderly_new_chain.sh +++ b/utils/run_script_tenderly_new_chain.sh @@ -3,6 +3,13 @@ # Read the RPC URL source .env +BASE_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template basevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) + +BSC_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template bscdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +POLYGON_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template polygondevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +AVAX_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template avaxdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) + + # Run the script echo Running Stage 1: ... @@ -28,12 +35,12 @@ wait echo Configuring new chain on Stage 2 of previous chains: ... -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.NewChain.s.sol:MainnetDeployNewChain --sig "configurePreviousChains(uint256)" 0 --rpc-url $ETHEREUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.NewChain.s.sol:MainnetDeployNewChain --sig "configurePreviousChains(uint256)" 0 --rpc-url $BSC_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.NewChain.s.sol:MainnetDeployNewChain --sig "configurePreviousChains(uint256)" 1 --rpc-url $AVALANCHE_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.NewChain.s.sol:MainnetDeployNewChain --sig "configurePreviousChains(uint256)" 1 --rpc-url $POLYGON_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait -FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.NewChain.s.sol:MainnetDeployNewChain --sig "configurePreviousChains(uint256)" 2 --rpc-url $GNOSIS_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 +FOUNDRY_PROFILE=default forge script script/Mainnet.Deploy.NewChain.s.sol:MainnetDeployNewChain --sig "configurePreviousChains(uint256)" 2 --rpc-url $AVAX_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 diff --git a/utils/spawn_tenderly_devnets.sh b/utils/spawn_tenderly_devnets.sh index f6693ea4b..da4e32c96 100644 --- a/utils/spawn_tenderly_devnets.sh +++ b/utils/spawn_tenderly_devnets.sh @@ -4,6 +4,6 @@ source .env -# tenderly devnet spawn-rpc --project superform-v1-d4 --template ethereumdevnet --account superform -tenderly devnet spawn-rpc --project superform-v1-d4 --template avaxdevnet --account superform -tenderly devnet spawn-rpc --project superform-v1-d4 --template gnosisdevnet --account superform +tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template bscdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY +tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template polygondevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY +tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template basedevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY \ No newline at end of file