diff --git a/packages/contract/script/da/DeployDAZeroXBridger.s.sol b/packages/contract/script/da/DeployDAZeroXBridger.s.sol index 5f14f76..3b6642f 100644 --- a/packages/contract/script/da/DeployDAZeroXBridger.s.sol +++ b/packages/contract/script/da/DeployDAZeroXBridger.s.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.13; import "forge-std/Script.sol"; import "../../src/DAZeroXBridger.sol"; +import {DestinationType} from "../../src/DestinationUtils.sol"; import { getDAZeroXBridgeRoutes } from "./constants/DAZeroXBridgeRouteConstants.sol"; @@ -18,8 +19,9 @@ contract DeployDAZeroXBridger is Script { uint256 maxQuoteAge = vm.envUint("MAX_ZEROX_QUOTE_AGE"); ( + DestinationType[] memory destinationTypes, uint256[] memory toChainIds, - address[] memory bridgeTokenOuts, + bytes[] memory bridgeTokenOuts, DAZeroXBridger.ZeroXRoute[] memory bridgeRoutes ) = getDAZeroXBridgeRoutes(block.chainid); @@ -28,8 +30,9 @@ contract DeployDAZeroXBridger is Script { } for (uint256 i = 0; i < toChainIds.length; ++i) { + console.log("destinationType:", uint256(destinationTypes[i])); console.log("toChainId:", toChainIds[i]); - console.log("bridgeTokenOut:", bridgeTokenOuts[i]); + console.logBytes(bridgeTokenOuts[i]); console.log("bridgeTokenIn:", bridgeRoutes[i].bridgeTokenIn); console.log("--------------------------------"); } @@ -44,6 +47,7 @@ contract DeployDAZeroXBridger is Script { signer, // _owner signer, // _trustedSigner maxQuoteAge, + destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes diff --git a/packages/contract/script/da/DeployDaimoPayHopBridger.s.sol b/packages/contract/script/da/DeployDaimoPayHopBridger.s.sol index cca3dad..d45a744 100644 --- a/packages/contract/script/da/DeployDaimoPayHopBridger.s.sol +++ b/packages/contract/script/da/DeployDaimoPayHopBridger.s.sol @@ -45,7 +45,11 @@ contract DeployDaimoPayHopBridger is Script { console.log("--------------------------------"); for (uint256 i = 0; i < finalChains.length; ++i) { console.log("Final chain:", finalChains[i]); - console.log("Final coin address:", finalChainCoins[i].coinAddr); + console.log( + "Destination type:", + uint256(finalChainCoins[i].destinationType) + ); + console.logBytes(finalChainCoins[i].coin); console.log( "Final coin decimals:", finalChainCoins[i].coinDecimals diff --git a/packages/contract/script/da/DeployDepositAddressBridger.s.sol b/packages/contract/script/da/DeployDepositAddressBridger.s.sol index c191f9c..d42c94f 100644 --- a/packages/contract/script/da/DeployDepositAddressBridger.s.sol +++ b/packages/contract/script/da/DeployDepositAddressBridger.s.sol @@ -4,6 +4,13 @@ pragma solidity ^0.8.13; import "forge-std/Script.sol"; import "../../src/DepositAddressBridger.sol"; +import { + DestinationType, + DestinationUtils +} from "../../src/DestinationUtils.sol"; +import { + BridgeRecipientMode +} from "../../src/interfaces/IDepositAddressBridger.sol"; import "../../src/DaimoPayCCTPV2Bridger.sol"; import "../../src/DaimoPayLayerZeroBridger.sol"; import "../../src/DaimoPayHopBridger.sol"; @@ -43,16 +50,20 @@ import { contract DeployDepositAddressBridger is Script { function run() public { ( + DestinationType[] memory destinationTypes, uint256[] memory chainIds, - address[] memory stableOuts, - address[] memory bridgers + bytes[] memory stableOuts, + address[] memory bridgers, + BridgeRecipientMode[] memory recipientModes ) = _getBridgerRoutes(); console.log("--------------------------------"); for (uint256 i = 0; i < chainIds.length; ++i) { + console.log("destinationType:", uint256(destinationTypes[i])); console.log("toChain:", chainIds[i]); - console.log(" stableOut:", stableOuts[i]); + console.logBytes(stableOuts[i]); console.log(" bridger:", bridgers[i]); + console.log(" recipientMode:", uint256(recipientModes[i])); } console.log("--------------------------------"); @@ -62,7 +73,13 @@ contract DeployDepositAddressBridger is Script { DEPLOY_SALT_DA_BRIDGER, abi.encodePacked( type(DepositAddressBridger).creationCode, - abi.encode(chainIds, stableOuts, bridgers) + abi.encode( + destinationTypes, + chainIds, + stableOuts, + bridgers, + recipientModes + ) ) ); @@ -75,9 +92,11 @@ contract DeployDepositAddressBridger is Script { private view returns ( + DestinationType[] memory destinationTypes, uint256[] memory chainIds, - address[] memory stableOuts, - address[] memory bridgers + bytes[] memory stableOuts, + address[] memory bridgers, + BridgeRecipientMode[] memory recipientModes ) { // Get addresses of deployed bridger implementations @@ -157,8 +176,9 @@ contract DeployDepositAddressBridger is Script { // 0x ( + DestinationType[] memory zeroXDestinationTypes, uint256[] memory zeroXChainIds, - address[] memory zeroXBridgeTokenOuts, + bytes[] memory zeroXBridgeTokenOuts, ) = getDAZeroXBridgeRoutes(block.chainid); @@ -172,69 +192,104 @@ contract DeployDepositAddressBridger is Script { zeroXChainIds.length; // Initialize arrays for the combined result + destinationTypes = new DestinationType[](totalChains); chainIds = new uint256[](totalChains); - stableOuts = new address[](totalChains); + stableOuts = new bytes[](totalChains); bridgers = new address[](totalChains); + recipientModes = new BridgeRecipientMode[](totalChains); uint256 index = 0; // Add CCTP V2 routes for (uint256 i = 0; i < cctpV2ChainIds.length; ++i) { + destinationTypes[index] = DestinationType.EVM; chainIds[index] = cctpV2ChainIds[i]; - stableOuts[index] = cctpV2BridgeRoutes[i].bridgeTokenOut; + stableOuts[index] = DestinationUtils.evmAddressToBytes( + cctpV2BridgeRoutes[i].bridgeTokenOut + ); bridgers[index] = cctpV2Bridger; + recipientModes[index] = BridgeRecipientMode.FULFILLMENT; index++; } // Add Stargate USDC routes for (uint256 i = 0; i < stargateUSDCChainIds.length; ++i) { + destinationTypes[index] = DestinationType.EVM; chainIds[index] = stargateUSDCChainIds[i]; - stableOuts[index] = stargateUSDCBridgeRoutes[i].bridgeTokenOut; + stableOuts[index] = DestinationUtils.evmAddressToBytes( + stargateUSDCBridgeRoutes[i].bridgeTokenOut + ); bridgers[index] = stargateUSDCBridger; + recipientModes[index] = BridgeRecipientMode.FULFILLMENT; index++; } // Add Stargate USDT routes for (uint256 i = 0; i < stargateUSDTChainIds.length; ++i) { + destinationTypes[index] = DestinationType.EVM; chainIds[index] = stargateUSDTChainIds[i]; - stableOuts[index] = stargateUSDTBridgeRoutes[i].bridgeTokenOut; + stableOuts[index] = DestinationUtils.evmAddressToBytes( + stargateUSDTBridgeRoutes[i].bridgeTokenOut + ); bridgers[index] = stargateUSDTBridger; + recipientModes[index] = BridgeRecipientMode.FULFILLMENT; index++; } // Add Legacy Mesh routes for (uint256 i = 0; i < legacyMeshChainIds.length; i++) { + destinationTypes[index] = DestinationType.EVM; chainIds[index] = legacyMeshChainIds[i]; - stableOuts[index] = legacyMeshBridgeRoutes[i].bridgeTokenOut; + stableOuts[index] = DestinationUtils.evmAddressToBytes( + legacyMeshBridgeRoutes[i].bridgeTokenOut + ); bridgers[index] = legacyMeshBridger; + recipientModes[index] = BridgeRecipientMode.FULFILLMENT; index++; } // Add Hop routes for (uint256 i = 0; i < hopDestChainIds.length; ++i) { + destinationTypes[index] = finalChainCoins[i].destinationType; chainIds[index] = hopDestChainIds[i]; - stableOuts[index] = finalChainCoins[i].coinAddr; + stableOuts[index] = finalChainCoins[i].coin; bridgers[index] = hopBridger; + recipientModes[index] = BridgeRecipientMode.FULFILLMENT; index++; } // Add USDT0 routes for (uint256 i = 0; i < usdt0ChainIds.length; ++i) { + destinationTypes[index] = DestinationType.EVM; chainIds[index] = usdt0ChainIds[i]; - stableOuts[index] = usdt0BridgeRoutes[i].bridgeTokenOut; + stableOuts[index] = DestinationUtils.evmAddressToBytes( + usdt0BridgeRoutes[i].bridgeTokenOut + ); bridgers[index] = usdt0Bridger; + recipientModes[index] = BridgeRecipientMode.FULFILLMENT; index++; } // Add 0x routes for (uint256 i = 0; i < zeroXChainIds.length; ++i) { + destinationTypes[index] = zeroXDestinationTypes[i]; chainIds[index] = zeroXChainIds[i]; stableOuts[index] = zeroXBridgeTokenOuts[i]; bridgers[index] = zeroXBridger; + recipientModes[index] = zeroXDestinationTypes[i] == + DestinationType.EVM + ? BridgeRecipientMode.FULFILLMENT + : BridgeRecipientMode.DIRECT; index++; } - return (chainIds, stableOuts, bridgers); + return ( + destinationTypes, + chainIds, + stableOuts, + bridgers, + recipientModes + ); } // Exclude from forge coverage diff --git a/packages/contract/script/da/constants/DAHopBridgeRouteConstants.sol b/packages/contract/script/da/constants/DAHopBridgeRouteConstants.sol index a503092..bd7b7fe 100644 --- a/packages/contract/script/da/constants/DAHopBridgeRouteConstants.sol +++ b/packages/contract/script/da/constants/DAHopBridgeRouteConstants.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.12; import "../../../src/DaimoPayHopBridger.sol"; +import "../../../src/DestinationUtils.sol"; import { DEPLOY_SALT_CCTP_V2_BRIDGER, DEPLOY_SALT_LEGACY_MESH_BRIDGER, @@ -155,16 +156,18 @@ function getDAHopBridgeRoutes( // 10 -> 100 USDC destChainIds[0] = 100; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 10 -> 42220 USDT destChainIds[1] = 42220; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -179,16 +182,18 @@ function getDAHopBridgeRoutes( // 56 -> 4326 USDT destChainIds[0] = 4326; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 56 -> 42220 USDT destChainIds[1] = 42220; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -197,62 +202,78 @@ function getDAHopBridgeRoutes( // Source chain 100 if (sourceChainId == 100) { - destChainIds = new uint256[](7); - finalChainCoins = new DaimoPayHopBridger.FinalChainCoin[](7); + destChainIds = new uint256[](8); + finalChainCoins = new DaimoPayHopBridger.FinalChainCoin[](8); // 100 -> 10 USDC destChainIds[0] = 10; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 10, - coinAddr: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, + coin: abi.encodePacked(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85), coinDecimals: 6 }); // 100 -> 143 USDC destChainIds[1] = 143; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 143, - coinAddr: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, + coin: abi.encodePacked(0x754704Bc059F8C67012fEd69BC8A327a5aafb603), coinDecimals: 6 }); // 100 -> 480 USDC destChainIds[2] = 480; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); - // 100 -> 999 USDC - destChainIds[3] = 999; + // 100 -> 501 USDC + destChainIds[3] = 501; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.SOLANA, + finalChainId: 501, + coin: hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61", + coinDecimals: 6 + }); + + // 100 -> 999 USDC + destChainIds[4] = 999; + finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 999, - coinAddr: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, + coin: abi.encodePacked(0xb88339CB7199b77E23DB6E890353E22632Ba630f), coinDecimals: 6 }); // 100 -> 4326 USDT - destChainIds[4] = 4326; - finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[5] = 4326; + finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 100 -> 42220 USDT - destChainIds[5] = 42220; - finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[6] = 42220; + finalChainCoins[6] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); // 100 -> 59144 USDC - destChainIds[6] = 59144; - finalChainCoins[6] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[7] = 59144; + finalChainCoins[7] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -267,8 +288,9 @@ function getDAHopBridgeRoutes( // 137 -> 42220 USDT destChainIds[0] = 42220; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -283,24 +305,27 @@ function getDAHopBridgeRoutes( // 143 -> 100 USDC destChainIds[0] = 100; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 143 -> 4217 USDC destChainIds[1] = 4217; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 143 -> 42220 USDT destChainIds[2] = 42220; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -315,24 +340,27 @@ function getDAHopBridgeRoutes( // 480 -> 100 USDC destChainIds[0] = 100; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 480 -> 4326 USDT destChainIds[1] = 4326; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 480 -> 42220 USDT destChainIds[2] = 42220; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -347,32 +375,36 @@ function getDAHopBridgeRoutes( // 999 -> 56 USDC destChainIds[0] = 56; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 999 -> 100 USDC destChainIds[1] = 100; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 999 -> 4217 USDC destChainIds[2] = 4217; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 999 -> 42220 USDT destChainIds[3] = 42220; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -381,54 +413,51 @@ function getDAHopBridgeRoutes( // Source chain 4217 if (sourceChainId == 4217) { - destChainIds = new uint256[](6); - finalChainCoins = new DaimoPayHopBridger.FinalChainCoin[](6); + destChainIds = new uint256[](5); + finalChainCoins = new DaimoPayHopBridger.FinalChainCoin[](5); // 4217 -> 143 USDC destChainIds[0] = 143; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 143, - coinAddr: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, + coin: abi.encodePacked(0x754704Bc059F8C67012fEd69BC8A327a5aafb603), coinDecimals: 6 }); // 4217 -> 480 USDC destChainIds[1] = 480; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); // 4217 -> 999 USDC destChainIds[2] = 999; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 999, - coinAddr: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, - coinDecimals: 6 - }); - - // 4217 -> 4326 USDT - destChainIds[3] = 4326; - finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ - finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xb88339CB7199b77E23DB6E890353E22632Ba630f), coinDecimals: 6 }); // 4217 -> 42220 USDT - destChainIds[4] = 42220; - finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[3] = 42220; + finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); // 4217 -> 59144 USDC - destChainIds[5] = 59144; - finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[4] = 59144; + finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -443,56 +472,63 @@ function getDAHopBridgeRoutes( // 4326 -> 56 USDC destChainIds[0] = 56; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 4326 -> 100 USDC destChainIds[1] = 100; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 4326 -> 480 USDC destChainIds[2] = 480; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); - // 4326 -> 4217 USDC - destChainIds[3] = 4217; + // 4326 -> 501 USDC + destChainIds[3] = 501; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ - finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + destinationType: DestinationType.SOLANA, + finalChainId: 501, + coin: hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61", coinDecimals: 6 }); // 4326 -> 8453 USDC destChainIds[4] = 8453; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 8453, - coinAddr: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, + coin: abi.encodePacked(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913), coinDecimals: 6 }); // 4326 -> 42220 USDT destChainIds[5] = 42220; finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); // 4326 -> 59144 USDC destChainIds[6] = 59144; finalChainCoins[6] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -507,16 +543,18 @@ function getDAHopBridgeRoutes( // 8453 -> 4326 USDT destChainIds[0] = 4326; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 8453 -> 42220 USDT destChainIds[1] = 42220; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -525,94 +563,114 @@ function getDAHopBridgeRoutes( // Source chain 42220 if (sourceChainId == 42220) { - destChainIds = new uint256[](11); - finalChainCoins = new DaimoPayHopBridger.FinalChainCoin[](11); + destChainIds = new uint256[](12); + finalChainCoins = new DaimoPayHopBridger.FinalChainCoin[](12); // 42220 -> 10 USDC destChainIds[0] = 10; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 10, - coinAddr: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, + coin: abi.encodePacked(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85), coinDecimals: 6 }); // 42220 -> 56 USDC destChainIds[1] = 56; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 42220 -> 100 USDC destChainIds[2] = 100; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 42220 -> 137 USDC destChainIds[3] = 137; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 137, - coinAddr: 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, + coin: abi.encodePacked(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359), coinDecimals: 6 }); // 42220 -> 143 USDC destChainIds[4] = 143; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 143, - coinAddr: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, + coin: abi.encodePacked(0x754704Bc059F8C67012fEd69BC8A327a5aafb603), coinDecimals: 6 }); // 42220 -> 480 USDC destChainIds[5] = 480; finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); - // 42220 -> 999 USDC - destChainIds[6] = 999; + // 42220 -> 501 USDC + destChainIds[6] = 501; finalChainCoins[6] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.SOLANA, + finalChainId: 501, + coin: hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61", + coinDecimals: 6 + }); + + // 42220 -> 999 USDC + destChainIds[7] = 999; + finalChainCoins[7] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 999, - coinAddr: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, + coin: abi.encodePacked(0xb88339CB7199b77E23DB6E890353E22632Ba630f), coinDecimals: 6 }); // 42220 -> 4217 USDC - destChainIds[7] = 4217; - finalChainCoins[7] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[8] = 4217; + finalChainCoins[8] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 42220 -> 4326 USDT - destChainIds[8] = 4326; - finalChainCoins[8] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[9] = 4326; + finalChainCoins[9] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 42220 -> 8453 USDC - destChainIds[9] = 8453; - finalChainCoins[9] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[10] = 8453; + finalChainCoins[10] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 8453, - coinAddr: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, + coin: abi.encodePacked(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913), coinDecimals: 6 }); // 42220 -> 59144 USDC - destChainIds[10] = 59144; - finalChainCoins[10] = DaimoPayHopBridger.FinalChainCoin({ + destChainIds[11] = 59144; + finalChainCoins[11] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -627,24 +685,27 @@ function getDAHopBridgeRoutes( // 59144 -> 100 USDC destChainIds[0] = 100; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 59144 -> 4326 USDT destChainIds[1] = 4326; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 59144 -> 42220 USDT destChainIds[2] = 42220; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); diff --git a/packages/contract/script/da/constants/DAZeroXBridgeRouteConstants.sol b/packages/contract/script/da/constants/DAZeroXBridgeRouteConstants.sol index b0490a7..b448e36 100644 --- a/packages/contract/script/da/constants/DAZeroXBridgeRouteConstants.sol +++ b/packages/contract/script/da/constants/DAZeroXBridgeRouteConstants.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.12; import "../../../src/DAZeroXBridger.sol"; +import "../../../src/DestinationUtils.sol"; // @title DAZeroXBridgeRouteConstants // @notice Auto-generated DA constants for 0x bridge routes @@ -12,295 +13,462 @@ function getDAZeroXBridgeRoutes( ) pure returns ( + DestinationType[] memory destinationTypes, uint256[] memory toChainIds, - address[] memory bridgeTokenOuts, + bytes[] memory bridgeTokenOuts, DAZeroXBridger.ZeroXRoute[] memory bridgeRoutes ) { + // Source chain 1 + if (sourceChainId == 1) { + destinationTypes = new DestinationType[](1); + toChainIds = new uint256[](1); + bridgeTokenOuts = new bytes[](1); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](1); + + // 1 -> 501 USDC + destinationTypes[0] = DestinationType.SOLANA; + toChainIds[0] = 501; + bridgeTokenOuts[0] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; + bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); + + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); + } + // Source chain 10 if (sourceChainId == 10) { - toChainIds = new uint256[](2); - bridgeTokenOuts = new address[](2); - bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](2); + destinationTypes = new DestinationType[](3); + toChainIds = new uint256[](3); + bridgeTokenOuts = new bytes[](3); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](3); // 10 -> 56 USDC + destinationTypes[0] = DestinationType.EVM; toChainIds[0] = 56; - bridgeTokenOuts[0] = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; + bridgeTokenOuts[0] = abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d); bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 18 }); // 10 -> 56 USDT + destinationTypes[1] = DestinationType.EVM; toChainIds[1] = 56; - bridgeTokenOuts[1] = 0x55d398326f99059fF775485246999027B3197955; + bridgeTokenOuts[1] = abi.encodePacked(0x55d398326f99059fF775485246999027B3197955); bridgeRoutes[1] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 18 }); + // 10 -> 501 USDC + destinationTypes[2] = DestinationType.SOLANA; + toChainIds[2] = 501; + bridgeTokenOuts[2] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; + bridgeRoutes[2] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); - return (toChainIds, bridgeTokenOuts, bridgeRoutes); + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); } // Source chain 56 if (sourceChainId == 56) { - toChainIds = new uint256[](12); - bridgeTokenOuts = new address[](12); - bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](12); + destinationTypes = new DestinationType[](13); + toChainIds = new uint256[](13); + bridgeTokenOuts = new bytes[](13); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](13); // 56 -> 1 USDT + destinationTypes[0] = DestinationType.EVM; toChainIds[0] = 1; - bridgeTokenOuts[0] = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + bridgeTokenOuts[0] = abi.encodePacked(0xdAC17F958D2ee523a2206206994597C13D831ec7); bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x55d398326f99059fF775485246999027B3197955, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 10 USDC + destinationTypes[1] = DestinationType.EVM; toChainIds[1] = 10; - bridgeTokenOuts[1] = 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85; + bridgeTokenOuts[1] = abi.encodePacked(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); bridgeRoutes[1] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 10 USDC + destinationTypes[2] = DestinationType.EVM; toChainIds[2] = 10; - bridgeTokenOuts[2] = 0x01bFF41798a0BcF287b996046Ca68b395DbC1071; + bridgeTokenOuts[2] = abi.encodePacked(0x01bFF41798a0BcF287b996046Ca68b395DbC1071); bridgeRoutes[2] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x55d398326f99059fF775485246999027B3197955, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 10 USDT + destinationTypes[3] = DestinationType.EVM; toChainIds[3] = 10; - bridgeTokenOuts[3] = 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; + bridgeTokenOuts[3] = abi.encodePacked(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58); bridgeRoutes[3] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x55d398326f99059fF775485246999027B3197955, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 143 USDC + destinationTypes[4] = DestinationType.EVM; toChainIds[4] = 143; - bridgeTokenOuts[4] = 0x754704Bc059F8C67012fEd69BC8A327a5aafb603; + bridgeTokenOuts[4] = abi.encodePacked(0x754704Bc059F8C67012fEd69BC8A327a5aafb603); bridgeRoutes[4] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 143 USDT + destinationTypes[5] = DestinationType.EVM; toChainIds[5] = 143; - bridgeTokenOuts[5] = 0xe7cd86e13AC4309349F30B3435a9d337750fC82D; + bridgeTokenOuts[5] = abi.encodePacked(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); bridgeRoutes[5] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x55d398326f99059fF775485246999027B3197955, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 480 USDC + destinationTypes[6] = DestinationType.EVM; toChainIds[6] = 480; - bridgeTokenOuts[6] = 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1; + bridgeTokenOuts[6] = abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1); bridgeRoutes[6] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); - // 56 -> 999 USDC - toChainIds[7] = 999; - bridgeTokenOuts[7] = 0xb88339CB7199b77E23DB6E890353E22632Ba630f; + // 56 -> 501 USDC + destinationTypes[7] = DestinationType.SOLANA; + toChainIds[7] = 501; + bridgeTokenOuts[7] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; bridgeRoutes[7] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); - // 56 -> 999 USDT + // 56 -> 999 USDC + destinationTypes[8] = DestinationType.EVM; toChainIds[8] = 999; - bridgeTokenOuts[8] = 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb; + bridgeTokenOuts[8] = abi.encodePacked(0xb88339CB7199b77E23DB6E890353E22632Ba630f); bridgeRoutes[8] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + bridgeTokenInDecimals: 18, + bridgeTokenOutDecimals: 6 + }); + // 56 -> 999 USDT + destinationTypes[9] = DestinationType.EVM; + toChainIds[9] = 999; + bridgeTokenOuts[9] = abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); + bridgeRoutes[9] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x55d398326f99059fF775485246999027B3197955, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 8453 USDT - toChainIds[9] = 8453; - bridgeTokenOuts[9] = 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2; - bridgeRoutes[9] = DAZeroXBridger.ZeroXRoute({ + destinationTypes[10] = DestinationType.EVM; + toChainIds[10] = 8453; + bridgeTokenOuts[10] = abi.encodePacked(0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2); + bridgeRoutes[10] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x55d398326f99059fF775485246999027B3197955, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 42161 USDT - toChainIds[10] = 42161; - bridgeTokenOuts[10] = 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; - bridgeRoutes[10] = DAZeroXBridger.ZeroXRoute({ + destinationTypes[11] = DestinationType.EVM; + toChainIds[11] = 42161; + bridgeTokenOuts[11] = abi.encodePacked(0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9); + bridgeRoutes[11] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x55d398326f99059fF775485246999027B3197955, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); // 56 -> 59144 USDC - toChainIds[11] = 59144; - bridgeTokenOuts[11] = 0x176211869cA2b568f2A7D4EE941E073a821EE1ff; - bridgeRoutes[11] = DAZeroXBridger.ZeroXRoute({ + destinationTypes[12] = DestinationType.EVM; + toChainIds[12] = 59144; + bridgeTokenOuts[12] = abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); + bridgeRoutes[12] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, bridgeTokenInDecimals: 18, bridgeTokenOutDecimals: 6 }); - return (toChainIds, bridgeTokenOuts, bridgeRoutes); + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); } // Source chain 137 if (sourceChainId == 137) { - toChainIds = new uint256[](1); - bridgeTokenOuts = new address[](1); - bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](1); + destinationTypes = new DestinationType[](2); + toChainIds = new uint256[](2); + bridgeTokenOuts = new bytes[](2); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](2); // 137 -> 10 USDT + destinationTypes[0] = DestinationType.EVM; toChainIds[0] = 10; - bridgeTokenOuts[0] = 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; + bridgeTokenOuts[0] = abi.encodePacked(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58); bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0xc2132D05D31c914a87C6611C10748AEb04B58e8F, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 6 }); + // 137 -> 501 USDC + destinationTypes[1] = DestinationType.SOLANA; + toChainIds[1] = 501; + bridgeTokenOuts[1] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; + bridgeRoutes[1] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); - return (toChainIds, bridgeTokenOuts, bridgeRoutes); + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); } // Source chain 143 if (sourceChainId == 143) { - toChainIds = new uint256[](2); - bridgeTokenOuts = new address[](2); - bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](2); + destinationTypes = new DestinationType[](3); + toChainIds = new uint256[](3); + bridgeTokenOuts = new bytes[](3); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](3); // 143 -> 56 USDC + destinationTypes[0] = DestinationType.EVM; toChainIds[0] = 56; - bridgeTokenOuts[0] = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; + bridgeTokenOuts[0] = abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d); bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 18 }); // 143 -> 56 USDT + destinationTypes[1] = DestinationType.EVM; toChainIds[1] = 56; - bridgeTokenOuts[1] = 0x55d398326f99059fF775485246999027B3197955; + bridgeTokenOuts[1] = abi.encodePacked(0x55d398326f99059fF775485246999027B3197955); bridgeRoutes[1] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0xe7cd86e13AC4309349F30B3435a9d337750fC82D, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 18 }); + // 143 -> 501 USDC + destinationTypes[2] = DestinationType.SOLANA; + toChainIds[2] = 501; + bridgeTokenOuts[2] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; + bridgeRoutes[2] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); - return (toChainIds, bridgeTokenOuts, bridgeRoutes); + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); } // Source chain 480 if (sourceChainId == 480) { - toChainIds = new uint256[](2); - bridgeTokenOuts = new address[](2); - bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](2); + destinationTypes = new DestinationType[](3); + toChainIds = new uint256[](3); + bridgeTokenOuts = new bytes[](3); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](3); // 480 -> 56 USDC + destinationTypes[0] = DestinationType.EVM; toChainIds[0] = 56; - bridgeTokenOuts[0] = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; + bridgeTokenOuts[0] = abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d); bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 18 }); - // 480 -> 4217 USDC - toChainIds[1] = 4217; - bridgeTokenOuts[1] = 0x20C000000000000000000000b9537d11c60E8b50; + // 480 -> 501 USDC + destinationTypes[1] = DestinationType.SOLANA; + toChainIds[1] = 501; + bridgeTokenOuts[1] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; bridgeRoutes[1] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 6 }); + // 480 -> 4217 USDC + destinationTypes[2] = DestinationType.EVM; + toChainIds[2] = 4217; + bridgeTokenOuts[2] = abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50); + bridgeRoutes[2] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); - return (toChainIds, bridgeTokenOuts, bridgeRoutes); + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); + } + + // Source chain 999 + if (sourceChainId == 999) { + destinationTypes = new DestinationType[](1); + toChainIds = new uint256[](1); + bridgeTokenOuts = new bytes[](1); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](1); + + // 999 -> 501 USDC + destinationTypes[0] = DestinationType.SOLANA; + toChainIds[0] = 501; + bridgeTokenOuts[0] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; + bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); + + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); + } + + // Source chain 4217 + if (sourceChainId == 4217) { + destinationTypes = new DestinationType[](1); + toChainIds = new uint256[](1); + bridgeTokenOuts = new bytes[](1); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](1); + + // 4217 -> 501 USDC + destinationTypes[0] = DestinationType.SOLANA; + toChainIds[0] = 501; + bridgeTokenOuts[0] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; + bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0x20C000000000000000000000b9537d11c60E8b50, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); + + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); } // Source chain 8453 if (sourceChainId == 8453) { - toChainIds = new uint256[](3); - bridgeTokenOuts = new address[](3); - bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](3); + destinationTypes = new DestinationType[](4); + toChainIds = new uint256[](4); + bridgeTokenOuts = new bytes[](4); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](4); // 8453 -> 10 USDC + destinationTypes[0] = DestinationType.EVM; toChainIds[0] = 10; - bridgeTokenOuts[0] = 0x01bFF41798a0BcF287b996046Ca68b395DbC1071; + bridgeTokenOuts[0] = abi.encodePacked(0x01bFF41798a0BcF287b996046Ca68b395DbC1071); bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 6 }); // 8453 -> 10 USDT + destinationTypes[1] = DestinationType.EVM; toChainIds[1] = 10; - bridgeTokenOuts[1] = 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; + bridgeTokenOuts[1] = abi.encodePacked(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58); bridgeRoutes[1] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 6 }); // 8453 -> 137 USDT + destinationTypes[2] = DestinationType.EVM; toChainIds[2] = 137; - bridgeTokenOuts[2] = 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + bridgeTokenOuts[2] = abi.encodePacked(0xc2132D05D31c914a87C6611C10748AEb04B58e8F); bridgeRoutes[2] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 6 }); + // 8453 -> 501 USDC + destinationTypes[3] = DestinationType.SOLANA; + toChainIds[3] = 501; + bridgeTokenOuts[3] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; + bridgeRoutes[3] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); - return (toChainIds, bridgeTokenOuts, bridgeRoutes); + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); } // Source chain 42161 if (sourceChainId == 42161) { - toChainIds = new uint256[](1); - bridgeTokenOuts = new address[](1); - bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](1); + destinationTypes = new DestinationType[](2); + toChainIds = new uint256[](2); + bridgeTokenOuts = new bytes[](2); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](2); // 42161 -> 10 USDT + destinationTypes[0] = DestinationType.EVM; toChainIds[0] = 10; - bridgeTokenOuts[0] = 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; + bridgeTokenOuts[0] = abi.encodePacked(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58); bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 6 }); + // 42161 -> 501 USDC + destinationTypes[1] = DestinationType.SOLANA; + toChainIds[1] = 501; + bridgeTokenOuts[1] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; + bridgeRoutes[1] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); - return (toChainIds, bridgeTokenOuts, bridgeRoutes); + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); } // Source chain 59144 if (sourceChainId == 59144) { - toChainIds = new uint256[](2); - bridgeTokenOuts = new address[](2); - bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](2); + destinationTypes = new DestinationType[](3); + toChainIds = new uint256[](3); + bridgeTokenOuts = new bytes[](3); + bridgeRoutes = new DAZeroXBridger.ZeroXRoute[](3); // 59144 -> 56 USDC + destinationTypes[0] = DestinationType.EVM; toChainIds[0] = 56; - bridgeTokenOuts[0] = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; + bridgeTokenOuts[0] = abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d); bridgeRoutes[0] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 18 }); - // 59144 -> 4217 USDC - toChainIds[1] = 4217; - bridgeTokenOuts[1] = 0x20C000000000000000000000b9537d11c60E8b50; + // 59144 -> 501 USDC + destinationTypes[1] = DestinationType.SOLANA; + toChainIds[1] = 501; + bridgeTokenOuts[1] = hex"c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61"; bridgeRoutes[1] = DAZeroXBridger.ZeroXRoute({ bridgeTokenIn: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, bridgeTokenInDecimals: 6, bridgeTokenOutDecimals: 6 }); + // 59144 -> 4217 USDC + destinationTypes[2] = DestinationType.EVM; + toChainIds[2] = 4217; + bridgeTokenOuts[2] = abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50); + bridgeRoutes[2] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); - return (toChainIds, bridgeTokenOuts, bridgeRoutes); + return (destinationTypes, toChainIds, bridgeTokenOuts, bridgeRoutes); } // If source chain not found, return empty arrays return ( + new DestinationType[](0), new uint256[](0), - new address[](0), + new bytes[](0), new DAZeroXBridger.ZeroXRoute[](0) ); } diff --git a/packages/contract/script/deploy.sh b/packages/contract/script/deploy.sh index d12bf06..c58c7d6 100755 --- a/packages/contract/script/deploy.sh +++ b/packages/contract/script/deploy.sh @@ -14,6 +14,7 @@ SCRIPTS=( # "script/da/DeployDaimoPayLegacyMeshBridger.s.sol" # "script/da/DeployDaimoPayUSDT0Bridger.s.sol" # "script/da/DeployDaimoPayHopBridger.s.sol" + # "script/da/DeployDAZeroXBridger.s.sol" # "script/da/DeployDepositAddressBridger.s.sol" # === DA core === @@ -84,9 +85,9 @@ for SCRIPT in "${SCRIPTS[@]}"; do elif [[ "$RPC_URL" == *"tempo"* ]]; then FORGE_CMD="forge script $SCRIPT --sig run --fork-url $RPC_URL --private-key $PRIVATE_KEY --verify --verifier sourcify --broadcast" elif [[ "$RPC_URL" == *"hyperliquid"* ]]; then - FORGE_CMD="forge script $SCRIPT --sig run --fork-url $RPC_URL --private-key $PRIVATE_KEY --verify --verifier etherscan --verifier-url https://api.etherscan.io/v2/api?chainid=999 --etherscan-api-key $ETHERSCAN_API_KEY --broadcast" + FORGE_CMD="forge script $SCRIPT --sig run --fork-url $RPC_URL --private-key $PRIVATE_KEY --verify --verifier etherscan --verifier-url https://api.etherscan.io/v2/api?chainid=999& --etherscan-api-key $ETHERSCAN_API_KEY --broadcast" elif [[ "$RPC_URL" == *"megaeth"* ]]; then - FORGE_CMD="forge script $SCRIPT --sig run --fork-url $RPC_URL --private-key $PRIVATE_KEY --verify --verifier etherscan --verifier-url https://api.etherscan.io/v2/api?chainid=4326 --etherscan-api-key $ETHERSCAN_API_KEY --broadcast" + FORGE_CMD="forge script $SCRIPT --sig run --fork-url $RPC_URL --private-key $PRIVATE_KEY --verify --verifier etherscan --verifier-url https://api.etherscan.io/v2/api?chainid=4326& --etherscan-api-key $ETHERSCAN_API_KEY --broadcast" else FORGE_CMD="forge script $SCRIPT --sig run --fork-url $RPC_URL --private-key $PRIVATE_KEY --verify --etherscan-api-key $ETHERSCAN_API_KEY --broadcast" fi @@ -121,7 +122,7 @@ for SCRIPT in "${SCRIPTS[@]}"; do # gas limit. 200M is safe — MegaETH's block gas limit is 10B. # NOTE: --skip-simulation also skips auto-verification. Contracts # must be verified manually after deploy using forge verify-contract - # with --verifier-url "https://api.etherscan.io/v2/api?chainid=4326". + # with --verifier-url "https://api.etherscan.io/v2/api?chainid=4326&". if [[ "$RPC_URL" == *"megaeth"* ]]; then FORGE_CMD="$FORGE_CMD --legacy --skip-simulation --gas-limit 200000000" fi diff --git a/packages/contract/script/pay/DeployDaimoPayHopBridger.s.sol b/packages/contract/script/pay/DeployDaimoPayHopBridger.s.sol index eb04d79..7d04202 100644 --- a/packages/contract/script/pay/DeployDaimoPayHopBridger.s.sol +++ b/packages/contract/script/pay/DeployDaimoPayHopBridger.s.sol @@ -45,7 +45,11 @@ contract DeployDaimoPayHopBridger is Script { console.log("--------------------------------"); for (uint256 i = 0; i < finalChains.length; ++i) { console.log("Final chain:", finalChains[i]); - console.log("Final coin address:", finalChainCoins[i].coinAddr); + console.log( + "Destination type:", + uint256(finalChainCoins[i].destinationType) + ); + console.logBytes(finalChainCoins[i].coin); console.log( "Final coin decimals:", finalChainCoins[i].coinDecimals diff --git a/packages/contract/script/pay/constants/HopBridgeRouteConstants.sol b/packages/contract/script/pay/constants/HopBridgeRouteConstants.sol index d9c0117..a08a3d9 100644 --- a/packages/contract/script/pay/constants/HopBridgeRouteConstants.sol +++ b/packages/contract/script/pay/constants/HopBridgeRouteConstants.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.12; import "../../../src/DaimoPayHopBridger.sol"; +import "../../../src/DestinationUtils.sol"; import { DEPLOY_SALT_ACROSS_BRIDGER, DEPLOY_SALT_CCTP_V2_BRIDGER, @@ -155,24 +156,27 @@ function getHopBridgeRoutes( // 10 -> 56 destChainIds[0] = 56; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 10 -> 100 destChainIds[1] = 100; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 10 -> 42220 destChainIds[2] = 42220; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -187,56 +191,63 @@ function getHopBridgeRoutes( // 56 -> 10 destChainIds[0] = 10; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 10, - coinAddr: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, + coin: abi.encodePacked(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85), coinDecimals: 6 }); // 56 -> 143 destChainIds[1] = 143; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 143, - coinAddr: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, + coin: abi.encodePacked(0x754704Bc059F8C67012fEd69BC8A327a5aafb603), coinDecimals: 6 }); // 56 -> 480 destChainIds[2] = 480; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); // 56 -> 999 destChainIds[3] = 999; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 999, - coinAddr: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, + coin: abi.encodePacked(0xb88339CB7199b77E23DB6E890353E22632Ba630f), coinDecimals: 6 }); // 56 -> 4326 destChainIds[4] = 4326; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 56 -> 42220 destChainIds[5] = 42220; finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); // 56 -> 59144 destChainIds[6] = 59144; finalChainCoins[6] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -251,56 +262,63 @@ function getHopBridgeRoutes( // 100 -> 10 destChainIds[0] = 10; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 10, - coinAddr: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, + coin: abi.encodePacked(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85), coinDecimals: 6 }); // 100 -> 143 destChainIds[1] = 143; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 143, - coinAddr: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, + coin: abi.encodePacked(0x754704Bc059F8C67012fEd69BC8A327a5aafb603), coinDecimals: 6 }); // 100 -> 480 destChainIds[2] = 480; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); // 100 -> 999 destChainIds[3] = 999; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 999, - coinAddr: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, + coin: abi.encodePacked(0xb88339CB7199b77E23DB6E890353E22632Ba630f), coinDecimals: 6 }); // 100 -> 4326 destChainIds[4] = 4326; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 100 -> 42220 destChainIds[5] = 42220; finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); // 100 -> 59144 destChainIds[6] = 59144; finalChainCoins[6] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -315,8 +333,9 @@ function getHopBridgeRoutes( // 137 -> 42220 destChainIds[0] = 42220; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -331,32 +350,36 @@ function getHopBridgeRoutes( // 143 -> 56 destChainIds[0] = 56; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 143 -> 100 destChainIds[1] = 100; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 143 -> 4217 destChainIds[2] = 4217; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 143 -> 42220 destChainIds[3] = 42220; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -371,40 +394,45 @@ function getHopBridgeRoutes( // 480 -> 56 destChainIds[0] = 56; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 480 -> 100 destChainIds[1] = 100; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 480 -> 4217 destChainIds[2] = 4217; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 480 -> 4326 destChainIds[3] = 4326; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 480 -> 42220 destChainIds[4] = 42220; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -419,32 +447,36 @@ function getHopBridgeRoutes( // 999 -> 56 destChainIds[0] = 56; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 999 -> 100 destChainIds[1] = 100; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 999 -> 4217 destChainIds[2] = 4217; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 999 -> 42220 destChainIds[3] = 42220; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -459,48 +491,54 @@ function getHopBridgeRoutes( // 4217 -> 143 destChainIds[0] = 143; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 143, - coinAddr: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, + coin: abi.encodePacked(0x754704Bc059F8C67012fEd69BC8A327a5aafb603), coinDecimals: 6 }); // 4217 -> 480 destChainIds[1] = 480; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); // 4217 -> 999 destChainIds[2] = 999; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 999, - coinAddr: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, + coin: abi.encodePacked(0xb88339CB7199b77E23DB6E890353E22632Ba630f), coinDecimals: 6 }); // 4217 -> 4326 destChainIds[3] = 4326; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 4217 -> 42220 destChainIds[4] = 42220; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); // 4217 -> 59144 destChainIds[5] = 59144; finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -515,56 +553,63 @@ function getHopBridgeRoutes( // 4326 -> 56 destChainIds[0] = 56; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 4326 -> 100 destChainIds[1] = 100; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 4326 -> 480 destChainIds[2] = 480; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); // 4326 -> 4217 destChainIds[3] = 4217; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 4326 -> 8453 destChainIds[4] = 8453; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 8453, - coinAddr: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, + coin: abi.encodePacked(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913), coinDecimals: 6 }); // 4326 -> 42220 destChainIds[5] = 42220; finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); // 4326 -> 59144 destChainIds[6] = 59144; finalChainCoins[6] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -579,16 +624,18 @@ function getHopBridgeRoutes( // 8453 -> 4326 destChainIds[0] = 4326; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 8453 -> 42220 destChainIds[1] = 42220; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); @@ -603,88 +650,99 @@ function getHopBridgeRoutes( // 42220 -> 10 destChainIds[0] = 10; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 10, - coinAddr: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, + coin: abi.encodePacked(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85), coinDecimals: 6 }); // 42220 -> 56 destChainIds[1] = 56; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 42220 -> 100 destChainIds[2] = 100; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 42220 -> 137 destChainIds[3] = 137; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 137, - coinAddr: 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, + coin: abi.encodePacked(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359), coinDecimals: 6 }); // 42220 -> 143 destChainIds[4] = 143; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 143, - coinAddr: 0x754704Bc059F8C67012fEd69BC8A327a5aafb603, + coin: abi.encodePacked(0x754704Bc059F8C67012fEd69BC8A327a5aafb603), coinDecimals: 6 }); // 42220 -> 480 destChainIds[5] = 480; finalChainCoins[5] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 480, - coinAddr: 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1, + coin: abi.encodePacked(0x79A02482A880bCE3F13e09Da970dC34db4CD24d1), coinDecimals: 6 }); // 42220 -> 999 destChainIds[6] = 999; finalChainCoins[6] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 999, - coinAddr: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, + coin: abi.encodePacked(0xb88339CB7199b77E23DB6E890353E22632Ba630f), coinDecimals: 6 }); // 42220 -> 4217 destChainIds[7] = 4217; finalChainCoins[7] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 42220 -> 4326 destChainIds[8] = 4326; finalChainCoins[8] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 42220 -> 8453 destChainIds[9] = 8453; finalChainCoins[9] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 8453, - coinAddr: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, + coin: abi.encodePacked(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913), coinDecimals: 6 }); // 42220 -> 59144 destChainIds[10] = 59144; finalChainCoins[10] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 59144, - coinAddr: 0x176211869cA2b568f2A7D4EE941E073a821EE1ff, + coin: abi.encodePacked(0x176211869cA2b568f2A7D4EE941E073a821EE1ff), coinDecimals: 6 }); @@ -699,40 +757,45 @@ function getHopBridgeRoutes( // 59144 -> 56 destChainIds[0] = 56; finalChainCoins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 56, - coinAddr: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, + coin: abi.encodePacked(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d), coinDecimals: 18 }); // 59144 -> 100 destChainIds[1] = 100; finalChainCoins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 100, - coinAddr: 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0, + coin: abi.encodePacked(0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0), coinDecimals: 6 }); // 59144 -> 4217 destChainIds[2] = 4217; finalChainCoins[2] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4217, - coinAddr: 0x20C000000000000000000000b9537d11c60E8b50, + coin: abi.encodePacked(0x20C000000000000000000000b9537d11c60E8b50), coinDecimals: 6 }); // 59144 -> 4326 destChainIds[3] = 4326; finalChainCoins[3] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 4326, - coinAddr: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb, + coin: abi.encodePacked(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb), coinDecimals: 6 }); // 59144 -> 42220 destChainIds[4] = 42220; finalChainCoins[4] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: 42220, - coinAddr: 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e, + coin: abi.encodePacked(0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e), coinDecimals: 6 }); diff --git a/packages/contract/src/DAZeroXBridger.sol b/packages/contract/src/DAZeroXBridger.sol index ff845b2..c36e80c 100644 --- a/packages/contract/src/DAZeroXBridger.sol +++ b/packages/contract/src/DAZeroXBridger.sol @@ -7,8 +7,10 @@ import "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; import "openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol"; +import "./DestinationUtils.sol"; import "./TokenUtils.sol"; import "./interfaces/IDaimoPayBridger.sol"; +import "./interfaces/IDepositAddressBridger.sol"; /// @author Daimo, Inc /// @custom:security-contact security@daimo.com @@ -24,9 +26,12 @@ import "./interfaces/IDaimoPayBridger.sol"; /// `minBuyAmount` may be smaller than `outAmount` due to slippage and /// underlying-bridge fees; the recipient eats that difference. /// Multiple destination tokens per chain are supported via the nested -/// `bridgeRouteMapping[toChainId][bridgeTokenOut] -> ZeroXRoute`. Caller -/// (DepositAddressBridger) always passes a single-element option array. -contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { +/// `bridgeRouteMapping[destinationType][toChainId][bridgeTokenOutHash]`. +contract DAZeroXBridger is + IDaimoPayBridger, + IDepositAddressBridgeAdapter, + ReentrancyGuard +{ using SafeERC20 for IERC20; using ECDSA for bytes32; using MessageHashUtils for bytes32; @@ -40,7 +45,7 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { struct SignedQuote { // route + amount. outAmount is denominated in bridgeTokenOut decimals; // the source-side inAmount is derived from outAmount + route decimals. - address bridgeTokenOut; + bytes bridgeTokenOut; uint256 outAmount; // execution address allowanceTarget; @@ -51,8 +56,9 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { uint256 timestamp; bytes32 quoteId; // bound to caller args + DestinationType destinationType; uint256 toChainId; - address toAddress; + bytes toAddress; // sig bytes signature; } @@ -66,10 +72,9 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { /// @notice Authorized to sweep balances. Set at deploy-time address public immutable owner; - /// @notice Maps (destination chainId, bridge output token) to the - /// configured route. The same chainId can have multiple bridgeTokenOut - /// entries with distinct bridgeTokenIn assets. - mapping(uint256 toChainId => mapping(address bridgeTokenOut => ZeroXRoute bridgeRoute)) + /// @notice Maps (destination type, chainId, bridge output token) to the + /// configured route. + mapping(DestinationType destinationType => mapping(uint256 toChainId => mapping(bytes32 bridgeTokenOutHash => ZeroXRoute bridgeRoute))) public bridgeRouteMapping; /// @notice Quote IDs already consumed. @@ -79,8 +84,9 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { address _owner, address _trustedSigner, uint256 _maxQuoteAge, + DestinationType[] memory _destinationTypes, uint256[] memory _toChainIds, - address[] memory _bridgeTokenOuts, + bytes[] memory _bridgeTokenOuts, ZeroXRoute[] memory _bridgeRoutes ) { require(_owner != address(0), "DAZX: invalid owner"); @@ -92,13 +98,29 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { uint256 n = _toChainIds.length; require( - n == _bridgeTokenOuts.length && n == _bridgeRoutes.length, + n == _destinationTypes.length && + n == _bridgeTokenOuts.length && + n == _bridgeRoutes.length, "DAZX: length mismatch" ); for (uint256 i = 0; i < n; ++i) { require( - _bridgeTokenOuts[i] != address(0) && - _bridgeRoutes[i].bridgeTokenIn != address(0), + DestinationUtils.isValidDestinationBytesMemory( + _destinationTypes[i], + _bridgeTokenOuts[i] + ), + "DAZX: bad route token" + ); + require( + _destinationTypes[i] != DestinationType.EVM || + DestinationUtils.decodeEvmAddressMemory( + _bridgeTokenOuts[i] + ) != + address(0), + "DAZX: zero token in route" + ); + require( + _bridgeRoutes[i].bridgeTokenIn != address(0), "DAZX: zero token in route" ); require( @@ -106,8 +128,8 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { _bridgeRoutes[i].bridgeTokenOutDecimals > 0, "DAZX: zero decimals in route" ); - bridgeRouteMapping[_toChainIds[i]][ - _bridgeTokenOuts[i] + bridgeRouteMapping[_destinationTypes[i]][_toChainIds[i]][ + keccak256(_bridgeTokenOuts[i]) ] = _bridgeRoutes[i]; } } @@ -126,18 +148,30 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { require(bridgeTokenOutOptions.length == 1, "DAZX: multiple options"); TokenAmount calldata option = bridgeTokenOutOptions[0]; - ZeroXRoute memory route = bridgeRouteMapping[toChainId][ - address(option.token) - ]; - require(route.bridgeTokenIn != address(0), "DAZX: route not found"); + return + _getBridgeTokenIn({ + destinationType: DestinationType.EVM, + toChainId: toChainId, + bridgeTokenOut: DestinationUtils.evmAddressToBytes( + address(option.token) + ), + outAmount: option.amount + }); + } - bridgeTokenIn = route.bridgeTokenIn; - inAmount = TokenUtils.convertTokenAmountDecimals({ - amount: option.amount, - fromDecimals: route.bridgeTokenOutDecimals, - toDecimals: route.bridgeTokenInDecimals, - roundUp: true - }); + /// @inheritdoc IDepositAddressBridgeAdapter + function getBridgeTokenIn( + DestinationType destinationType, + uint256 toChainId, + BridgeTokenAmount calldata tokenOut + ) external view returns (address bridgeTokenIn, uint256 inAmount) { + return + _getBridgeTokenIn({ + destinationType: destinationType, + toChainId: toChainId, + bridgeTokenOut: tokenOut.token, + outAmount: tokenOut.amount + }); } /// @inheritdoc IDaimoPayBridger @@ -151,26 +185,109 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { address refundAddress, bytes calldata extraData ) external nonReentrant { - require(toChainId != block.chainid, "DAZX: same chain"); // The DepositAddressBridger should only ever send one option require(bridgeTokenOutOptions.length == 1, "DAZX: multiple options"); TokenAmount calldata option = bridgeTokenOutOptions[0]; - require(option.amount > 0, "DAZX: zero amount"); + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: DestinationUtils.evmAddressToBytes(address(option.token)), + amount: option.amount + }); + + _sendToChain({ + destinationType: DestinationType.EVM, + toChainId: toChainId, + toAddress: DestinationUtils.evmAddressToBytes(toAddress), + tokenOut: tokenOut, + refundAddress: refundAddress, + extraData: extraData + }); + } + + /// @inheritdoc IDepositAddressBridgeAdapter + function sendToChain( + DestinationType destinationType, + uint256 toChainId, + bytes calldata toAddress, + BridgeTokenAmount calldata tokenOut, + address refundAddress, + bytes calldata extraData + ) external nonReentrant { + _sendToChain({ + destinationType: destinationType, + toChainId: toChainId, + toAddress: toAddress, + tokenOut: tokenOut, + refundAddress: refundAddress, + extraData: extraData + }); + } + + /// @notice Send the contract's full balance of `token` to `to`. + /// Pass `token == address(0)` to sweep native. Bridges may refund + /// unspent ERC20 / native to this contract. + function sweep(address token, address payable to) external { + require(msg.sender == owner, "DAZX: not owner"); + require(to != address(0), "DAZX: zero recipient"); + TokenUtils.transferBalance({token: IERC20(token), recipient: to}); + } + + function _sendToChain( + DestinationType destinationType, + uint256 toChainId, + bytes memory toAddress, + BridgeTokenAmount memory tokenOut, + address refundAddress, + bytes calldata extraData + ) private { + require(toChainId != block.chainid, "DAZX: same chain"); + require(tokenOut.amount > 0, "DAZX: zero amount"); + require( + DestinationUtils.isValidDestinationBytesMemory( + destinationType, + toAddress + ), + "DAZX: bad to address" + ); + require( + DestinationUtils.isValidDestinationBytesMemory( + destinationType, + tokenOut.token + ), + "DAZX: bad bridge token" + ); SignedQuote memory q = abi.decode(extraData, (SignedQuote)); require( - q.bridgeTokenOut == address(option.token), + q.destinationType == destinationType, + "DAZX: dest type mismatch" + ); + require(q.toChainId == toChainId, "DAZX: chain id mismatch"); + require( + keccak256(q.toAddress) == keccak256(toAddress), + "DAZX: to address mismatch" + ); + require( + keccak256(q.bridgeTokenOut) == keccak256(tokenOut.token), "DAZX: bridge token mismatch" ); - require(q.outAmount == option.amount, "DAZX: out amount mismatch"); + require(q.outAmount == tokenOut.amount, "DAZX: out amount mismatch"); + require( + block.timestamp <= q.timestamp + maxQuoteAge, + "DAZX: quote stale" + ); - ZeroXRoute memory route = bridgeRouteMapping[toChainId][ - q.bridgeTokenOut - ]; + ZeroXRoute memory route = bridgeRouteMapping[destinationType][ + toChainId + ][keccak256(q.bridgeTokenOut)]; require(route.bridgeTokenIn != address(0), "DAZX: route not found"); + require(!usedQuoteIds[q.quoteId], "DAZX: quote replayed"); + usedQuoteIds[q.quoteId] = true; + + _verifySignature(q, route.bridgeTokenIn); + // Convert dest-decimal outAmount to source-decimal inAmount. // Round up so we never under-fund the 0x call; any residue (zero in // the happy path) is swept to refundAddress below. @@ -181,19 +298,6 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { roundUp: true }); - require(q.toChainId == toChainId, "DAZX: chain id mismatch"); - require(q.toAddress == toAddress, "DAZX: to address mismatch"); - - require( - block.timestamp <= q.timestamp + maxQuoteAge, - "DAZX: quote stale" - ); - - require(!usedQuoteIds[q.quoteId], "DAZX: quote replayed"); - usedQuoteIds[q.quoteId] = true; - - _verifySignature(q, route.bridgeTokenIn); - // Move input token from caller to this contract and approve 0x. IERC20(route.bridgeTokenIn).safeTransferFrom({ from: msg.sender, @@ -234,10 +338,26 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { require(nativeOk, "DAZX: native refund failed"); } - emit BridgeInitiated({ + if (destinationType == DestinationType.EVM) { + emit BridgeInitiated({ + fromAddress: msg.sender, + fromToken: route.bridgeTokenIn, + fromAmount: inAmount, + toChainId: toChainId, + toAddress: DestinationUtils.decodeEvmAddressMemory(toAddress), + toToken: DestinationUtils.decodeEvmAddressMemory( + q.bridgeTokenOut + ), + toAmount: q.outAmount, + refundAddress: refundAddress + }); + } + + emit BridgeInitiatedBytes({ fromAddress: msg.sender, fromToken: route.bridgeTokenIn, fromAmount: inAmount, + destinationType: destinationType, toChainId: toChainId, toAddress: toAddress, toToken: q.bridgeTokenOut, @@ -246,15 +366,6 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { }); } - /// @notice Send the contract's full balance of `token` to `to`. - /// Pass `token == address(0)` to sweep native. Bridges may refund - /// unspent ERC20 / native to this contract. - function sweep(address token, address payable to) external { - require(msg.sender == owner, "DAZX: not owner"); - require(to != address(0), "DAZX: zero recipient"); - TokenUtils.transferBalance({token: IERC20(token), recipient: to}); - } - function _verifySignature( SignedQuote memory q, address bridgeTokenIn @@ -263,10 +374,11 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { abi.encode( block.chainid, address(this), + q.destinationType, q.toChainId, - q.toAddress, + keccak256(q.toAddress), bridgeTokenIn, - q.bridgeTokenOut, + keccak256(q.bridgeTokenOut), q.outAmount, q.allowanceTarget, q.callTarget, @@ -280,4 +392,24 @@ contract DAZeroXBridger is IDaimoPayBridger, ReentrancyGuard { address recovered = ethSignedMessageHash.recover(q.signature); require(recovered == trustedSigner, "DAZX: bad signature"); } + + function _getBridgeTokenIn( + DestinationType destinationType, + uint256 toChainId, + bytes memory bridgeTokenOut, + uint256 outAmount + ) private view returns (address bridgeTokenIn, uint256 inAmount) { + ZeroXRoute memory route = bridgeRouteMapping[destinationType][ + toChainId + ][keccak256(bridgeTokenOut)]; + require(route.bridgeTokenIn != address(0), "DAZX: route not found"); + + bridgeTokenIn = route.bridgeTokenIn; + inAmount = TokenUtils.convertTokenAmountDecimals({ + amount: outAmount, + fromDecimals: route.bridgeTokenOutDecimals, + toDecimals: route.bridgeTokenInDecimals, + roundUp: true + }); + } } diff --git a/packages/contract/src/DaimoPayHopBridger.sol b/packages/contract/src/DaimoPayHopBridger.sol index 97afbfa..7de053f 100644 --- a/packages/contract/src/DaimoPayHopBridger.sol +++ b/packages/contract/src/DaimoPayHopBridger.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.12; import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./DestinationUtils.sol"; import "./interfaces/IDaimoPayBridger.sol"; -import "../vendor/cctp/v1/ITokenMinter.sol"; -import "../vendor/cctp/v1/ICCTPTokenMessenger.sol"; +import "./interfaces/IDepositAddressBridger.sol"; /// @author Daimo, Inc /// @custom:security-contact security@daimo.com @@ -14,7 +14,7 @@ import "../vendor/cctp/v1/ICCTPTokenMessenger.sol"; /// @dev Hop bridger must ONLY be used with intent address destinations. /// It ignores bridgeTokenOutOptions, and relies on the intent address /// on the hop chain to perform the correct swap + final bridge. -contract DaimoPayHopBridger is IDaimoPayBridger { +contract DaimoPayHopBridger is IDaimoPayBridger, IDepositAddressBridgeAdapter { using SafeERC20 for IERC20; /// Hop chain ID, eg Arbitrum. @@ -28,12 +28,14 @@ contract DaimoPayHopBridger is IDaimoPayBridger { /// For each final dest chain, we require a specific stablecoin to be on the /// bridgeTokenOutOptions list. We convert that amount to the correct hop- /// coin amount at 1:1, accounting for decimals. - mapping(uint256 chainId => FinalChainCoin chainCoin) public finalChainCoins; + mapping(DestinationType destinationType => mapping(uint256 chainId => FinalChainCoin chainCoin)) + public finalChainCoins; /// Stablecoin required on the final chain. struct FinalChainCoin { + DestinationType destinationType; uint256 finalChainId; - address coinAddr; + bytes coin; uint256 coinDecimals; } @@ -49,7 +51,20 @@ contract DaimoPayHopBridger is IDaimoPayBridger { hopCoinDecimals = _hopCoinDecimals; hopBridger = _hopBridger; for (uint256 i = 0; i < _finalChainCoins.length; i++) { - finalChainCoins[ + require( + DestinationUtils.isValidDestinationBytesMemory( + _finalChainCoins[i].destinationType, + _finalChainCoins[i].coin + ), + "DPHB: bad final token" + ); + require( + finalChainCoins[_finalChainCoins[i].destinationType][ + _finalChainCoins[i].finalChainId + ].coin.length == 0, + "DPHB: duplicate final coin" + ); + finalChainCoins[_finalChainCoins[i].destinationType][ _finalChainCoins[i].finalChainId ] = _finalChainCoins[i]; } @@ -61,7 +76,7 @@ contract DaimoPayHopBridger is IDaimoPayBridger { uint256 toChainId, TokenAmount[] calldata bridgeTokenOutOptions ) external view returns (address bridgeTokenIn, uint256 inAmount) { - TokenAmount[] memory hopAssetOpts = _getHopAsset({ + TokenAmount[] memory hopAssetOpts = _getHopAssetFromEvmOptions({ toChainId: toChainId, tokenOpts: bridgeTokenOutOptions }); @@ -72,6 +87,26 @@ contract DaimoPayHopBridger is IDaimoPayBridger { }); } + /// Determine the input token and amount required for bytes-aware DA hop + /// routes, including non-EVM final destinations. + function getBridgeTokenIn( + DestinationType destinationType, + uint256 toChainId, + BridgeTokenAmount calldata tokenOut + ) external view returns (address bridgeTokenIn, uint256 inAmount) { + TokenAmount[] memory hopAssetOpts = _getHopAsset({ + destinationType: destinationType, + toChainId: toChainId, + token: tokenOut.token, + amount: tokenOut.amount + }); + + (bridgeTokenIn, inAmount) = hopBridger.getBridgeTokenIn({ + toChainId: hopChainId, + bridgeTokenOutOptions: hopAssetOpts + }); + } + /// Initiate a bridge to a destination chain via a hop chain. function sendToChain( uint256 toChainId, @@ -82,12 +117,75 @@ contract DaimoPayHopBridger is IDaimoPayBridger { ) public { require(toChainId != block.chainid, "DPHB: same chain"); - TokenAmount[] memory hopAssetOpts = _getHopAsset({ + TokenAmount[] memory hopAssetOpts = _getHopAssetFromEvmOptions({ toChainId: toChainId, tokenOpts: bridgeTokenOutOptions }); - (address inToken, uint256 inAmount) = hopBridger.getBridgeTokenIn({ + _sendHop({ + toAddress: toAddress, + hopAssetOpts: hopAssetOpts, + refundAddress: refundAddress, + extraData: extraData + }); + } + + /// Initiate a bytes-aware DA bridge to a final destination via the hop + /// chain. The recipient is still an EVM fulfillment address on the hop + /// chain; that fulfillment starts the final leg. + function sendToChain( + DestinationType destinationType, + uint256 toChainId, + bytes calldata toAddress, + BridgeTokenAmount calldata tokenOut, + address refundAddress, + bytes calldata extraData + ) external { + require(toChainId != block.chainid, "DPHB: same chain"); + require( + DestinationUtils.isValidDestinationBytes( + DestinationType.EVM, + toAddress + ), + "DPHB: bad hop recipient" + ); + + TokenAmount[] memory hopAssetOpts = _getHopAsset({ + destinationType: destinationType, + toChainId: toChainId, + token: tokenOut.token, + amount: tokenOut.amount + }); + + (address inToken, uint256 inAmount) = _sendHop({ + toAddress: DestinationUtils.decodeEvmAddress(toAddress), + hopAssetOpts: hopAssetOpts, + refundAddress: refundAddress, + extraData: extraData + }); + + emit BridgeInitiatedBytes({ + fromAddress: msg.sender, + fromToken: inToken, + fromAmount: inAmount, + destinationType: DestinationType.EVM, + toChainId: hopChainId, + toAddress: toAddress, + toToken: DestinationUtils.evmAddressToBytes( + address(hopAssetOpts[0].token) + ), + toAmount: hopAssetOpts[0].amount, + refundAddress: refundAddress + }); + } + + function _sendHop( + address toAddress, + TokenAmount[] memory hopAssetOpts, + address refundAddress, + bytes calldata extraData + ) private returns (address inToken, uint256 inAmount) { + (inToken, inAmount) = hopBridger.getBridgeTokenIn({ toChainId: hopChainId, bridgeTokenOutOptions: hopAssetOpts }); @@ -133,25 +231,65 @@ contract DaimoPayHopBridger is IDaimoPayBridger { /// 1:1 by decimals from the required final-chain stablecoin. Rounds up when /// reducing precision to avoid underfunding. function _getHopAsset( + DestinationType destinationType, uint256 toChainId, - TokenAmount[] calldata tokenOpts + bytes memory token, + uint256 amount ) internal view returns (TokenAmount[] memory) { - FinalChainCoin memory finalCoin = finalChainCoins[toChainId]; + FinalChainCoin memory finalCoin = _getFinalCoin({ + destinationType: destinationType, + toChainId: toChainId + }); require( - finalCoin.coinAddr != address(0), - "DPHB: unsupported dest chain" + keccak256(token) == keccak256(finalCoin.coin) && amount > 0, + "DPHB: required token missing" ); + return _buildHopAsset(finalCoin, amount); + } + + /// Build the hop-asset option from legacy EVM token options. + function _getHopAssetFromEvmOptions( + uint256 toChainId, + TokenAmount[] calldata tokenOpts + ) internal view returns (TokenAmount[] memory) { + FinalChainCoin memory finalCoin = _getFinalCoin({ + destinationType: DestinationType.EVM, + toChainId: toChainId + }); + bytes32 finalCoinHash = keccak256(finalCoin.coin); + uint256 finalAmount = 0; uint256 n = tokenOpts.length; for (uint256 i = 0; i < n; ++i) { - if (address(tokenOpts[i].token) == finalCoin.coinAddr) { + if ( + keccak256( + DestinationUtils.evmAddressToBytes( + address(tokenOpts[i].token) + ) + ) == finalCoinHash + ) { finalAmount = tokenOpts[i].amount; break; } } require(finalAmount > 0, "DPHB: required token missing"); + return _buildHopAsset(finalCoin, finalAmount); + } + + function _getFinalCoin( + DestinationType destinationType, + uint256 toChainId + ) private view returns (FinalChainCoin memory finalCoin) { + finalCoin = finalChainCoins[destinationType][toChainId]; + require(finalCoin.coin.length > 0, "DPHB: unsupported dest chain"); + } + + function _buildHopAsset( + FinalChainCoin memory finalCoin, + uint256 finalAmount + ) private view returns (TokenAmount[] memory) { uint256 convertedAmount = _convertStableAmount({ amount: finalAmount, fromDecimals: finalCoin.coinDecimals, diff --git a/packages/contract/src/DaimoPayLayerZeroBridger.sol b/packages/contract/src/DaimoPayLayerZeroBridger.sol index 74cd722..efd73c0 100644 --- a/packages/contract/src/DaimoPayLayerZeroBridger.sol +++ b/packages/contract/src/DaimoPayLayerZeroBridger.sol @@ -66,8 +66,9 @@ abstract contract DaimoPayLayerZeroBridger is IDaimoPayBridger { constructor(uint256[] memory _toChainIds, LZBridgeRoute[] memory _routes) { uint256 n = _toChainIds.length; require(n == _routes.length, "DPLZB: wrong routes length"); - for (uint256 i = 0; i < n; ++i) + for (uint256 i = 0; i < n; ++i) { bridgeRouteMapping[_toChainIds[i]] = _routes[i]; + } } /// @notice Accept native tokens for paying LayerZero fees and refunds. diff --git a/packages/contract/src/DepositAddress.sol b/packages/contract/src/DepositAddress.sol index 1c5a571..2cb830d 100644 --- a/packages/contract/src/DepositAddress.sol +++ b/packages/contract/src/DepositAddress.sol @@ -7,19 +7,22 @@ import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; import "./TokenUtils.sol"; +import "./DestinationUtils.sol"; import "./interfaces/IDepositAddressBridger.sol"; import "./interfaces/IDaimoPayPricer.sol"; /// @notice Parameters that uniquely identify a Deposit Address. struct DAParams { + /// Destination chain type + DestinationType destinationType; /// Destination chain uint256 toChainId; /// Final token received on destination chain - IERC20 toToken; + bytes toToken; /// Destination address. If finalCallData is empty, tokens are transferred /// here. Otherwise, tokens are transferred here and a call is made with /// finalCallData (e.g., toAddress is an adapter contract). - address toAddress; + bytes toAddress; /// Recipient for refunds address refundAddress; /// Optional calldata to execute on toAddress after swapping to toToken. @@ -52,7 +55,7 @@ struct DAFulfillmentParams { /// Unique salt/nonce provided by the relayer bytes32 relaySalt; /// Address and amount of token bridged to destination chain - TokenAmount bridgeTokenOut; + BridgeTokenAmount bridgeTokenOut; /// Chain ID where the bridge transfer originated uint256 sourceChainId; } diff --git a/packages/contract/src/DepositAddressBridger.sol b/packages/contract/src/DepositAddressBridger.sol index 5ca3722..5db08ad 100644 --- a/packages/contract/src/DepositAddressBridger.sol +++ b/packages/contract/src/DepositAddressBridger.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.12; import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./DestinationUtils.sol"; import "./TokenUtils.sol"; // Provides TokenAmount struct import "./interfaces/IDaimoPayBridger.sol"; import "./interfaces/IDepositAddressBridger.sol"; @@ -19,26 +20,53 @@ contract DepositAddressBridger is IDepositAddressBridger { // Immutable routing data (set once in the constructor) // --------------------------------------------------------------------- - /// Is a given bridging route (destination chainId, stableOut, bridger adapter) + /// Is a given bridging route (destination type, chainId, token, adapter) /// allowed? - mapping(uint256 toChainId => mapping(address stableOut => mapping(address bridgerAdapter => bool isAllowed))) + mapping(DestinationType destinationType => mapping(uint256 toChainId => mapping(bytes32 tokenOutHash => mapping(address bridgerAdapter => bool isAllowed)))) public isRouteAllowed; + /// Recipient mode for each allowed route. + mapping(DestinationType destinationType => mapping(uint256 toChainId => mapping(bytes32 tokenOutHash => mapping(address bridgerAdapter => BridgeRecipientMode mode)))) + public routeRecipientModes; + /// Set the allowed bridging routes. constructor( + DestinationType[] memory destinationTypes, uint256[] memory toChainIds, - address[] memory stableOut, - address[] memory bridgerAdapters + bytes[] memory tokenOuts, + address[] memory bridgerAdapters, + BridgeRecipientMode[] memory recipientModes ) { uint256 n = toChainIds.length; require( - n == stableOut.length && n == bridgerAdapters.length, + n == destinationTypes.length && + n == tokenOuts.length && + n == bridgerAdapters.length && + n == recipientModes.length, "DAB: length mismatch" ); for (uint256 i; i < n; ++i) { - isRouteAllowed[toChainIds[i]][stableOut[i]][ + DestinationType destinationType = destinationTypes[i]; + require( + DestinationUtils.isValidDestinationBytesMemory( + destinationType, + tokenOuts[i] + ), + "DAB: bad token out" + ); + require( + destinationType != DestinationType.EVM || + recipientModes[i] == BridgeRecipientMode.FULFILLMENT, + "DAB: evm direct route" + ); + + bytes32 tokenOutHash = keccak256(tokenOuts[i]); + isRouteAllowed[destinationType][toChainIds[i]][tokenOutHash][ bridgerAdapters[i] ] = true; + routeRecipientModes[destinationType][toChainIds[i]][tokenOutHash][ + bridgerAdapters[i] + ] = recipientModes[i]; } } @@ -48,23 +76,28 @@ contract DepositAddressBridger is IDepositAddressBridger { /// @inheritdoc IDepositAddressBridger function sendToChain( + DestinationType destinationType, uint256 toChainId, - address toAddress, - TokenAmount calldata stableOut, + bytes calldata toAddress, + bytes calldata fulfillmentAddress, + BridgeTokenAmount calldata tokenOut, address bridgerAdapter, address refundAddress, bytes calldata extraData ) external { - require( - isRouteAllowed[toChainId][address(stableOut.token)][bridgerAdapter], - "DAB: route not allowed" - ); + BridgeRecipientMode recipientMode = getBridgeRecipientMode({ + destinationType: destinationType, + toChainId: toChainId, + tokenOut: tokenOut, + bridgerAdapter: bridgerAdapter + }); - // Determine the required input asset and quantity for the requested bridge. - TokenAmount[] memory opts = _getSingleBridgeTokenOutOption(stableOut); - (address bridgeTokenIn, uint256 inAmount) = IDaimoPayBridger( - bridgerAdapter - ).getBridgeTokenIn({toChainId: toChainId, bridgeTokenOutOptions: opts}); + (address bridgeTokenIn, uint256 inAmount) = getBridgeTokenIn({ + destinationType: destinationType, + toChainId: toChainId, + tokenOut: tokenOut, + bridgerAdapter: bridgerAdapter + }); // Pull tokens from caller into this contract. IERC20(bridgeTokenIn).safeTransferFrom({ @@ -79,13 +112,58 @@ contract DepositAddressBridger is IDepositAddressBridger { value: inAmount }); - IDaimoPayBridger(bridgerAdapter).sendToChain({ - toChainId: toChainId, - toAddress: toAddress, - bridgeTokenOutOptions: opts, - refundAddress: refundAddress, - extraData: extraData - }); + if (recipientMode == BridgeRecipientMode.DIRECT) { + // Send directly to the toAddress + _sendToAdapter({ + destinationType: destinationType, + toChainId: toChainId, + bridgeRecipient: toAddress, + tokenOut: tokenOut, + bridgerAdapter: bridgerAdapter, + refundAddress: refundAddress, + extraData: extraData + }); + } else { + // Send to the fulfillment address + _sendToAdapter({ + destinationType: destinationType, + toChainId: toChainId, + bridgeRecipient: fulfillmentAddress, + tokenOut: tokenOut, + bridgerAdapter: bridgerAdapter, + refundAddress: refundAddress, + extraData: extraData + }); + } + } + + function _sendToAdapter( + DestinationType destinationType, + uint256 toChainId, + bytes calldata bridgeRecipient, + BridgeTokenAmount calldata tokenOut, + address bridgerAdapter, + address refundAddress, + bytes calldata extraData + ) private { + if (destinationType == DestinationType.EVM) { + IDaimoPayBridger(bridgerAdapter).sendToChain({ + toChainId: toChainId, + toAddress: DestinationUtils.decodeEvmAddress(bridgeRecipient), + bridgeTokenOutOptions: _getSingleBridgeTokenOutOption(tokenOut), + refundAddress: refundAddress, + extraData: extraData + }); + } else { + IDepositAddressBridgeAdapter(bridgerAdapter).sendToChain({ + destinationType: destinationType, + toChainId: toChainId, + toAddress: bridgeRecipient, + tokenOut: tokenOut, + refundAddress: refundAddress, + extraData: extraData + }); + } } // --------------------------------------------------------------------- @@ -94,27 +172,69 @@ contract DepositAddressBridger is IDepositAddressBridger { /// @inheritdoc IDepositAddressBridger function getBridgeTokenIn( + DestinationType destinationType, uint256 toChainId, - TokenAmount calldata stableOut, + BridgeTokenAmount calldata tokenOut, address bridgerAdapter ) public view returns (address bridgeTokenIn, uint256 inAmount) { require( - isRouteAllowed[toChainId][address(stableOut.token)][bridgerAdapter], + isRouteAllowed[destinationType][toChainId][ + keccak256(tokenOut.token) + ][bridgerAdapter], "DAB: route not allowed" ); - TokenAmount[] memory opts = _getSingleBridgeTokenOutOption(stableOut); - (bridgeTokenIn, inAmount) = IDaimoPayBridger(bridgerAdapter) - .getBridgeTokenIn({ - toChainId: toChainId, - bridgeTokenOutOptions: opts - }); + if (destinationType == DestinationType.EVM) { + (bridgeTokenIn, inAmount) = IDaimoPayBridger(bridgerAdapter) + .getBridgeTokenIn({ + toChainId: toChainId, + bridgeTokenOutOptions: _getSingleBridgeTokenOutOption( + tokenOut + ) + }); + } else { + (bridgeTokenIn, inAmount) = IDepositAddressBridgeAdapter( + bridgerAdapter + ).getBridgeTokenIn({ + destinationType: destinationType, + toChainId: toChainId, + tokenOut: tokenOut + }); + } } + /// @inheritdoc IDepositAddressBridger + function getBridgeRecipientMode( + DestinationType destinationType, + uint256 toChainId, + BridgeTokenAmount calldata tokenOut, + address bridgerAdapter + ) public view returns (BridgeRecipientMode) { + bytes32 tokenOutHash = keccak256(tokenOut.token); + require( + isRouteAllowed[destinationType][toChainId][tokenOutHash][ + bridgerAdapter + ], + "DAB: route not allowed" + ); + return + routeRecipientModes[destinationType][toChainId][tokenOutHash][ + bridgerAdapter + ]; + } + + /// @notice Converts a chain-agnostic token amount into the legacy EVM + /// bridger option shape. + /// @dev Only use this for `DestinationType.EVM` routes. This helper decodes + /// `tokenOut.token` as a 20-byte EVM address and will revert for + /// Solana or Tron destination token bytes. function _getSingleBridgeTokenOutOption( - TokenAmount calldata stableOut - ) private view returns (TokenAmount[] memory opts) { + BridgeTokenAmount calldata tokenOut + ) private pure returns (TokenAmount[] memory opts) { opts = new TokenAmount[](1); - opts[0] = stableOut; + opts[0] = TokenAmount({ + token: IERC20(DestinationUtils.decodeEvmAddress(tokenOut.token)), + amount: tokenOut.amount + }); } } diff --git a/packages/contract/src/DepositAddressManager.sol b/packages/contract/src/DepositAddressManager.sol index c1d0bbc..16e0fb3 100644 --- a/packages/contract/src/DepositAddressManager.sol +++ b/packages/contract/src/DepositAddressManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.12; +pragma solidity ^0.8.26; import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -10,9 +10,11 @@ import "openzeppelin-contracts/contracts/utils/Create2.sol"; import "./DepositAddressFactory.sol"; import "./DepositAddress.sol"; import "./DaimoPayExecutor.sol"; +import "./DestinationUtils.sol"; import "./TokenUtils.sol"; import "./SwapMath.sol"; import "./interfaces/IDaimoPayBridger.sol"; +import "./interfaces/IDepositAddressBridger.sol"; import "./interfaces/IDaimoPayPricer.sol"; /// @author Daimo, Inc @@ -38,6 +40,39 @@ import "./interfaces/IDaimoPayPricer.sol"; contract DepositAddressManager is Ownable, ReentrancyGuard { using SafeERC20 for IERC20; + error AlreadyClaimed(); + error AlreadyFinished(); + error BadBridgeToken(); + error BadDestinationAddress(); + error BadDestinationToken(); + error BridgeInputLow(); + error BridgePriceInvalid(); + error BridgeTokenMismatch(); + error BridgeTokenOutMismatch(); + error BridgeTokenOutPriceInvalid(); + error BridgedAmountTooLow(); + error DirectTokenMismatch(); + error EvmOnly(); + error Expired(); + error FinalCallUnsupported(); + error FulfillmentAddressMismatch(); + error FulfillmentUsed(); + error HopOnDestChain(); + error HopOnSourceChain(); + error Leg1BridgeTokenMismatch(); + error Leg1PriceInvalid(); + error Leg2PriceInvalid(); + error NotExpired(); + error NotRelayer(); + error PaymentPriceInvalid(); + error PaymentTokenMismatch(); + error SameChainFinishSource(); + error StartOnDestChain(); + error ToTokenMismatch(); + error ToTokenPriceInvalid(); + error WrongChain(); + error WrongEscrow(); + // --------------------------------------------------------------------- // Constants // --------------------------------------------------------------------- @@ -64,8 +99,8 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { /// Authorized relayer addresses. mapping(address relayer => bool authorized) public relayerAuthorized; - /// On the source chain, record fulfillment addresses that have been used. - mapping(address fulfillment => bool used) public fulfillmentUsed; + /// On the source chain, record fulfillments that have been used. + mapping(bytes32 fulfillmentId => bool used) public fulfillmentUsed; /// On the destination chain, map fulfillment address to status: /// - address(0) = not finished. @@ -82,9 +117,11 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { event Start( address indexed depositAddress, + bytes32 indexed fulfillmentId, address indexed fulfillmentAddress, DAParams params, DAFulfillmentParams fulfillment, + bytes bridgeRecipient, address paymentToken, uint256 paymentAmount, uint256 paymentTokenPriceUsd, @@ -160,7 +197,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { /// @dev Only allow designated relayers to call certain functions. modifier onlyRelayer() { - require(relayerAuthorized[msg.sender], "DAM: not relayer"); + require(relayerAuthorized[msg.sender], NotRelayer()); _; } @@ -206,7 +243,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { function start( DAParams calldata params, IERC20 paymentToken, - TokenAmount calldata bridgeTokenOut, + BridgeTokenAmount calldata bridgeTokenOut, PriceData calldata paymentTokenPrice, PriceData calldata bridgeTokenInPrice, address bridgerAdapter, @@ -214,9 +251,11 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { Call[] calldata calls, bytes calldata bridgeExtraData ) external nonReentrant onlyRelayer { - require(block.chainid != params.toChainId, "DAM: start on dest chain"); - require(params.escrow == address(this), "DAM: wrong escrow"); - require(!isDAExpired(params), "DAM: expired"); + require(block.chainid != params.toChainId, StartOnDestChain()); + require(params.escrow == address(this), WrongEscrow()); + require(!isDAExpired(params), Expired()); + _requireValidDestination(params); + _requireValidBridgeTokenOut(params, bridgeTokenOut); bool paymentTokenPriceValid = params.pricer.validatePrice( paymentTokenPrice @@ -224,11 +263,11 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { bool bridgeTokenInPriceValid = params.pricer.validatePrice( bridgeTokenInPrice ); - require(paymentTokenPriceValid, "DAM: payment price invalid"); - require(bridgeTokenInPriceValid, "DAM: bridge price invalid"); + require(paymentTokenPriceValid, PaymentPriceInvalid()); + require(bridgeTokenInPriceValid, BridgePriceInvalid()); require( paymentTokenPrice.token == address(paymentToken), - "DAM: payment token mismatch" + PaymentTokenMismatch() ); // Deploy (or fetch) deposit address @@ -240,27 +279,47 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { bridgeTokenOut: bridgeTokenOut, sourceChainId: block.chainid }); - (address fulfillmentAddress, ) = computeFulfillmentAddress(fulfillment); - - // Generate a unique fulfillment address for each bridge transfer. - // Without this check, a malicious relayer could reuse the same - // fulfillment address to claim multiple bridge transfers, double-paying - // themselves. - require(!fulfillmentUsed[fulfillmentAddress], "DAM: fulfillment used"); - // Mark the fulfillment address as used to prevent double-processing - fulfillmentUsed[fulfillmentAddress] = true; + bytes32 fulfillmentId = computeFulfillmentId(fulfillment); + ( + address payable computedFulfillmentAddress, + + ) = computeFulfillmentAddress(fulfillment); + bytes memory fulfillmentAddressBytes = DestinationUtils + .evmAddressToBytes(computedFulfillmentAddress); + BridgeRecipientMode recipientMode = params + .bridger + .getBridgeRecipientMode({ + destinationType: params.destinationType, + toChainId: params.toChainId, + tokenOut: bridgeTokenOut, + bridgerAdapter: bridgerAdapter + }); + address payable fulfillmentAddress = recipientMode == + BridgeRecipientMode.FULFILLMENT + ? computedFulfillmentAddress + : payable(address(0)); + bytes memory bridgeRecipient = recipientMode == + BridgeRecipientMode.DIRECT + ? params.toAddress + : fulfillmentAddressBytes; + + // Generate a unique fulfillment id for each bridge transfer. For EVM + // destinations this is also the CREATE2 salt for the fulfillment. + require(!fulfillmentUsed[fulfillmentId], FulfillmentUsed()); + fulfillmentUsed[fulfillmentId] = true; // Quote bridge input requirements. (address bridgeTokenIn, uint256 inAmount) = params .bridger .getBridgeTokenIn({ + destinationType: params.destinationType, toChainId: params.toChainId, - stableOut: bridgeTokenOut, + tokenOut: bridgeTokenOut, bridgerAdapter: bridgerAdapter }); require( bridgeTokenIn == address(bridgeTokenInPrice.token), - "DAM: bridge token mismatch" + BridgeTokenMismatch() ); // Send payment token to executor @@ -278,7 +337,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { sellAmount: paymentAmount, maxSlippage: params.maxStartSlippageBps }); - require(inAmount >= minSwapOutput.amount, "DAM: bridge input low"); + require(inAmount >= minSwapOutput.amount, BridgeInputLow()); // Run arbitrary calls provided by the relayer. These will generally // approve the swap contract and swap if necessary. @@ -302,9 +361,11 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { value: inAmount }); params.bridger.sendToChain({ + destinationType: params.destinationType, toChainId: params.toChainId, - toAddress: fulfillmentAddress, - stableOut: bridgeTokenOut, + toAddress: params.toAddress, + fulfillmentAddress: fulfillmentAddressBytes, + tokenOut: bridgeTokenOut, bridgerAdapter: bridgerAdapter, refundAddress: params.refundAddress, extraData: bridgeExtraData @@ -312,9 +373,11 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { emit Start({ depositAddress: address(da), + fulfillmentId: fulfillmentId, fulfillmentAddress: fulfillmentAddress, params: params, fulfillment: fulfillment, + bridgeRecipient: bridgeRecipient, paymentToken: address(paymentToken), paymentAmount: paymentAmount, paymentTokenPriceUsd: paymentTokenPrice.priceUsd, @@ -336,24 +399,23 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { PriceData calldata toTokenPrice, Call[] calldata calls ) external nonReentrant onlyRelayer { - require(params.toChainId == block.chainid, "DAM: wrong chain"); - require(params.escrow == address(this), "DAM: wrong escrow"); - require(!isDAExpired(params), "DAM: expired"); + _requireEvmDestination(params); + require(params.toChainId == block.chainid, WrongChain()); + require(params.escrow == address(this), WrongEscrow()); + require(!isDAExpired(params), Expired()); + IERC20 toToken = _decodeToToken(params); bool paymentTokenPriceValid = params.pricer.validatePrice( paymentTokenPrice ); bool toTokenPriceValid = params.pricer.validatePrice(toTokenPrice); - require(paymentTokenPriceValid, "DAM: payment price invalid"); - require(toTokenPriceValid, "DAM: toToken price invalid"); + require(paymentTokenPriceValid, PaymentPriceInvalid()); + require(toTokenPriceValid, ToTokenPriceInvalid()); require( paymentTokenPrice.token == address(paymentToken), - "DAM: payment token mismatch" - ); - require( - toTokenPrice.token == address(params.toToken), - "DAM: toToken mismatch" + PaymentTokenMismatch() ); + require(toTokenPrice.token == address(toToken), ToTokenMismatch()); // Deploy (or fetch) the Deposit Address for this params. DepositAddress da = depositAddressFactory.createDepositAddress(params); @@ -410,29 +472,30 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { IERC20 token, PriceData calldata bridgeTokenOutPrice, PriceData calldata toTokenPrice, - TokenAmount calldata bridgeTokenOut, + BridgeTokenAmount calldata bridgeTokenOut, bytes32 relaySalt, uint256 sourceChainId ) external nonReentrant onlyRelayer { - require(sourceChainId != block.chainid, "DAM: same chain finish"); - require(params.toChainId == block.chainid, "DAM: wrong chain"); - require(params.escrow == address(this), "DAM: wrong escrow"); - require(!isDAExpired(params), "DAM: expired"); + _requireEvmDestination(params); + _requireValidBridgeTokenOut(params, bridgeTokenOut); + require(sourceChainId != block.chainid, SameChainFinishSource()); + require(params.toChainId == block.chainid, WrongChain()); + require(params.escrow == address(this), WrongEscrow()); + require(!isDAExpired(params), Expired()); + IERC20 bridgeToken = _decodeBridgeToken(bridgeTokenOut); + IERC20 toToken = _decodeToToken(params); bool bridgeTokenOutPriceValid = params.pricer.validatePrice( bridgeTokenOutPrice ); bool toTokenPriceValid = params.pricer.validatePrice(toTokenPrice); - require(bridgeTokenOutPriceValid, "DAM: bridgeTokenOut price invalid"); - require(toTokenPriceValid, "DAM: toToken price invalid"); + require(bridgeTokenOutPriceValid, BridgeTokenOutPriceInvalid()); + require(toTokenPriceValid, ToTokenPriceInvalid()); require( - bridgeTokenOutPrice.token == address(bridgeTokenOut.token), - "DAM: bridgeTokenOut mismatch" - ); - require( - toTokenPrice.token == address(params.toToken), - "DAM: toToken mismatch" + bridgeTokenOutPrice.token == address(bridgeToken), + BridgeTokenOutMismatch() ); + require(toTokenPrice.token == address(toToken), ToTokenMismatch()); // Calculate salt for this bridge transfer. address da = depositAddressFactory.getDepositAddress(params); @@ -447,7 +510,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { // Check that the salt hasn't already been fast finished or claimed. require( fulfillmentToRecipient[fulfillmentAddress] == address(0), - "DAM: already finished" + AlreadyFinished() ); // Record relayer as new recipient when the bridged tokens arrive fulfillmentToRecipient[fulfillmentAddress] = msg.sender; @@ -493,14 +556,19 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { function claim( DAParams calldata params, Call[] calldata calls, - TokenAmount calldata bridgeTokenOut, + BridgeTokenAmount calldata bridgeTokenOut, PriceData calldata bridgeTokenOutPrice, PriceData calldata toTokenPrice, bytes32 relaySalt, uint256 sourceChainId ) external nonReentrant onlyRelayer { - require(params.toChainId == block.chainid, "DAM: wrong chain"); - require(params.escrow == address(this), "DAM: wrong escrow"); + _requireEvmDestination(params); + _requireValidBridgeTokenOut(params, bridgeTokenOut); + require(params.toChainId == block.chainid, WrongChain()); + require(params.escrow == address(this), WrongEscrow()); + IERC20 bridgeToken = _decodeBridgeToken(bridgeTokenOut); + IERC20 toToken = _decodeToToken(params); + address payable toAddress = _decodeToAddress(params); // Calculate salt for this bridge transfer. address da = depositAddressFactory.getDepositAddress(params); @@ -514,7 +582,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { // Check the recipient for this fulfillment. address recipient = fulfillmentToRecipient[fulfillmentAddress]; - require(recipient != ADDR_MAX, "DAM: already claimed"); + require(recipient != ADDR_MAX, AlreadyClaimed()); // Mark fulfillment as claimed fulfillmentToRecipient[fulfillmentAddress] = ADDR_MAX; @@ -522,12 +590,9 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { uint256 bridgedAmount; (fulfillmentAddress, bridgedAmount) = _deployAndPullFromFulfillment( fulfillment, - bridgeTokenOut.token - ); - require( - bridgedAmount >= bridgeTokenOut.amount, - "DAM: bridged amount too low" + bridgeToken ); + require(bridgedAmount >= bridgeTokenOut.amount, BridgedAmountTooLow()); uint256 outputAmount = 0; if (recipient == address(0)) { @@ -536,28 +601,22 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { bridgeTokenOutPrice ); bool toTokenPriceValid = params.pricer.validatePrice(toTokenPrice); + require(bridgeTokenOutPriceValid, BridgeTokenOutPriceInvalid()); + require(toTokenPriceValid, ToTokenPriceInvalid()); require( - bridgeTokenOutPriceValid, - "DAM: bridgeTokenOut price invalid" - ); - require(toTokenPriceValid, "DAM: toToken price invalid"); - require( - bridgeTokenOutPrice.token == address(bridgeTokenOut.token), - "DAM: bridgeTokenOut mismatch" - ); - require( - toTokenPrice.token == address(params.toToken), - "DAM: toToken mismatch" + bridgeTokenOutPrice.token == address(bridgeToken), + BridgeTokenOutMismatch() ); + require(toTokenPrice.token == address(toToken), ToTokenMismatch()); // No relayer showed up, so complete the fulfillment. Update the // recipient to the params's recipient. - recipient = params.toAddress; + recipient = toAddress; // Send tokens to the executor contract to run relayer-provided // calls in _finishFulfillment. TokenUtils.transfer({ - token: bridgeTokenOut.token, + token: bridgeToken, recipient: payable(address(executor)), amount: bridgedAmount }); @@ -582,7 +641,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { } else { // Otherwise, the relayer fastFinished the fulfillment. Repay them. TokenUtils.transfer({ - token: bridgeTokenOut.token, + token: bridgeToken, recipient: payable(recipient), amount: bridgedAmount }); @@ -624,17 +683,19 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { TokenAmount calldata leg1BridgeTokenOut, uint256 leg1SourceChainId, PriceData calldata leg1BridgeTokenOutPrice, - TokenAmount calldata leg2BridgeTokenOut, + BridgeTokenAmount calldata leg2BridgeTokenOut, PriceData calldata leg2BridgeTokenInPrice, address bridgerAdapter, bytes32 relaySalt, Call[] calldata calls, bytes calldata bridgeExtraData ) external nonReentrant onlyRelayer { + _requireValidDestination(params); + _requireValidBridgeTokenOut(params, leg2BridgeTokenOut); // Must be on hop chain (not source, not dest) - require(block.chainid != leg1SourceChainId, "DAM: hop on source chain"); - require(block.chainid != params.toChainId, "DAM: hop on dest chain"); - require(params.escrow == address(this), "DAM: wrong escrow"); + require(block.chainid != leg1SourceChainId, HopOnSourceChain()); + require(block.chainid != params.toChainId, HopOnDestChain()); + require(params.escrow == address(this), WrongEscrow()); // Validate prices bool leg1PriceValid = params.pricer.validatePrice( @@ -643,11 +704,11 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { bool leg2PriceValid = params.pricer.validatePrice( leg2BridgeTokenInPrice ); - require(leg1PriceValid, "DAM: leg1 price invalid"); - require(leg2PriceValid, "DAM: leg2 price invalid"); + require(leg1PriceValid, Leg1PriceInvalid()); + require(leg2PriceValid, Leg2PriceInvalid()); require( leg1BridgeTokenOutPrice.token == address(leg1BridgeTokenOut.token), - "DAM: leg1 bridge token mismatch" + Leg1BridgeTokenMismatch() ); // Compute the shared fulfillment address @@ -666,7 +727,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { // Check that the fulfillment hasn't been claimed already address recipient = fulfillmentToRecipient[fulfillmentAddress]; - require(recipient != ADDR_MAX, "DAM: already claimed"); + require(recipient != ADDR_MAX, AlreadyClaimed()); // Mark as claimed to prevent double-processing fulfillmentToRecipient[fulfillmentAddress] = ADDR_MAX; @@ -678,20 +739,22 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { ); // Ensure the fulfillment hasn't been used - require(!fulfillmentUsed[fulfillmentAddress], "DAM: fulfillment used"); - fulfillmentUsed[fulfillmentAddress] = true; + bytes32 fulfillmentId = computeFulfillmentId(fulfillment); + require(!fulfillmentUsed[fulfillmentId], FulfillmentUsed()); + fulfillmentUsed[fulfillmentId] = true; // Get bridge input requirements for leg 2 (address bridgeTokenIn, uint256 inAmount) = params .bridger .getBridgeTokenIn({ + destinationType: params.destinationType, toChainId: params.toChainId, - stableOut: leg2BridgeTokenOut, + tokenOut: leg2BridgeTokenOut, bridgerAdapter: bridgerAdapter }); require( bridgeTokenIn == address(leg2BridgeTokenInPrice.token), - "DAM: bridge token mismatch" + BridgeTokenMismatch() ); // Validate swap output meets minimum requirements @@ -701,7 +764,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { sellAmount: leg1BridgeTokenOut.amount, maxSlippage: params.maxStartSlippageBps }); - require(inAmount >= minSwapOutput.amount, "DAM: bridge input low"); + require(inAmount >= minSwapOutput.amount, BridgeInputLow()); // Send to executor, run swap calls, get bridge input TokenUtils.transfer({ @@ -728,9 +791,13 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { value: inAmount }); params.bridger.sendToChain({ + destinationType: params.destinationType, toChainId: params.toChainId, - toAddress: fulfillmentAddress, - stableOut: leg2BridgeTokenOut, + toAddress: params.toAddress, + fulfillmentAddress: DestinationUtils.evmAddressToBytes( + fulfillmentAddress + ), + tokenOut: leg2BridgeTokenOut, bridgerAdapter: bridgerAdapter, refundAddress: params.refundAddress, extraData: bridgeExtraData @@ -758,11 +825,11 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { DAParams calldata params, IERC20[] calldata tokens ) external nonReentrant { - require(params.escrow == address(this), "DAM: wrong escrow"); + require(params.escrow == address(this), WrongEscrow()); // Relayers can refund before expiry (e.g. emergency recovery). // Non-relayers must wait for expiry. if (!relayerAuthorized[msg.sender]) { - require(isDAExpired(params), "DAM: not expired"); + require(isDAExpired(params), NotExpired()); } // Deploy (or fetch) the Deposit Address for this params @@ -797,12 +864,14 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { /// @param tokens The tokens to refund from the fulfillment function refundFulfillment( DAParams calldata params, - TokenAmount calldata bridgeTokenOut, + BridgeTokenAmount calldata bridgeTokenOut, bytes32 relaySalt, uint256 sourceChainId, IERC20[] calldata tokens ) external nonReentrant onlyRelayer { - require(params.escrow == address(this), "DAM: wrong escrow"); + _requireValidDestination(params); + _requireValidBridgeTokenOut(params, bridgeTokenOut); + require(params.escrow == address(this), WrongEscrow()); // Can be refunded before expiry (e.g. emergency recovery). This is safe // because the function is only callable by relayers. @@ -820,7 +889,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { // Block refund if fast-finished, claimed, or hopped require( fulfillmentToRecipient[fulfillmentAddress] == address(0), - "DAM: already finished" + AlreadyFinished() ); // Mark as done to prevent subsequent claim/hopStart fulfillmentToRecipient[fulfillmentAddress] = ADDR_MAX; @@ -857,11 +926,20 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { function computeFulfillmentAddress( DAFulfillmentParams memory fulfillment ) public view returns (address payable addr, bytes32 relaySalt) { - relaySalt = keccak256(abi.encode(fulfillment)); + relaySalt = computeFulfillmentId(fulfillment); bytes memory initCode = type(DAFulfillment).creationCode; addr = payable(Create2.computeAddress(relaySalt, keccak256(initCode))); } + /// @notice Computes the chain-agnostic fulfillment id. + /// @param fulfillment The bridge fulfillment + /// @return fulfillmentId The id used for replay protection + function computeFulfillmentId( + DAFulfillmentParams memory fulfillment + ) public pure returns (bytes32 fulfillmentId) { + fulfillmentId = keccak256(abi.encode(fulfillment)); + } + /// @notice Checks if a Deposit Address params has expired. /// @param params The Deposit Address params to check /// @return true if the params has expired, false otherwise @@ -894,7 +972,7 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { fulfillmentContract = new DAFulfillment{salt: relaySalt}(); require( fulfillmentAddress == address(fulfillmentContract), - "DAM: fulfillment" + FulfillmentAddressMismatch() ); } else { fulfillmentContract = DAFulfillment(payable(fulfillmentAddress)); @@ -921,12 +999,15 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { Call[] calldata calls, uint256 minOutputAmount ) internal returns (uint256 outputAmount) { + IERC20 toToken = _decodeToToken(params); + address payable toAddress = _decodeToAddress(params); + if (params.finalCallData.length > 0) { // Swap and keep tokens in executor for final call outputAmount = executor.executeAndSendBalance({ calls: calls, minOutputAmount: TokenAmount({ - token: params.toToken, + token: toToken, amount: minOutputAmount }), recipient: payable(address(executor)) @@ -935,12 +1016,12 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { // Execute final call - approves token to toAddress and calls it (bool success, uint256 refundAmount) = executor.executeFinalCall({ finalCall: Call({ - to: params.toAddress, + to: toAddress, value: 0, data: params.finalCallData }), finalCallToken: TokenAmount({ - token: params.toToken, + token: toToken, amount: outputAmount }), refundAddr: payable(params.refundAddress) @@ -948,9 +1029,9 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { emit FinalCallExecuted( depositAddress, - params.toAddress, + toAddress, success, - address(params.toToken), + address(toToken), refundAmount ); } else { @@ -958,14 +1039,76 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { outputAmount = executor.executeAndSendBalance({ calls: calls, minOutputAmount: TokenAmount({ - token: params.toToken, + token: toToken, amount: minOutputAmount }), - recipient: payable(params.toAddress) + recipient: toAddress }); } } + function _requireValidDestination(DAParams calldata params) internal pure { + require( + DestinationUtils.isValidDestinationBytes( + params.destinationType, + params.toToken + ), + BadDestinationToken() + ); + require( + DestinationUtils.isValidDestinationBytes( + params.destinationType, + params.toAddress + ), + BadDestinationAddress() + ); + if (params.destinationType != DestinationType.EVM) { + require(params.finalCallData.length == 0, FinalCallUnsupported()); + } + } + + function _requireEvmDestination(DAParams calldata params) internal pure { + require(params.destinationType == DestinationType.EVM, EvmOnly()); + _requireValidDestination(params); + } + + function _requireValidBridgeTokenOut( + DAParams calldata params, + BridgeTokenAmount calldata bridgeTokenOut + ) internal pure { + require( + DestinationUtils.isValidDestinationBytes( + params.destinationType, + bridgeTokenOut.token + ), + BadBridgeToken() + ); + if (params.destinationType != DestinationType.EVM) { + require( + keccak256(bridgeTokenOut.token) == keccak256(params.toToken), + DirectTokenMismatch() + ); + } + } + + function _decodeToToken( + DAParams calldata params + ) internal pure returns (IERC20) { + return IERC20(DestinationUtils.decodeEvmAddress(params.toToken)); + } + + function _decodeToAddress( + DAParams calldata params + ) internal pure returns (address payable) { + return payable(DestinationUtils.decodeEvmAddress(params.toAddress)); + } + + function _decodeBridgeToken( + BridgeTokenAmount calldata bridgeTokenOut + ) internal pure returns (IERC20) { + return IERC20(DestinationUtils.decodeEvmAddress(bridgeTokenOut.token)); + } + // --------------------------------------------------------------------- // Admin functions // --------------------------------------------------------------------- @@ -992,6 +1135,8 @@ contract DepositAddressManager is Ownable, ReentrancyGuard { contract DAFulfillment { using SafeERC20 for IERC20; + error FulfillmentNotAuthorized(); + /// @notice Address allowed to pull funds from this contract address payable public immutable depositAddressManager; @@ -1017,7 +1162,10 @@ contract DAFulfillment { /// token == IERC20(address(0))) to the deployer address. /// @return amount The amount of tokens pulled function pull(IERC20 token) external returns (uint256) { - require(msg.sender == depositAddressManager, "BR: not authorized"); + require( + msg.sender == depositAddressManager, + FulfillmentNotAuthorized() + ); return TokenUtils.transferBalance({ token: token, diff --git a/packages/contract/src/DestinationUtils.sol b/packages/contract/src/DestinationUtils.sol new file mode 100644 index 0000000..79ddd07 --- /dev/null +++ b/packages/contract/src/DestinationUtils.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.12; + +enum DestinationType { + EVM, + SOLANA, + TRON +} + +struct BridgeTokenAmount { + bytes token; + uint256 amount; +} + +library DestinationUtils { + /// @notice Converts an EVM address to its canonical raw byte form. + /// @dev This uses `abi.encodePacked`, so the result is exactly 20 bytes. + /// Use this when populating chain-agnostic destination fields for EVM + /// tokens or recipients. + /// @param addr The EVM address to encode. + /// @return The 20-byte raw address representation. + function evmAddressToBytes( + address addr + ) internal pure returns (bytes memory) { + return abi.encodePacked(addr); + } + + /// @notice Checks whether calldata bytes are canonical for a destination type. + /// @dev EVM values are 20 raw bytes, Solana values are 32 raw bytes, and + /// Tron values are 21 raw bytes prefixed with `0x41`. This validates + /// both token identifiers and recipient addresses because their + /// canonical byte shapes are currently identical per destination type. + /// Unknown enum values return false instead of reverting. + /// @param destinationType The destination chain family that defines `data`. + /// @param data The raw token or address bytes to validate. + /// @return True if `data` matches the canonical byte format for `destinationType`. + function isValidDestinationBytes( + DestinationType destinationType, + bytes calldata data + ) internal pure returns (bool) { + if (destinationType == DestinationType.EVM) { + return data.length == 20; + } + if (destinationType == DestinationType.SOLANA) { + return data.length == 32; + } + if (destinationType == DestinationType.TRON) { + return data.length == 21 && data[0] == bytes1(0x41); + } + return false; + } + + /// @notice Checks whether memory bytes are canonical for a destination type. + /// @dev Memory variant of `isValidDestinationBytes` for structs, + /// constructor inputs, and intermediate values already loaded into + /// memory. Unknown enum values return false instead of reverting. + /// @param destinationType The destination chain family that defines `data`. + /// @param data The raw token or address bytes to validate. + /// @return True if `data` matches the canonical byte format for `destinationType`. + function isValidDestinationBytesMemory( + DestinationType destinationType, + bytes memory data + ) internal pure returns (bool) { + if (destinationType == DestinationType.EVM) { + return data.length == 20; + } + if (destinationType == DestinationType.SOLANA) { + return data.length == 32; + } + if (destinationType == DestinationType.TRON) { + return data.length == 21 && data[0] == bytes1(0x41); + } + return false; + } + + /// @notice Decodes a calldata raw EVM address. + /// @dev Reverts unless `addr` is exactly 20 bytes. The input must be the + /// raw packed address form, not ABI-encoded `address` data. + /// @param addr The 20-byte raw EVM address. + /// @return ret The decoded EVM address. + function decodeEvmAddress( + bytes calldata addr + ) internal pure returns (address ret) { + require(addr.length == 20, "DU: bad evm address"); + assembly { + ret := shr(96, calldataload(addr.offset)) + } + } + + /// @notice Decodes a memory raw EVM address. + /// @dev Reverts unless `addr` is exactly 20 bytes. The input must be the + /// raw packed address form, not ABI-encoded `address` data. + /// @param addr The 20-byte raw EVM address. + /// @return ret The decoded EVM address. + function decodeEvmAddressMemory( + bytes memory addr + ) internal pure returns (address ret) { + require(addr.length == 20, "DU: bad evm address"); + assembly { + ret := shr(96, mload(add(addr, 32))) + } + } + +} diff --git a/packages/contract/src/interfaces/IDepositAddressBridger.sol b/packages/contract/src/interfaces/IDepositAddressBridger.sol index 903ca4e..3e39c39 100644 --- a/packages/contract/src/interfaces/IDepositAddressBridger.sol +++ b/packages/contract/src/interfaces/IDepositAddressBridger.sol @@ -1,9 +1,28 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.12; -import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "../DestinationUtils.sol"; -import "../TokenUtils.sol"; +enum BridgeRecipientMode { + FULFILLMENT, + DIRECT +} + +/// @notice Shared Deposit Address bridge events emitted by the bridger +/// multiplexer and by bytes-aware bridge adapters. +interface IDepositAddressBridgeEvents { + event BridgeInitiatedBytes( + address indexed fromAddress, + address fromToken, + uint256 fromAmount, + DestinationType indexed destinationType, + uint256 indexed toChainId, + bytes toAddress, + bytes toToken, + uint256 toAmount, + address refundAddress + ); +} /// @notice Simplified bridging interface for the Deposit Address system /// that multiplexes between multiple bridge-specific adapters (e.g. @@ -11,8 +30,9 @@ import "../TokenUtils.sol"; interface IDepositAddressBridger { /// @notice Fetches a quote: what do I have to send in so that $x shows up /// on the destination? + /// @param destinationType Destination chain type /// @param toChainId Destination chain - /// @param stableOut The stablecoin token and amount to receive on + /// @param tokenOut The stablecoin token and amount to receive on /// the destination chain /// @param bridgerAdapter The bridger adapter to use /// @return bridgeTokenIn The asset that must be provided on the source @@ -20,26 +40,64 @@ interface IDepositAddressBridger { /// @return inAmount The exact quantity of bridgeTokenIn that must be /// provided function getBridgeTokenIn( + DestinationType destinationType, uint256 toChainId, - TokenAmount calldata stableOut, + BridgeTokenAmount calldata tokenOut, address bridgerAdapter ) external view returns (address bridgeTokenIn, uint256 inAmount); + /// @notice Returns whether a route should bridge to the deterministic + /// fulfillment address or directly to the final recipient bytes. + /// @param destinationType Destination chain type + /// @param toChainId Destination chain + /// @param tokenOut The stablecoin token and amount to receive on + /// the destination chain + /// @param bridgerAdapter The bridger adapter to use + function getBridgeRecipientMode( + DestinationType destinationType, + uint256 toChainId, + BridgeTokenAmount calldata tokenOut, + address bridgerAdapter + ) external view returns (BridgeRecipientMode); + /// @notice Execute the bridge. Reverts if the adapter can't deliver the /// specified destination amount. + /// @param destinationType Destination chain type /// @param toChainId Destination chain id - /// @param toAddress Recipient address on the destination chain - /// @param stableOut The stablecoin token and amount to receive on + /// @param toAddress Final recipient bytes on the destination chain + /// @param fulfillmentAddress Deterministic EVM fulfillment address bytes + /// @param tokenOut The stablecoin token and amount to receive on /// the destination chain /// @param bridgerAdapter The bridger adapter to use /// @param refundAddress Address to send funds to if the bridge fails /// @param extraData Adapter-specific calldata function sendToChain( + DestinationType destinationType, uint256 toChainId, - address toAddress, - TokenAmount calldata stableOut, + bytes calldata toAddress, + bytes calldata fulfillmentAddress, + BridgeTokenAmount calldata tokenOut, address bridgerAdapter, address refundAddress, bytes calldata extraData ) external; } + +/// @notice Bytes-aware adapter interface used by DepositAddressBridger for +/// non-EVM delivery routes. +interface IDepositAddressBridgeAdapter is IDepositAddressBridgeEvents { + function getBridgeTokenIn( + DestinationType destinationType, + uint256 toChainId, + BridgeTokenAmount calldata tokenOut + ) external view returns (address bridgeTokenIn, uint256 inAmount); + + function sendToChain( + DestinationType destinationType, + uint256 toChainId, + bytes calldata toAddress, + BridgeTokenAmount calldata tokenOut, + address refundAddress, + bytes calldata extraData + ) external; +} diff --git a/packages/contract/src/relayer/DaimoPayRelayer.sol b/packages/contract/src/relayer/DaimoPayRelayer.sol index 14b7a3c..46432ff 100644 --- a/packages/contract/src/relayer/DaimoPayRelayer.sol +++ b/packages/contract/src/relayer/DaimoPayRelayer.sol @@ -6,6 +6,7 @@ import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "../DaimoPay.sol"; +import {BridgeTokenAmount} from "../DestinationUtils.sol"; import "../TokenUtils.sol"; import {DepositAddressManager} from "../DepositAddressManager.sol"; import {DAParams} from "../DepositAddress.sol"; @@ -460,7 +461,7 @@ contract DaimoPayRelayer is AccessControl { DepositAddressManager manager, DAParams calldata params, IERC20 paymentToken, - TokenAmount calldata bridgeTokenOut, + BridgeTokenAmount calldata bridgeTokenOut, PriceData calldata paymentTokenPrice, PriceData calldata bridgeTokenInPrice, address bridgerAdapter, @@ -550,7 +551,7 @@ contract DaimoPayRelayer is AccessControl { TokenAmount calldata tokenIn, PriceData calldata bridgeTokenOutPrice, PriceData calldata toTokenPrice, - TokenAmount calldata bridgeTokenOut, + BridgeTokenAmount calldata bridgeTokenOut, bytes32 relaySalt, Call[] calldata calls, uint256 sourceChainId, @@ -609,7 +610,7 @@ contract DaimoPayRelayer is AccessControl { DepositAddressManager manager, DAParams calldata params, Call[] calldata calls, - TokenAmount calldata bridgeTokenOut, + BridgeTokenAmount calldata bridgeTokenOut, PriceData calldata bridgeTokenOutPrice, PriceData calldata toTokenPrice, bytes32 relaySalt, @@ -656,7 +657,7 @@ contract DaimoPayRelayer is AccessControl { TokenAmount calldata leg1BridgeTokenOut, uint256 leg1SourceChainId, PriceData calldata leg1BridgeTokenOutPrice, - TokenAmount calldata leg2BridgeTokenOut, + BridgeTokenAmount calldata leg2BridgeTokenOut, PriceData calldata leg2BridgeTokenInPrice, address bridgerAdapter, bytes32 relaySalt, diff --git a/packages/contract/test/DAZeroXBridger.t.sol b/packages/contract/test/DAZeroXBridger.t.sol index 6a10e74..b0bb533 100644 --- a/packages/contract/test/DAZeroXBridger.t.sol +++ b/packages/contract/test/DAZeroXBridger.t.sol @@ -5,6 +5,11 @@ import "forge-std/Test.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {DAZeroXBridger} from "../src/DAZeroXBridger.sol"; +import { + DestinationType, + BridgeTokenAmount, + DestinationUtils +} from "../src/DestinationUtils.sol"; import {IDaimoPayBridger} from "../src/interfaces/IDaimoPayBridger.sol"; import {TokenAmount} from "../src/TokenUtils.sol"; import {TestUSDC} from "./utils/DummyUSDC.sol"; @@ -89,9 +94,9 @@ contract DAZeroXBridgerTest is Test { chains[0] = DST_CHAIN; chains[1] = DST_CHAIN; - address[] memory tokenOuts = new address[](2); - tokenOuts[0] = bridgeTokenOutA; - tokenOuts[1] = bridgeTokenOutB; + address[] memory tokenOutAddrs = new address[](2); + tokenOutAddrs[0] = bridgeTokenOutA; + tokenOutAddrs[1] = bridgeTokenOutB; DAZeroXBridger.ZeroXRoute[] memory routes = new DAZeroXBridger.ZeroXRoute[](2); @@ -110,8 +115,9 @@ contract DAZeroXBridgerTest is Test { _owner: trustedSigner, _trustedSigner: trustedSigner, _maxQuoteAge: MAX_QUOTE_AGE, + _destinationTypes: _evmDestinationTypes(chains.length), _toChainIds: chains, - _bridgeTokenOuts: tokenOuts, + _bridgeTokenOuts: _evmTokenOuts(tokenOutAddrs), _bridgeRoutes: routes }); @@ -135,12 +141,34 @@ contract DAZeroXBridgerTest is Test { opts[0] = TokenAmount({token: IERC20(tokenOut), amount: amount}); } + function _evmBytes(address addr) internal pure returns (bytes memory) { + return DestinationUtils.evmAddressToBytes(addr); + } + + function _evmDestinationTypes( + uint256 n + ) internal pure returns (DestinationType[] memory destinationTypes) { + destinationTypes = new DestinationType[](n); + for (uint256 i = 0; i < n; ++i) { + destinationTypes[i] = DestinationType.EVM; + } + } + + function _evmTokenOuts( + address[] memory tokenOuts + ) internal pure returns (bytes[] memory ret) { + ret = new bytes[](tokenOuts.length); + for (uint256 i = 0; i < tokenOuts.length; ++i) { + ret[i] = _evmBytes(tokenOuts[i]); + } + } + function _baseQuote( address tokenOut, uint256 outAmount ) internal view returns (DAZeroXBridger.SignedQuote memory q) { q = DAZeroXBridger.SignedQuote({ - bridgeTokenOut: tokenOut, + bridgeTokenOut: _evmBytes(tokenOut), outAmount: outAmount, allowanceTarget: address(router), callTarget: address(router), @@ -150,9 +178,12 @@ contract DAZeroXBridgerTest is Test { ), callValue: 0, timestamp: block.timestamp, - quoteId: keccak256(abi.encode(tokenOut, outAmount, block.timestamp)), + quoteId: keccak256( + abi.encode(tokenOut, outAmount, block.timestamp) + ), + destinationType: DestinationType.EVM, toChainId: DST_CHAIN, - toAddress: toAddress, + toAddress: _evmBytes(toAddress), signature: "" }); } @@ -174,10 +205,11 @@ contract DAZeroXBridgerTest is Test { abi.encode( chainId, bridgerAddr, + q.destinationType, q.toChainId, - q.toAddress, + keccak256(q.toAddress), bridgeTokenIn, - q.bridgeTokenOut, + keccak256(q.bridgeTokenOut), q.outAmount, q.allowanceTarget, q.callTarget, @@ -199,7 +231,9 @@ contract DAZeroXBridgerTest is Test { ) internal view returns (DAZeroXBridger.SignedQuote memory) { q.signature = _sign( q, - _routeInToken(q.bridgeTokenOut), + _routeInToken( + DestinationUtils.decodeEvmAddressMemory(q.bridgeTokenOut) + ), TRUSTED_SIGNER_KEY, address(bridger), block.chainid @@ -230,8 +264,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, trustedSigner, MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -247,21 +282,19 @@ contract DAZeroXBridgerTest is Test { uint256 inAmount ) internal view returns (DAZeroXBridger.SignedQuote memory q) { q = DAZeroXBridger.SignedQuote({ - bridgeTokenOut: tokenOut, + bridgeTokenOut: _evmBytes(tokenOut), outAmount: outAmount, allowanceTarget: address(router), callTarget: address(router), - callData: abi.encodeCall( - MockZeroXRouter.swap, - (tokenIn, inAmount) - ), + callData: abi.encodeCall(MockZeroXRouter.swap, (tokenIn, inAmount)), callValue: 0, timestamp: block.timestamp, quoteId: keccak256( abi.encode(tokenOut, outAmount, inAmount, block.timestamp) ), + destinationType: DestinationType.EVM, toChainId: DST_CHAIN, - toAddress: toAddress, + toAddress: _evmBytes(toAddress), signature: "" }); q.signature = _sign( @@ -273,6 +306,82 @@ contract DAZeroXBridgerTest is Test { ); } + function _solanaBytes(uint256 seed) internal pure returns (bytes memory) { + return abi.encodePacked(bytes32(seed)); + } + + function _deployDirectBridger( + DestinationType destinationType, + uint256 toChainId, + bytes memory tokenOut + ) internal returns (DAZeroXBridger) { + DestinationType[] memory destinationTypes = new DestinationType[](1); + destinationTypes[0] = destinationType; + uint256[] memory chains = new uint256[](1); + chains[0] = toChainId; + bytes[] memory tokenOuts = new bytes[](1); + tokenOuts[0] = tokenOut; + DAZeroXBridger.ZeroXRoute[] + memory routes = new DAZeroXBridger.ZeroXRoute[](1); + routes[0] = DAZeroXBridger.ZeroXRoute({ + bridgeTokenIn: address(usdcIn), + bridgeTokenInDecimals: 6, + bridgeTokenOutDecimals: 6 + }); + return + new DAZeroXBridger( + trustedSigner, + trustedSigner, + MAX_QUOTE_AGE, + destinationTypes, + chains, + tokenOuts, + routes + ); + } + + function _baseDirectQuote( + DestinationType destinationType, + uint256 toChainId, + bytes memory tokenOut, + bytes memory recipient, + uint256 amount + ) internal view returns (DAZeroXBridger.SignedQuote memory q) { + q = DAZeroXBridger.SignedQuote({ + bridgeTokenOut: tokenOut, + outAmount: amount, + allowanceTarget: address(router), + callTarget: address(router), + callData: abi.encodeCall( + MockZeroXRouter.swap, + (address(usdcIn), amount) + ), + callValue: 0, + timestamp: block.timestamp, + quoteId: keccak256( + abi.encode(destinationType, toChainId, tokenOut, amount) + ), + destinationType: destinationType, + toChainId: toChainId, + toAddress: recipient, + signature: "" + }); + } + + function _signDirectQuote( + DAZeroXBridger b, + DAZeroXBridger.SignedQuote memory q + ) internal view returns (DAZeroXBridger.SignedQuote memory) { + q.signature = _sign( + q, + address(usdcIn), + TRUSTED_SIGNER_KEY, + address(b), + block.chainid + ); + return q; + } + // --------------------------------------------------------------------- // Constructor // --------------------------------------------------------------------- @@ -286,8 +395,9 @@ contract DAZeroXBridgerTest is Test { address(0), trustedSigner, MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -302,8 +412,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, address(0), MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -318,8 +429,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, trustedSigner, 0, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -347,8 +459,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, trustedSigner, MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -370,8 +483,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, trustedSigner, MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -393,8 +507,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, trustedSigner, MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -416,8 +531,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, trustedSigner, MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -439,8 +555,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, trustedSigner, MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); } @@ -470,14 +587,8 @@ contract DAZeroXBridgerTest is Test { function testGetBridgeTokenIn_MultipleOptions_Reverts() public { TokenAmount[] memory opts = new TokenAmount[](2); - opts[0] = TokenAmount({ - token: IERC20(bridgeTokenOutA), - amount: 100 - }); - opts[1] = TokenAmount({ - token: IERC20(bridgeTokenOutB), - amount: 100 - }); + opts[0] = TokenAmount({token: IERC20(bridgeTokenOutA), amount: 100}); + opts[1] = TokenAmount({token: IERC20(bridgeTokenOutB), amount: 100}); vm.expectRevert(bytes("DAZX: multiple options")); bridger.getBridgeTokenIn(DST_CHAIN, opts); } @@ -746,14 +857,8 @@ contract DAZeroXBridgerTest is Test { function testSendToChain_MultipleOptions_Reverts() public { TokenAmount[] memory opts = new TokenAmount[](2); - opts[0] = TokenAmount({ - token: IERC20(bridgeTokenOutA), - amount: 100 - }); - opts[1] = TokenAmount({ - token: IERC20(bridgeTokenOutB), - amount: 100 - }); + opts[0] = TokenAmount({token: IERC20(bridgeTokenOutA), amount: 100}); + opts[1] = TokenAmount({token: IERC20(bridgeTokenOutB), amount: 100}); DAZeroXBridger.SignedQuote memory q = _signQuote( _baseQuote(bridgeTokenOutA, 100) ); @@ -913,7 +1018,7 @@ contract DAZeroXBridgerTest is Test { bridgeTokenOutA, amount ); - q.toAddress = address(0xBAD); + q.toAddress = _evmBytes(address(0xBAD)); q = _signQuote(q); vm.prank(multiplexer); @@ -1008,6 +1113,215 @@ contract DAZeroXBridgerTest is Test { ); } + // --------------------------------------------------------------------- + // direct bytes-aware sendToChain + // --------------------------------------------------------------------- + + function testDirectSendToChain_SolanaHappyPath() public { + uint256 solanaChainId = 501; + bytes memory tokenOutBytes = _solanaBytes(1); + bytes memory recipient = _solanaBytes(2); + uint256 amount = 1_000_000; + DAZeroXBridger b = _deployDirectBridger( + DestinationType.SOLANA, + solanaChainId, + tokenOutBytes + ); + vm.prank(multiplexer); + usdcIn.approve(address(b), type(uint256).max); + + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: tokenOutBytes, + amount: amount + }); + DAZeroXBridger.SignedQuote memory q = _signDirectQuote( + b, + _baseDirectQuote( + DestinationType.SOLANA, + solanaChainId, + tokenOutBytes, + recipient, + amount + ) + ); + + vm.prank(multiplexer); + b.sendToChain( + DestinationType.SOLANA, + solanaChainId, + recipient, + tokenOut, + refundAddress, + abi.encode(q) + ); + + assertEq(router.lastPulled(), amount); + assertEq(router.lastInToken(), address(usdcIn)); + assertEq(usdcIn.allowance(address(b), address(router)), 0); + assertEq(usdcIn.balanceOf(address(b)), 0); + } + + function testDirectSendToChain_WrongRecipientBytes_Reverts() public { + uint256 solanaChainId = 501; + bytes memory tokenOutBytes = _solanaBytes(1); + bytes memory recipient = _solanaBytes(2); + uint256 amount = 100; + DAZeroXBridger b = _deployDirectBridger( + DestinationType.SOLANA, + solanaChainId, + tokenOutBytes + ); + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: tokenOutBytes, + amount: amount + }); + DAZeroXBridger.SignedQuote memory q = _signDirectQuote( + b, + _baseDirectQuote( + DestinationType.SOLANA, + solanaChainId, + tokenOutBytes, + _solanaBytes(3), + amount + ) + ); + + vm.prank(multiplexer); + vm.expectRevert(bytes("DAZX: to address mismatch")); + b.sendToChain( + DestinationType.SOLANA, + solanaChainId, + recipient, + tokenOut, + refundAddress, + abi.encode(q) + ); + } + + function testDirectSendToChain_WrongTokenBytes_Reverts() public { + uint256 solanaChainId = 501; + bytes memory tokenOutBytes = _solanaBytes(1); + bytes memory recipient = _solanaBytes(2); + uint256 amount = 100; + DAZeroXBridger b = _deployDirectBridger( + DestinationType.SOLANA, + solanaChainId, + tokenOutBytes + ); + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: tokenOutBytes, + amount: amount + }); + DAZeroXBridger.SignedQuote memory q = _signDirectQuote( + b, + _baseDirectQuote( + DestinationType.SOLANA, + solanaChainId, + _solanaBytes(4), + recipient, + amount + ) + ); + + vm.prank(multiplexer); + vm.expectRevert(bytes("DAZX: bridge token mismatch")); + b.sendToChain( + DestinationType.SOLANA, + solanaChainId, + recipient, + tokenOut, + refundAddress, + abi.encode(q) + ); + } + + function testDirectSendToChain_WrongDestinationType_Reverts() public { + uint256 solanaChainId = 501; + bytes memory tokenOutBytes = _solanaBytes(1); + bytes memory recipient = _solanaBytes(2); + uint256 amount = 100; + DAZeroXBridger b = _deployDirectBridger( + DestinationType.SOLANA, + solanaChainId, + tokenOutBytes + ); + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: tokenOutBytes, + amount: amount + }); + DAZeroXBridger.SignedQuote memory q = _signDirectQuote( + b, + _baseDirectQuote( + DestinationType.TRON, + solanaChainId, + tokenOutBytes, + recipient, + amount + ) + ); + + vm.prank(multiplexer); + vm.expectRevert(bytes("DAZX: dest type mismatch")); + b.sendToChain( + DestinationType.SOLANA, + solanaChainId, + recipient, + tokenOut, + refundAddress, + abi.encode(q) + ); + } + + function testDirectSendToChain_QuoteReplay_Reverts() public { + uint256 solanaChainId = 501; + bytes memory tokenOutBytes = _solanaBytes(1); + bytes memory recipient = _solanaBytes(2); + uint256 amount = 100; + DAZeroXBridger b = _deployDirectBridger( + DestinationType.SOLANA, + solanaChainId, + tokenOutBytes + ); + vm.prank(multiplexer); + usdcIn.approve(address(b), type(uint256).max); + + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: tokenOutBytes, + amount: amount + }); + DAZeroXBridger.SignedQuote memory q = _signDirectQuote( + b, + _baseDirectQuote( + DestinationType.SOLANA, + solanaChainId, + tokenOutBytes, + recipient, + amount + ) + ); + + vm.prank(multiplexer); + b.sendToChain( + DestinationType.SOLANA, + solanaChainId, + recipient, + tokenOut, + refundAddress, + abi.encode(q) + ); + + vm.prank(multiplexer); + vm.expectRevert(bytes("DAZX: quote replayed")); + b.sendToChain( + DestinationType.SOLANA, + solanaChainId, + recipient, + tokenOut, + refundAddress, + abi.encode(q) + ); + } + // --------------------------------------------------------------------- // sendToChain - cross-decimal routes // --------------------------------------------------------------------- @@ -1163,8 +1477,9 @@ contract DAZeroXBridgerTest is Test { trustedSigner, trustedSigner, MAX_QUOTE_AGE, + _evmDestinationTypes(chains.length), chains, - tokenOuts, + _evmTokenOuts(tokenOuts), routes ); diff --git a/packages/contract/test/DaimoPayHopBridger.t.sol b/packages/contract/test/DaimoPayHopBridger.t.sol index 225d10e..c9ce442 100644 --- a/packages/contract/test/DaimoPayHopBridger.t.sol +++ b/packages/contract/test/DaimoPayHopBridger.t.sol @@ -3,14 +3,67 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import { + SafeERC20 +} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import {DaimoPayHopBridger} from "../src/DaimoPayHopBridger.sol"; import {IDaimoPayBridger} from "../src/interfaces/IDaimoPayBridger.sol"; +import { + IDepositAddressBridgeEvents +} from "../src/interfaces/IDepositAddressBridger.sol"; import {TokenAmount} from "../src/TokenUtils.sol"; -import {DummyDepositAddressBridger} from "./utils/DummyDepositBridger.sol"; +import { + BridgeTokenAmount, + DestinationType, + DestinationUtils +} from "../src/DestinationUtils.sol"; import {TestUSDC} from "./utils/DummyUSDC.sol"; import {TestToken2Decimals} from "./utils/Dummy2DecimalsToken.sol"; +contract DummyLegacyHopBridger is IDaimoPayBridger { + using SafeERC20 for IERC20; + + uint256 public lastToChainId; + address public lastToAddress; + + function getBridgeTokenIn( + uint256, + TokenAmount[] calldata bridgeTokenOutOptions + ) external pure returns (address bridgeTokenIn, uint256 inAmount) { + require(bridgeTokenOutOptions.length == 1, "DLHB: one option"); + bridgeTokenIn = address(bridgeTokenOutOptions[0].token); + inAmount = bridgeTokenOutOptions[0].amount; + } + + function sendToChain( + uint256 toChainId, + address toAddress, + TokenAmount[] calldata bridgeTokenOutOptions, + address refundAddress, + bytes calldata + ) external { + require(bridgeTokenOutOptions.length == 1, "DLHB: one option"); + IERC20 bridgeTokenIn = bridgeTokenOutOptions[0].token; + uint256 inAmount = bridgeTokenOutOptions[0].amount; + bridgeTokenIn.safeTransferFrom(msg.sender, address(0xdead), inAmount); + + lastToChainId = toChainId; + lastToAddress = toAddress; + + emit BridgeInitiated({ + fromAddress: msg.sender, + fromToken: address(bridgeTokenIn), + fromAmount: inAmount, + toChainId: toChainId, + toAddress: toAddress, + toToken: address(bridgeTokenIn), + toAmount: inAmount, + refundAddress: refundAddress + }); + } +} + contract DaimoPayHopBridgerHarness is DaimoPayHopBridger { constructor( uint256 hopChainId, @@ -32,29 +85,33 @@ contract DaimoPayHopBridgerHarness is DaimoPayHopBridger { uint256 toChainId, TokenAmount[] calldata tokenOpts ) external view returns (TokenAmount[] memory) { - return _getHopAsset(toChainId, tokenOpts); + return _getHopAssetFromEvmOptions(toChainId, tokenOpts); } } contract DaimoPayHopBridgerTest is Test { - DummyDepositAddressBridger private hop; + DummyLegacyHopBridger private hop; DaimoPayHopBridgerHarness private hb; TestUSDC private usdc6; // acts as final coin TestToken2Decimals private usdc2; // acts as hop coin (2 decimals) uint256 constant HOP_CHAIN = 42161; uint256 constant DST_CHAIN = 10; + uint256 constant SOLANA_CHAIN = 501; + bytes32 constant SOLANA_USDC = + 0xc6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61; function setUp() public { - hop = new DummyDepositAddressBridger(); + hop = new DummyLegacyHopBridger(); usdc6 = new TestUSDC(); usdc2 = new TestToken2Decimals(); DaimoPayHopBridger.FinalChainCoin[] memory coins = new DaimoPayHopBridger.FinalChainCoin[](1); coins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: DST_CHAIN, - coinAddr: address(usdc6), + coin: DestinationUtils.evmAddressToBytes(address(usdc6)), coinDecimals: 6 }); @@ -92,8 +149,9 @@ contract DaimoPayHopBridgerTest is Test { DaimoPayHopBridger.FinalChainCoin[] memory coins = new DaimoPayHopBridger.FinalChainCoin[](1); coins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, finalChainId: DST_CHAIN, - coinAddr: address(usdc2), + coin: DestinationUtils.evmAddressToBytes(address(usdc2)), coinDecimals: 2 }); @@ -114,4 +172,174 @@ contract DaimoPayHopBridgerTest is Test { assertEq(address(hopOpts[0].token), address(usdc6)); assertEq(hopOpts[0].amount, 100_000); } + + function test_getBridgeTokenIn_solanaFinalCoin() public { + DaimoPayHopBridgerHarness solanaHb = _newSolanaHopBridger(); + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: abi.encodePacked(SOLANA_USDC), + amount: 100_000 + }); + + (address bridgeTokenIn, uint256 inAmount) = solanaHb.getBridgeTokenIn({ + destinationType: DestinationType.SOLANA, + toChainId: SOLANA_CHAIN, + tokenOut: tokenOut + }); + + assertEq(bridgeTokenIn, address(usdc2)); + assertEq(inAmount, 10); + } + + function test_finalChainCoins_KeyedByDestinationType() public { + DaimoPayHopBridger.FinalChainCoin[] + memory coins = new DaimoPayHopBridger.FinalChainCoin[](2); + coins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.EVM, + finalChainId: SOLANA_CHAIN, + coin: DestinationUtils.evmAddressToBytes(address(usdc6)), + coinDecimals: 6 + }); + coins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.SOLANA, + finalChainId: SOLANA_CHAIN, + coin: abi.encodePacked(SOLANA_USDC), + coinDecimals: 6 + }); + + DaimoPayHopBridgerHarness keyedHb = new DaimoPayHopBridgerHarness({ + hopChainId: HOP_CHAIN, + hopCoinAddr: address(usdc2), + hopCoinDecimals: 2, + hopBridger: IDaimoPayBridger(address(hop)), + finalChainCoins: coins + }); + + TokenAmount[] memory evmOpts = new TokenAmount[](1); + evmOpts[0] = TokenAmount({ + token: IERC20(address(usdc6)), + amount: 100_000 + }); + TokenAmount[] memory evmHopOpts = keyedHb.expose_getHopAsset({ + toChainId: SOLANA_CHAIN, + tokenOpts: evmOpts + }); + assertEq(address(evmHopOpts[0].token), address(usdc2)); + assertEq(evmHopOpts[0].amount, 10); + + BridgeTokenAmount memory solanaTokenOut = BridgeTokenAmount({ + token: abi.encodePacked(SOLANA_USDC), + amount: 100_000 + }); + (address bridgeTokenIn, uint256 inAmount) = keyedHb.getBridgeTokenIn({ + destinationType: DestinationType.SOLANA, + toChainId: SOLANA_CHAIN, + tokenOut: solanaTokenOut + }); + assertEq(bridgeTokenIn, address(usdc2)); + assertEq(inAmount, 10); + } + + function testConstructor_DuplicateFinalCoinKey_Reverts() public { + DaimoPayHopBridger.FinalChainCoin[] + memory coins = new DaimoPayHopBridger.FinalChainCoin[](2); + coins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.SOLANA, + finalChainId: SOLANA_CHAIN, + coin: abi.encodePacked(SOLANA_USDC), + coinDecimals: 6 + }); + coins[1] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.SOLANA, + finalChainId: SOLANA_CHAIN, + coin: abi.encodePacked(bytes32(uint256(SOLANA_USDC) + 1)), + coinDecimals: 6 + }); + + vm.expectRevert("DPHB: duplicate final coin"); + new DaimoPayHopBridgerHarness({ + hopChainId: HOP_CHAIN, + hopCoinAddr: address(usdc2), + hopCoinDecimals: 2, + hopBridger: IDaimoPayBridger(address(hop)), + finalChainCoins: coins + }); + } + + function test_sendToChain_solanaFinalCoinEmitsHopLegBytesEvent() public { + DaimoPayHopBridgerHarness solanaHb = _newSolanaHopBridger(); + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: abi.encodePacked(SOLANA_USDC), + amount: 100_000 + }); + address fulfillment = address(0xBEEF); + + usdc2.approve(address(solanaHb), 10); + + vm.expectEmit(true, true, true, true, address(solanaHb)); + emit IDepositAddressBridgeEvents.BridgeInitiatedBytes({ + fromAddress: address(this), + fromToken: address(usdc2), + fromAmount: 10, + destinationType: DestinationType.EVM, + toChainId: HOP_CHAIN, + toAddress: DestinationUtils.evmAddressToBytes(fulfillment), + toToken: DestinationUtils.evmAddressToBytes(address(usdc2)), + toAmount: 10, + refundAddress: address(this) + }); + + solanaHb.sendToChain({ + destinationType: DestinationType.SOLANA, + toChainId: SOLANA_CHAIN, + toAddress: DestinationUtils.evmAddressToBytes(fulfillment), + tokenOut: tokenOut, + refundAddress: address(this), + extraData: bytes("") + }); + } + + function test_sendToChain_solanaFinalCoinSendsToHopFulfillment() public { + DaimoPayHopBridgerHarness solanaHb = _newSolanaHopBridger(); + BridgeTokenAmount memory tokenOut = BridgeTokenAmount({ + token: abi.encodePacked(SOLANA_USDC), + amount: 100_000 + }); + address fulfillment = address(0xBEEF); + + usdc2.approve(address(solanaHb), 10); + solanaHb.sendToChain({ + destinationType: DestinationType.SOLANA, + toChainId: SOLANA_CHAIN, + toAddress: DestinationUtils.evmAddressToBytes(fulfillment), + tokenOut: tokenOut, + refundAddress: address(this), + extraData: bytes("") + }); + + assertEq(hop.lastToChainId(), HOP_CHAIN); + assertEq(hop.lastToAddress(), fulfillment); + } + + function _newSolanaHopBridger() + private + returns (DaimoPayHopBridgerHarness) + { + DaimoPayHopBridger.FinalChainCoin[] + memory coins = new DaimoPayHopBridger.FinalChainCoin[](1); + coins[0] = DaimoPayHopBridger.FinalChainCoin({ + destinationType: DestinationType.SOLANA, + finalChainId: SOLANA_CHAIN, + coin: abi.encodePacked(SOLANA_USDC), + coinDecimals: 6 + }); + + return + new DaimoPayHopBridgerHarness({ + hopChainId: HOP_CHAIN, + hopCoinAddr: address(usdc2), + hopCoinDecimals: 2, + hopBridger: IDaimoPayBridger(address(hop)), + finalChainCoins: coins + }); + } } diff --git a/packages/contract/test/DepositAddressManager.t.sol b/packages/contract/test/DepositAddressManager.t.sol index 66ac41c..a046938 100644 --- a/packages/contract/test/DepositAddressManager.t.sol +++ b/packages/contract/test/DepositAddressManager.t.sol @@ -17,8 +17,14 @@ import { import {DaimoPayPricer} from "../src/DaimoPayPricer.sol"; import {PriceData} from "../src/interfaces/IDaimoPayPricer.sol"; import { - IDepositAddressBridger + IDepositAddressBridger, + BridgeRecipientMode } from "../src/interfaces/IDepositAddressBridger.sol"; +import { + DestinationType, + BridgeTokenAmount, + DestinationUtils +} from "../src/DestinationUtils.sol"; import {TokenAmount} from "../src/TokenUtils.sol"; import {DaimoPayExecutor, Call} from "../src/DaimoPayExecutor.sol"; import {TestUSDC} from "./utils/DummyUSDC.sol"; @@ -104,9 +110,10 @@ contract DepositAddressManagerTest is Test { function _createDAParams() internal view returns (DAParams memory) { return DAParams({ + destinationType: DestinationType.EVM, toChainId: DEST_CHAIN_ID, - toToken: usdc, - toAddress: RECIPIENT, + toToken: _addressBytes(address(usdc)), + toAddress: _addressBytes(RECIPIENT), refundAddress: REFUND_ADDRESS, finalCallData: "", escrow: address(manager), @@ -119,6 +126,49 @@ contract DepositAddressManagerTest is Test { }); } + function _addressBytes(address addr) internal pure returns (bytes memory) { + return DestinationUtils.evmAddressToBytes(addr); + } + + function _bridgeTokenOut( + IERC20 token, + uint256 amount + ) internal pure returns (BridgeTokenAmount memory) { + return + BridgeTokenAmount({ + token: _addressBytes(address(token)), + amount: amount + }); + } + + function _nonEvmBridgeTokenOut( + bytes memory token, + uint256 amount + ) internal pure returns (BridgeTokenAmount memory) { + return BridgeTokenAmount({token: token, amount: amount}); + } + + function _solanaBytes(uint256 seed) internal pure returns (bytes memory) { + return abi.encodePacked(bytes32(seed)); + } + + function _tronBytes(uint160 addr) internal pure returns (bytes memory) { + return abi.encodePacked(bytes1(0x41), bytes20(addr)); + } + + function _createNonEvmDAParams( + DestinationType destinationType, + uint256 toChainId, + bytes memory toToken, + bytes memory toAddress + ) internal view returns (DAParams memory params) { + params = _createDAParams(); + params.destinationType = destinationType; + params.toChainId = toChainId; + params.toToken = toToken; + params.toAddress = toAddress; + } + /// @dev Creates price data and signs it with the trusted signer function _createSignedPriceData( address token, @@ -193,10 +243,10 @@ contract DepositAddressManagerTest is Test { ); // Create bridge token out - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -229,7 +279,9 @@ contract DepositAddressManagerTest is Test { (address fulfillmentAddress, ) = manager.computeFulfillmentAddress( fulfillment ); - assertTrue(manager.fulfillmentUsed(fulfillmentAddress)); + assertTrue( + manager.fulfillmentUsed(manager.computeFulfillmentId(fulfillment)) + ); // Verify bridger burned the tokens assertTrue(usdc.balanceOf(address(0xdead)) == BRIDGE_AMOUNT); @@ -252,10 +304,10 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); @@ -271,14 +323,18 @@ contract DepositAddressManagerTest is Test { (address fulfillmentAddress, ) = manager.computeFulfillmentAddress( fulfillment ); + bytes32 fulfillmentId = manager.computeFulfillmentId(fulfillment); + bytes memory bridgeRecipient = _addressBytes(fulfillmentAddress); // Expect Start event - vm.expectEmit(true, true, false, true); + vm.expectEmit(true, true, true, true); emit DepositAddressManager.Start({ depositAddress: address(vault), + fulfillmentId: fulfillmentId, fulfillmentAddress: fulfillmentAddress, params: params, fulfillment: fulfillment, + bridgeRecipient: bridgeRecipient, paymentToken: address(usdc), paymentAmount: PAYMENT_AMOUNT, paymentTokenPriceUsd: USDC_PRICE, @@ -315,10 +371,10 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; @@ -356,10 +412,340 @@ contract DepositAddressManagerTest is Test { (address fulfillmentAddress, ) = manager.computeFulfillmentAddress( fulfillment ); - assertTrue(manager.fulfillmentUsed(fulfillmentAddress)); + assertTrue( + manager.fulfillmentUsed( + manager.computeFulfillmentId(fulfillment) + ) + ); } } + function test_depositAddress_DestinationTypesProduceDistinctAddresses() + public + view + { + DAParams memory evmParams = _createDAParams(); + DAParams memory solanaParams = _createNonEvmDAParams({ + destinationType: DestinationType.SOLANA, + toChainId: 501, + toToken: _solanaBytes(1), + toAddress: _solanaBytes(2) + }); + DAParams memory tronParams = _createNonEvmDAParams({ + destinationType: DestinationType.TRON, + toChainId: 728126428, + toToken: _tronBytes(0x1111), + toAddress: _tronBytes(0x2222) + }); + + address evmDepositAddress = factory.getDepositAddress(evmParams); + address solanaDepositAddress = factory.getDepositAddress(solanaParams); + address tronDepositAddress = factory.getDepositAddress(tronParams); + + assertTrue(evmDepositAddress != solanaDepositAddress); + assertTrue(evmDepositAddress != tronDepositAddress); + assertTrue(solanaDepositAddress != tronDepositAddress); + } + + function test_start_SolanaDirectDelivery() public { + DAParams memory params = _createNonEvmDAParams({ + destinationType: DestinationType.SOLANA, + toChainId: 501, + toToken: _solanaBytes(1), + toAddress: _solanaBytes(2) + }); + DepositAddress vault = factory.createDepositAddress(params); + _fundDepositAddress(vault, PAYMENT_AMOUNT); + bridger.setBridgeTokenInOverride(address(usdc)); + + BridgeTokenAmount memory bridgeTokenOut = _nonEvmBridgeTokenOut( + params.toToken, + BRIDGE_AMOUNT + ); + PriceData memory paymentTokenPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + PriceData memory bridgeTokenInPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + bytes32 relaySalt = keccak256("solana-salt"); + Call[] memory calls = new Call[](0); + + vm.prank(RELAYER); + manager.start({ + params: params, + paymentToken: usdc, + bridgeTokenOut: bridgeTokenOut, + paymentTokenPrice: paymentTokenPrice, + bridgeTokenInPrice: bridgeTokenInPrice, + bridgerAdapter: address(bridger), + relaySalt: relaySalt, + calls: calls, + bridgeExtraData: "" + }); + + DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ + depositAddress: address(vault), + relaySalt: relaySalt, + bridgeTokenOut: bridgeTokenOut, + sourceChainId: SOURCE_CHAIN_ID + }); + assertTrue( + manager.fulfillmentUsed(manager.computeFulfillmentId(fulfillment)) + ); + assertEq( + uint256(bridger.lastDestinationType()), + uint256(DestinationType.SOLANA) + ); + assertEq(bridger.lastToChainId(), params.toChainId); + assertEq( + keccak256(bridger.lastToAddress()), + keccak256(params.toAddress) + ); + assertEq(keccak256(bridger.lastTokenOut()), keccak256(params.toToken)); + assertEq(bridger.lastAmount(), BRIDGE_AMOUNT); + assertEq(usdc.balanceOf(address(0xdead)), BRIDGE_AMOUNT); + } + + function test_start_SolanaHopDeliveryUsesFulfillmentRecipient() public { + DAParams memory params = _createNonEvmDAParams({ + destinationType: DestinationType.SOLANA, + toChainId: 501, + toToken: _solanaBytes(1), + toAddress: _solanaBytes(2) + }); + DepositAddress vault = factory.createDepositAddress(params); + _fundDepositAddress(vault, PAYMENT_AMOUNT); + bridger.setBridgeTokenInOverride(address(usdc)); + bridger.setBridgeRecipientMode(BridgeRecipientMode.FULFILLMENT); + + BridgeTokenAmount memory bridgeTokenOut = _nonEvmBridgeTokenOut( + params.toToken, + BRIDGE_AMOUNT + ); + PriceData memory paymentTokenPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + PriceData memory bridgeTokenInPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + bytes32 relaySalt = keccak256("solana-hop-salt"); + Call[] memory calls = new Call[](0); + + vm.prank(RELAYER); + manager.start({ + params: params, + paymentToken: usdc, + bridgeTokenOut: bridgeTokenOut, + paymentTokenPrice: paymentTokenPrice, + bridgeTokenInPrice: bridgeTokenInPrice, + bridgerAdapter: address(bridger), + relaySalt: relaySalt, + calls: calls, + bridgeExtraData: "" + }); + + DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ + depositAddress: address(vault), + relaySalt: relaySalt, + bridgeTokenOut: bridgeTokenOut, + sourceChainId: SOURCE_CHAIN_ID + }); + (address fulfillmentAddress, ) = manager.computeFulfillmentAddress( + fulfillment + ); + + assertEq( + keccak256(bridger.lastToAddress()), + keccak256(DestinationUtils.evmAddressToBytes(fulfillmentAddress)) + ); + } + + function test_start_TronDirectDelivery() public { + DAParams memory params = _createNonEvmDAParams({ + destinationType: DestinationType.TRON, + toChainId: 728126428, + toToken: _tronBytes(0x1111), + toAddress: _tronBytes(0x2222) + }); + DepositAddress vault = factory.createDepositAddress(params); + _fundDepositAddress(vault, PAYMENT_AMOUNT); + bridger.setBridgeTokenInOverride(address(usdc)); + + BridgeTokenAmount memory bridgeTokenOut = _nonEvmBridgeTokenOut( + params.toToken, + BRIDGE_AMOUNT + ); + PriceData memory paymentTokenPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + PriceData memory bridgeTokenInPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + bytes32 relaySalt = keccak256("tron-salt"); + Call[] memory calls = new Call[](0); + + vm.prank(RELAYER); + manager.start({ + params: params, + paymentToken: usdc, + bridgeTokenOut: bridgeTokenOut, + paymentTokenPrice: paymentTokenPrice, + bridgeTokenInPrice: bridgeTokenInPrice, + bridgerAdapter: address(bridger), + relaySalt: relaySalt, + calls: calls, + bridgeExtraData: "" + }); + + DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ + depositAddress: address(vault), + relaySalt: relaySalt, + bridgeTokenOut: bridgeTokenOut, + sourceChainId: SOURCE_CHAIN_ID + }); + assertTrue( + manager.fulfillmentUsed(manager.computeFulfillmentId(fulfillment)) + ); + assertEq( + uint256(bridger.lastDestinationType()), + uint256(DestinationType.TRON) + ); + assertEq( + keccak256(bridger.lastToAddress()), + keccak256(params.toAddress) + ); + assertEq(keccak256(bridger.lastTokenOut()), keccak256(params.toToken)); + assertEq(bridger.lastAmount(), BRIDGE_AMOUNT); + } + + function test_start_RevertsNonEvmFinalCallData() public { + DAParams memory params = _createNonEvmDAParams({ + destinationType: DestinationType.SOLANA, + toChainId: 501, + toToken: _solanaBytes(1), + toAddress: _solanaBytes(2) + }); + params.finalCallData = hex"01"; + BridgeTokenAmount memory bridgeTokenOut = _nonEvmBridgeTokenOut( + params.toToken, + BRIDGE_AMOUNT + ); + PriceData memory paymentTokenPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + PriceData memory bridgeTokenInPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + Call[] memory calls = new Call[](0); + + vm.expectRevert(DepositAddressManager.FinalCallUnsupported.selector); + vm.prank(RELAYER); + manager.start({ + params: params, + paymentToken: usdc, + bridgeTokenOut: bridgeTokenOut, + paymentTokenPrice: paymentTokenPrice, + bridgeTokenInPrice: bridgeTokenInPrice, + bridgerAdapter: address(bridger), + relaySalt: keccak256("bad-final-call"), + calls: calls, + bridgeExtraData: "" + }); + } + + function test_start_RevertsNonEvmBridgeTokenMismatch() public { + DAParams memory params = _createNonEvmDAParams({ + destinationType: DestinationType.SOLANA, + toChainId: 501, + toToken: _solanaBytes(1), + toAddress: _solanaBytes(2) + }); + BridgeTokenAmount memory bridgeTokenOut = _nonEvmBridgeTokenOut( + _solanaBytes(3), + BRIDGE_AMOUNT + ); + PriceData memory paymentTokenPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + PriceData memory bridgeTokenInPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + Call[] memory calls = new Call[](0); + + vm.expectRevert(DepositAddressManager.DirectTokenMismatch.selector); + vm.prank(RELAYER); + manager.start({ + params: params, + paymentToken: usdc, + bridgeTokenOut: bridgeTokenOut, + paymentTokenPrice: paymentTokenPrice, + bridgeTokenInPrice: bridgeTokenInPrice, + bridgerAdapter: address(bridger), + relaySalt: keccak256("bad-token"), + calls: calls, + bridgeExtraData: "" + }); + } + + function test_start_RevertsBadDestinationBytes() public { + DAParams memory params = _createNonEvmDAParams({ + destinationType: DestinationType.TRON, + toChainId: 728126428, + toToken: abi.encodePacked(bytes1(0x42), bytes20(uint160(0x1111))), + toAddress: _tronBytes(0x2222) + }); + BridgeTokenAmount memory bridgeTokenOut = _nonEvmBridgeTokenOut( + params.toToken, + BRIDGE_AMOUNT + ); + PriceData memory paymentTokenPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + PriceData memory bridgeTokenInPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + Call[] memory calls = new Call[](0); + + vm.expectRevert(DepositAddressManager.BadDestinationToken.selector); + vm.prank(RELAYER); + manager.start({ + params: params, + paymentToken: usdc, + bridgeTokenOut: bridgeTokenOut, + paymentTokenPrice: paymentTokenPrice, + bridgeTokenInPrice: bridgeTokenInPrice, + bridgerAdapter: address(bridger), + relaySalt: keccak256("bad-tron"), + calls: calls, + bridgeExtraData: "" + }); + } + // --------------------------------------------------------------------- // start - Validation failures // --------------------------------------------------------------------- @@ -383,16 +769,16 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; - vm.expectRevert(bytes("DAM: start on dest chain")); + vm.expectRevert(DepositAddressManager.StartOnDestChain.selector); vm.prank(RELAYER); manager.start({ params: params, @@ -425,16 +811,16 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; - vm.expectRevert(bytes("DAM: wrong escrow")); + vm.expectRevert(DepositAddressManager.WrongEscrow.selector); vm.prank(RELAYER); manager.start({ params: params, @@ -471,10 +857,10 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); @@ -482,7 +868,7 @@ contract DepositAddressManagerTest is Test { // Expect revert vm.prank(RELAYER); - vm.expectRevert("DAM: expired"); + vm.expectRevert(DepositAddressManager.Expired.selector); manager.start({ params: params, paymentToken: usdc, @@ -516,16 +902,16 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; - vm.expectRevert(bytes("DAM: payment price invalid")); + vm.expectRevert(DepositAddressManager.PaymentPriceInvalid.selector); vm.prank(RELAYER); manager.start({ params: params, @@ -563,16 +949,16 @@ contract DepositAddressManagerTest is Test { 0xBAD ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; - vm.expectRevert(bytes("DAM: bridge price invalid")); + vm.expectRevert(DepositAddressManager.BridgePriceInvalid.selector); vm.prank(RELAYER); manager.start({ params: params, @@ -603,10 +989,10 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); @@ -630,7 +1016,7 @@ contract DepositAddressManagerTest is Test { _fundDepositAddress(vault, PAYMENT_AMOUNT); // Second call with same salt should revert - vm.expectRevert(bytes("DAM: fulfillment used")); + vm.expectRevert(DepositAddressManager.FulfillmentUsed.selector); vm.prank(RELAYER); manager.start({ params: params, @@ -664,16 +1050,16 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; - vm.expectRevert(bytes("DAM: bridge token mismatch")); + vm.expectRevert(DepositAddressManager.BridgeTokenMismatch.selector); vm.prank(RELAYER); manager.start({ params: params, @@ -707,16 +1093,16 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; - vm.expectRevert(bytes("DAM: payment token mismatch")); + vm.expectRevert(DepositAddressManager.PaymentTokenMismatch.selector); vm.prank(RELAYER); manager.start({ params: params, @@ -748,16 +1134,13 @@ contract DepositAddressManagerTest is Test { ); // Bridge amount too low - less than minimum after slippage - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: 50e6 // Much less than expected - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut(usdc, 50e6); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; - vm.expectRevert(bytes("DAM: bridge input low")); + vm.expectRevert(DepositAddressManager.BridgeInputLow.selector); vm.prank(RELAYER); manager.start({ params: params, @@ -788,16 +1171,16 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); bytes memory bridgeExtraData = ""; - vm.expectRevert(bytes("DAM: not relayer")); + vm.expectRevert(DepositAddressManager.NotRelayer.selector); vm.prank(address(0x1111)); manager.start({ params: params, @@ -820,10 +1203,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); DepositAddress vault = factory.createDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -852,10 +1235,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); DepositAddress vault = factory.createDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); DAFulfillmentParams memory fulfillment1 = DAFulfillmentParams({ depositAddress: address(vault), @@ -889,14 +1272,14 @@ contract DepositAddressManagerTest is Test { DAFulfillmentParams memory fulfillment1 = DAFulfillmentParams({ depositAddress: address(vault), relaySalt: relaySalt, - bridgeTokenOut: TokenAmount({token: usdc, amount: 100e6}), + bridgeTokenOut: _bridgeTokenOut(usdc, 100e6), sourceChainId: SOURCE_CHAIN_ID }); DAFulfillmentParams memory fulfillment2 = DAFulfillmentParams({ depositAddress: address(vault), relaySalt: relaySalt, - bridgeTokenOut: TokenAmount({token: usdc, amount: 200e6}), + bridgeTokenOut: _bridgeTokenOut(usdc, 200e6), sourceChainId: SOURCE_CHAIN_ID }); @@ -933,10 +1316,10 @@ contract DepositAddressManagerTest is Test { // Bridge amount should account for slippage uint256 bridgeAmount = (amount * (10_000 - MAX_START_SLIPPAGE_BPS)) / 10_000; - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: bridgeAmount - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + bridgeAmount + ); bytes32 relaySalt = keccak256(abi.encodePacked("salt", amount)); Call[] memory calls = new Call[](0); @@ -965,7 +1348,9 @@ contract DepositAddressManagerTest is Test { (address fulfillmentAddress, ) = manager.computeFulfillmentAddress( fulfillment ); - assertTrue(manager.fulfillmentUsed(fulfillmentAddress)); + assertTrue( + manager.fulfillmentUsed(manager.computeFulfillmentId(fulfillment)) + ); } function testFuzz_computeFulfillmentAddress_UniqueSalts( @@ -977,10 +1362,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); DepositAddress vault = factory.createDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); DAFulfillmentParams memory fulfillment1 = DAFulfillmentParams({ depositAddress: address(vault), @@ -1015,10 +1400,10 @@ contract DepositAddressManagerTest is Test { address depositAddress = factory.getDepositAddress(params); // Create bridge token out - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1080,10 +1465,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1149,10 +1534,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory bridgeTokenOutPrice = _createSignedPriceData( address(usdc), @@ -1221,9 +1606,10 @@ contract DepositAddressManagerTest is Test { // Create params that points to source chain DAParams memory params = DAParams({ + destinationType: DestinationType.EVM, toChainId: SOURCE_CHAIN_ID, - toToken: usdc, - toAddress: RECIPIENT, + toToken: _addressBytes(address(usdc)), + toAddress: _addressBytes(RECIPIENT), refundAddress: REFUND_ADDRESS, finalCallData: "", escrow: address(manager), @@ -1235,10 +1621,10 @@ contract DepositAddressManagerTest is Test { expiresAt: block.timestamp + 1000 }); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1259,7 +1645,7 @@ contract DepositAddressManagerTest is Test { vm.startPrank(RELAYER); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: same chain finish")); + vm.expectRevert(DepositAddressManager.SameChainFinishSource.selector); manager.fastFinish({ params: params, calls: calls, @@ -1279,10 +1665,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); // toChainId = DEST_CHAIN_ID - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1303,7 +1689,7 @@ contract DepositAddressManagerTest is Test { vm.startPrank(RELAYER); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: wrong chain")); + vm.expectRevert(DepositAddressManager.WrongChain.selector); manager.fastFinish({ params: params, calls: calls, @@ -1323,10 +1709,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); params.escrow = address(0xDEAD); // Wrong escrow - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1347,7 +1733,7 @@ contract DepositAddressManagerTest is Test { vm.startPrank(RELAYER); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: wrong escrow")); + vm.expectRevert(DepositAddressManager.WrongEscrow.selector); manager.fastFinish({ params: params, calls: calls, @@ -1373,10 +1759,10 @@ contract DepositAddressManagerTest is Test { // Fund relayer with tokens to fast finish usdc.transfer(RELAYER, BRIDGE_AMOUNT); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory bridgeTokenOutPrice = _createSignedPriceData( address(usdc), @@ -1394,7 +1780,7 @@ contract DepositAddressManagerTest is Test { // Expect revert vm.prank(RELAYER); - vm.expectRevert("DAM: expired"); + vm.expectRevert(DepositAddressManager.Expired.selector); manager.fastFinish({ params: params, calls: calls, @@ -1412,10 +1798,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1443,7 +1829,7 @@ contract DepositAddressManagerTest is Test { vm.startPrank(RELAYER); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: bridgeTokenOut price invalid")); + vm.expectRevert(DepositAddressManager.BridgeTokenOutPriceInvalid.selector); manager.fastFinish({ params: params, calls: calls, @@ -1462,10 +1848,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1490,7 +1876,7 @@ contract DepositAddressManagerTest is Test { vm.startPrank(RELAYER); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: toToken price invalid")); + vm.expectRevert(DepositAddressManager.ToTokenPriceInvalid.selector); manager.fastFinish({ params: params, calls: calls, @@ -1509,10 +1895,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1553,7 +1939,7 @@ contract DepositAddressManagerTest is Test { // Second fast finish with same salt should revert vm.startPrank(RELAYER); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: already finished")); + vm.expectRevert(DepositAddressManager.AlreadyFinished.selector); manager.fastFinish({ params: params, calls: calls, @@ -1572,10 +1958,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1598,7 +1984,7 @@ contract DepositAddressManagerTest is Test { vm.startPrank(RELAYER); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: bridgeTokenOut mismatch")); + vm.expectRevert(DepositAddressManager.BridgeTokenOutMismatch.selector); manager.fastFinish({ params: params, calls: calls, @@ -1617,10 +2003,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1644,7 +2030,7 @@ contract DepositAddressManagerTest is Test { vm.startPrank(RELAYER); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: toToken mismatch")); + vm.expectRevert(DepositAddressManager.ToTokenMismatch.selector); manager.fastFinish({ params: params, calls: calls, @@ -1663,10 +2049,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -1688,7 +2074,7 @@ contract DepositAddressManagerTest is Test { vm.startPrank(notRelayer); usdc.transfer(address(manager), BRIDGE_AMOUNT); - vm.expectRevert(bytes("DAM: not relayer")); + vm.expectRevert(DepositAddressManager.NotRelayer.selector); manager.fastFinish({ params: params, calls: calls, @@ -1719,10 +2105,7 @@ contract DepositAddressManagerTest is Test { uint256 toAmount = (amount * (10_000 - MAX_FAST_FINISH_SLIPPAGE_BPS)) / 10_000; - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: amount - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut(usdc, amount); bytes32 relaySalt = keccak256(abi.encodePacked("salt", amount)); @@ -1880,7 +2263,7 @@ contract DepositAddressManagerTest is Test { for (uint256 i = 0; i < recipients.length; i++) { DAParams memory params = _createDAParams(); - params.toAddress = recipients[i]; + params.toAddress = _addressBytes(recipients[i]); DepositAddress vault = factory.createDepositAddress(params); _fundDepositAddress(vault, PAYMENT_AMOUNT); @@ -1925,7 +2308,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: wrong chain")); + vm.expectRevert(DepositAddressManager.WrongChain.selector); vm.prank(RELAYER); manager.sameChainFinish({ params: params, @@ -1958,7 +2341,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: wrong escrow")); + vm.expectRevert(DepositAddressManager.WrongEscrow.selector); vm.prank(RELAYER); manager.sameChainFinish({ params: params, @@ -1998,7 +2381,7 @@ contract DepositAddressManagerTest is Test { // Expect revert vm.prank(RELAYER); - vm.expectRevert("DAM: expired"); + vm.expectRevert(DepositAddressManager.Expired.selector); manager.sameChainFinish({ params: params, paymentToken: usdc, @@ -2032,7 +2415,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: payment price invalid")); + vm.expectRevert(DepositAddressManager.PaymentPriceInvalid.selector); vm.prank(RELAYER); manager.sameChainFinish({ params: params, @@ -2067,7 +2450,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: toToken price invalid")); + vm.expectRevert(DepositAddressManager.ToTokenPriceInvalid.selector); vm.prank(RELAYER); manager.sameChainFinish({ params: params, @@ -2100,7 +2483,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: payment token mismatch")); + vm.expectRevert(DepositAddressManager.PaymentTokenMismatch.selector); vm.prank(RELAYER); manager.sameChainFinish({ params: params, @@ -2134,7 +2517,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: toToken mismatch")); + vm.expectRevert(DepositAddressManager.ToTokenMismatch.selector); vm.prank(RELAYER); manager.sameChainFinish({ params: params, @@ -2165,7 +2548,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: not relayer")); + vm.expectRevert(DepositAddressManager.NotRelayer.selector); vm.prank(address(0x1111)); // Not the relayer manager.sameChainFinish({ params: params, @@ -2266,10 +2649,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2329,10 +2712,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2413,10 +2796,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2476,10 +2859,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2555,10 +2938,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory bridgeTokenOutPrice = _createSignedPriceData( address(usdc), @@ -2620,10 +3003,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2677,10 +3060,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2739,10 +3122,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2798,10 +3181,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); // toChainId = DEST_CHAIN_ID - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2818,7 +3201,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: wrong chain")); + vm.expectRevert(DepositAddressManager.WrongChain.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -2837,10 +3220,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); params.escrow = address(0xDEAD); // Wrong escrow - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2857,7 +3240,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: wrong escrow")); + vm.expectRevert(DepositAddressManager.WrongEscrow.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -2876,10 +3259,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2911,7 +3294,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: bridgeTokenOut mismatch")); + vm.expectRevert(DepositAddressManager.BridgeTokenOutMismatch.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -2930,10 +3313,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -2966,7 +3349,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: toToken mismatch")); + vm.expectRevert(DepositAddressManager.ToTokenMismatch.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -2985,10 +3368,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -3034,7 +3417,7 @@ contract DepositAddressManagerTest is Test { usdc.transfer(fulfillmentAddress, BRIDGE_AMOUNT); // Second claim with same salt should revert - vm.expectRevert(bytes("DAM: already claimed")); + vm.expectRevert(DepositAddressManager.AlreadyClaimed.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -3053,10 +3436,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -3086,7 +3469,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: bridged amount too low")); + vm.expectRevert(DepositAddressManager.BridgedAmountTooLow.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -3105,10 +3488,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -3142,7 +3525,7 @@ contract DepositAddressManagerTest is Test { vm.stopPrank(); // Claim immediately without funding the fulfillment (grief attack) - vm.expectRevert(bytes("DAM: bridged amount too low")); + vm.expectRevert(DepositAddressManager.BridgedAmountTooLow.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -3163,10 +3546,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -3202,7 +3585,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: bridgeTokenOut price invalid")); + vm.expectRevert(DepositAddressManager.BridgeTokenOutPriceInvalid.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -3221,10 +3604,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -3257,7 +3640,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: toToken price invalid")); + vm.expectRevert(DepositAddressManager.ToTokenPriceInvalid.selector); vm.prank(RELAYER); manager.claim({ params: params, @@ -3276,11 +3659,11 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); - + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); + bytes32 relaySalt = keccak256("test-salt"); DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ @@ -3308,7 +3691,7 @@ contract DepositAddressManagerTest is Test { Call[] memory calls = new Call[](0); - vm.expectRevert(bytes("DAM: not relayer")); + vm.expectRevert(DepositAddressManager.NotRelayer.selector); vm.prank(address(0x1111)); // Not the relayer manager.claim({ params: params, @@ -3331,10 +3714,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -3435,10 +3818,7 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: amount - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut(usdc, amount); bytes32 relaySalt = keccak256(abi.encodePacked("salt", amount)); @@ -3496,10 +3876,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory bridgeTokenOutPrice = _createSignedPriceData( address(usdc), @@ -3584,10 +3964,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256(abi.encodePacked("salt", surplus)); @@ -3779,7 +4159,7 @@ contract DepositAddressManagerTest is Test { tokens[0] = usdc; // Expect revert - vm.expectRevert("DAM: not expired"); + vm.expectRevert(DepositAddressManager.NotExpired.selector); manager.refundDepositAddress({params: params, tokens: tokens}); } @@ -3795,7 +4175,7 @@ contract DepositAddressManagerTest is Test { tokens[0] = usdc; // Expect revert - vm.expectRevert("DAM: wrong escrow"); + vm.expectRevert(DepositAddressManager.WrongEscrow.selector); manager.refundDepositAddress({params: params, tokens: tokens}); } @@ -3944,7 +4324,7 @@ contract DepositAddressManagerTest is Test { // Non-relayer calling before expiry should revert vm.prank(address(0xBEEF)); - vm.expectRevert("DAM: not expired"); + vm.expectRevert(DepositAddressManager.NotExpired.selector); manager.refundDepositAddress({params: params, tokens: tokens}); } @@ -3958,7 +4338,7 @@ contract DepositAddressManagerTest is Test { // Relayer calling with wrong escrow should revert vm.prank(RELAYER); - vm.expectRevert("DAM: wrong escrow"); + vm.expectRevert(DepositAddressManager.WrongEscrow.selector); manager.refundDepositAddress({params: params, tokens: tokens}); } @@ -3984,10 +4364,10 @@ contract DepositAddressManagerTest is Test { USDC_PRICE, block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); Call[] memory calls = new Call[](0); @@ -4027,10 +4407,10 @@ contract DepositAddressManagerTest is Test { address depositAddress = factory.getDepositAddress(params); bytes32 relaySalt = keccak256("test-refund-salt"); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Compute fulfillment address DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ @@ -4079,10 +4459,10 @@ contract DepositAddressManagerTest is Test { address depositAddress = factory.getDepositAddress(params); bytes32 relaySalt = keccak256("test-refund-salt"); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ depositAddress: depositAddress, @@ -4132,10 +4512,10 @@ contract DepositAddressManagerTest is Test { address depositAddress = factory.getDepositAddress(params); bytes32 relaySalt = keccak256("test-refund-salt"); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Compute fulfillment address DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ @@ -4190,10 +4570,10 @@ contract DepositAddressManagerTest is Test { address depositAddress = factory.getDepositAddress(params); bytes32 relaySalt = keccak256("test-refund-salt"); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Compute fulfillment address DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ @@ -4244,10 +4624,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); bytes32 relaySalt = keccak256("test-refund-salt"); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Don't fund the fulfillment - it has zero balance @@ -4281,10 +4661,10 @@ contract DepositAddressManagerTest is Test { params.escrow = address(0x1234); // Wrong escrow bytes32 relaySalt = keccak256("test-refund-salt"); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Create tokens array IERC20[] memory tokens = new IERC20[](1); @@ -4292,7 +4672,7 @@ contract DepositAddressManagerTest is Test { // Expect revert vm.prank(RELAYER); - vm.expectRevert("DAM: wrong escrow"); + vm.expectRevert(DepositAddressManager.WrongEscrow.selector); manager.refundFulfillment({ params: params, bridgeTokenOut: bridgeTokenOut, @@ -4310,10 +4690,10 @@ contract DepositAddressManagerTest is Test { address depositAddress = factory.getDepositAddress(params); bytes32 relaySalt = keccak256("test-refund-salt"); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Compute fulfillment address DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ @@ -4335,7 +4715,7 @@ contract DepositAddressManagerTest is Test { // Expect revert when called by non-relayer vm.prank(address(0xBEEF)); - vm.expectRevert("DAM: not relayer"); + vm.expectRevert(DepositAddressManager.NotRelayer.selector); manager.refundFulfillment({ params: params, bridgeTokenOut: bridgeTokenOut, @@ -4356,10 +4736,10 @@ contract DepositAddressManagerTest is Test { address depositAddress = factory.getDepositAddress(params); bytes32 relaySalt = keccak256("test-refund-salt"); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Compute hop fulfillment address (where leg1 bridged tokens would arrive) DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ @@ -4394,6 +4774,73 @@ contract DepositAddressManagerTest is Test { assertEq(usdc.balanceOf(hopFulfillmentAddress), 0); } + function test_refundFulfillment_SolanaHopChainRefund() public { + _assertNonEvmHopChainRefund({ + destinationType: DestinationType.SOLANA, + toChainId: 501, + toToken: _solanaBytes(1), + toAddress: _solanaBytes(2) + }); + } + + function test_refundFulfillment_TronHopChainRefund() public { + _assertNonEvmHopChainRefund({ + destinationType: DestinationType.TRON, + toChainId: 728126428, + toToken: _tronBytes(1), + toAddress: _tronBytes(2) + }); + } + + function _assertNonEvmHopChainRefund( + DestinationType destinationType, + uint256 toChainId, + bytes memory toToken, + bytes memory toAddress + ) internal { + vm.chainId(HOP_CHAIN_ID); + + DAParams memory params = _createNonEvmDAParams({ + destinationType: destinationType, + toChainId: toChainId, + toToken: toToken, + toAddress: toAddress + }); + address depositAddress = factory.getDepositAddress(params); + bytes32 relaySalt = keccak256("test-non-evm-refund-salt"); + BridgeTokenAmount memory bridgeTokenOut = _nonEvmBridgeTokenOut({ + token: params.toToken, + amount: BRIDGE_AMOUNT + }); + + DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ + depositAddress: depositAddress, + relaySalt: relaySalt, + bridgeTokenOut: bridgeTokenOut, + sourceChainId: SOURCE_CHAIN_ID + }); + (address hopFulfillmentAddress, ) = manager.computeFulfillmentAddress( + fulfillment + ); + + usdc.transfer(hopFulfillmentAddress, BRIDGE_AMOUNT); + + IERC20[] memory tokens = new IERC20[](1); + tokens[0] = usdc; + + vm.prank(RELAYER); + manager.refundFulfillment({ + params: params, + bridgeTokenOut: bridgeTokenOut, + relaySalt: relaySalt, + sourceChainId: SOURCE_CHAIN_ID, + tokens: tokens + }); + + assertEq(usdc.balanceOf(REFUND_ADDRESS), BRIDGE_AMOUNT); + assertEq(usdc.balanceOf(hopFulfillmentAddress), 0); + } + function test_refundFulfillment_Leg2FulfillmentRefund() public { // Test refunding tokens stuck on the destination chain after a hop // Scenario: source -> hop -> dest bridge completed, but never claimed @@ -4412,10 +4859,10 @@ contract DepositAddressManagerTest is Test { }); // Leg 2: hop -> dest - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Compute and fund leg1 fulfillment DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ @@ -4498,10 +4945,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -4551,7 +4998,7 @@ contract DepositAddressManagerTest is Test { // Refund should revert — relayer already fast-finished vm.prank(RELAYER); - vm.expectRevert("DAM: already finished"); + vm.expectRevert(DepositAddressManager.AlreadyFinished.selector); manager.refundFulfillment({ params: params, bridgeTokenOut: bridgeTokenOut, @@ -4567,10 +5014,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -4615,7 +5062,7 @@ contract DepositAddressManagerTest is Test { // Refund should revert — already claimed vm.prank(RELAYER); - vm.expectRevert("DAM: already finished"); + vm.expectRevert(DepositAddressManager.AlreadyFinished.selector); manager.refundFulfillment({ params: params, bridgeTokenOut: bridgeTokenOut, @@ -4631,10 +5078,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -4680,7 +5127,7 @@ contract DepositAddressManagerTest is Test { // Claim should revert — already refunded vm.prank(RELAYER); - vm.expectRevert("DAM: already claimed"); + vm.expectRevert(DepositAddressManager.AlreadyClaimed.selector); manager.claim({ params: params, calls: calls, @@ -4698,10 +5145,10 @@ contract DepositAddressManagerTest is Test { DAParams memory params = _createDAParams(); address depositAddress = factory.getDepositAddress(params); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); bytes32 relaySalt = keccak256("test-salt"); @@ -4732,7 +5179,7 @@ contract DepositAddressManagerTest is Test { // Second refund should revert vm.prank(RELAYER); - vm.expectRevert("DAM: already finished"); + vm.expectRevert(DepositAddressManager.AlreadyFinished.selector); manager.refundFulfillment({ params: params, bridgeTokenOut: bridgeTokenOut, @@ -4775,7 +5222,7 @@ contract DepositAddressManagerTest is Test { // Create params using the reentrant token DAParams memory params = _createDAParams(); - params.toToken = evilToken; + params.toToken = _addressBytes(address(evilToken)); // Create deposit address address vault = address(factory.createDepositAddress(params)); @@ -4797,10 +5244,10 @@ contract DepositAddressManagerTest is Test { ); // Prepare fulfillment parameters - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); Call[] memory startCalls = new Call[](0); bytes memory bridgeExtraData = ""; @@ -4835,7 +5282,7 @@ contract DepositAddressManagerTest is Test { // Create params DAParams memory params = _createDAParams(); - params.toToken = evilToken; + params.toToken = _addressBytes(address(evilToken)); // Mint tokens to relayer evilToken.transfer(RELAYER, PAYMENT_AMOUNT); @@ -4853,10 +5300,10 @@ contract DepositAddressManagerTest is Test { ); // Prepare fulfillment parameters - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); Call[] memory finishCalls = new Call[](0); @@ -4893,7 +5340,7 @@ contract DepositAddressManagerTest is Test { // Create params with same source and dest chain DAParams memory params = _createDAParams(); params.toChainId = SOURCE_CHAIN_ID; // Same chain - params.toToken = evilToken; + params.toToken = _addressBytes(address(evilToken)); // Create deposit address and fund it address vault = address(factory.createDepositAddress(params)); @@ -4949,10 +5396,10 @@ contract DepositAddressManagerTest is Test { // Leg 2: hop -> dest (e.g., Arbitrum -> Base) // Use same amount since dummy bridger doesn't charge fees - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Compute leg 1 fulfillment (where funds from source->hop arrive) DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ @@ -5005,29 +5452,100 @@ contract DepositAddressManagerTest is Test { ); // Verify the fulfillment is marked as used (hopStart reuses fulfillment address) - assertTrue(manager.fulfillmentUsed(fulfillmentAddress)); + assertTrue( + manager.fulfillmentUsed(manager.computeFulfillmentId(fulfillment)) + ); // Verify bridger received tokens (burned to 0xdead by dummy bridger) assertEq(usdc.balanceOf(address(0xdead)), leg2BridgeTokenOut.amount); } - function test_hopStart_EmitsHopEvent() public { + function test_hopStart_SolanaDirectFinalLeg() public { vm.chainId(HOP_CHAIN_ID); - DAParams memory params = _createDAParams(); + DAParams memory params = _createNonEvmDAParams({ + destinationType: DestinationType.SOLANA, + toChainId: 501, + toToken: _solanaBytes(1), + toAddress: _solanaBytes(2) + }); + bridger.setBridgeTokenInOverride(address(usdc)); address depositAddress = factory.getDepositAddress(params); - bytes32 relaySalt = keccak256("test-relay-salt"); + bytes32 relaySalt = keccak256("solana-hopstart-salt"); TokenAmount memory leg1BridgeTokenOut = TokenAmount({ token: usdc, amount: BRIDGE_AMOUNT }); + BridgeTokenAmount memory leg2BridgeTokenOut = _nonEvmBridgeTokenOut( + params.toToken, + BRIDGE_AMOUNT + ); + + DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ + depositAddress: depositAddress, + relaySalt: relaySalt, + bridgeTokenOut: leg2BridgeTokenOut, + sourceChainId: SOURCE_CHAIN_ID + }); + (address fulfillmentAddress, ) = manager.computeFulfillmentAddress( + fulfillment + ); + usdc.transfer(fulfillmentAddress, BRIDGE_AMOUNT); + + PriceData memory leg1BridgeTokenOutPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + PriceData memory leg2BridgeTokenInPrice = _createSignedPriceData( + address(usdc), + USDC_PRICE, + block.timestamp + ); + Call[] memory calls = new Call[](0); + + vm.prank(RELAYER); + manager.hopStart({ + params: params, + leg1BridgeTokenOut: leg1BridgeTokenOut, + leg1SourceChainId: SOURCE_CHAIN_ID, + leg1BridgeTokenOutPrice: leg1BridgeTokenOutPrice, + leg2BridgeTokenOut: leg2BridgeTokenOut, + leg2BridgeTokenInPrice: leg2BridgeTokenInPrice, + bridgerAdapter: address(bridger), + relaySalt: relaySalt, + calls: calls, + bridgeExtraData: "" + }); + + assertEq( + manager.fulfillmentToRecipient(fulfillmentAddress), + manager.ADDR_MAX() + ); + assertEq( + keccak256(bridger.lastToAddress()), + keccak256(params.toAddress) + ); + } + + function test_hopStart_EmitsHopEvent() public { + vm.chainId(HOP_CHAIN_ID); + + DAParams memory params = _createDAParams(); + address depositAddress = factory.getDepositAddress(params); + bytes32 relaySalt = keccak256("test-relay-salt"); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ + TokenAmount memory leg1BridgeTokenOut = TokenAmount({ token: usdc, amount: BRIDGE_AMOUNT }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); + DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ depositAddress: depositAddress, relaySalt: relaySalt, @@ -5093,10 +5611,10 @@ contract DepositAddressManagerTest is Test { token: usdc, amount: BRIDGE_AMOUNT }); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory leg1Price = _createSignedPriceData( address(usdc), @@ -5109,7 +5627,7 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - vm.expectRevert(bytes("DAM: hop on source chain")); + vm.expectRevert(DepositAddressManager.HopOnSourceChain.selector); vm.prank(RELAYER); manager.hopStart({ params: params, @@ -5135,10 +5653,10 @@ contract DepositAddressManagerTest is Test { token: usdc, amount: BRIDGE_AMOUNT }); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory leg1Price = _createSignedPriceData( address(usdc), @@ -5151,7 +5669,7 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - vm.expectRevert(bytes("DAM: hop on dest chain")); + vm.expectRevert(DepositAddressManager.HopOnDestChain.selector); vm.prank(RELAYER); manager.hopStart({ params: params, @@ -5177,10 +5695,10 @@ contract DepositAddressManagerTest is Test { token: usdc, amount: BRIDGE_AMOUNT }); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory leg1Price = _createSignedPriceData( address(usdc), @@ -5193,7 +5711,7 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - vm.expectRevert(bytes("DAM: wrong escrow")); + vm.expectRevert(DepositAddressManager.WrongEscrow.selector); vm.prank(RELAYER); manager.hopStart({ params: params, @@ -5218,10 +5736,10 @@ contract DepositAddressManagerTest is Test { token: usdc, amount: BRIDGE_AMOUNT }); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory leg1Price = _createSignedPriceData( address(usdc), @@ -5234,7 +5752,7 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - vm.expectRevert(bytes("DAM: not relayer")); + vm.expectRevert(DepositAddressManager.NotRelayer.selector); vm.prank(address(0x1111)); // Not the relayer manager.hopStart({ params: params, @@ -5262,15 +5780,18 @@ contract DepositAddressManagerTest is Test { }); bytes32 leg1RelaySalt = keccak256("leg1-salt"); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ depositAddress: depositAddress, relaySalt: leg1RelaySalt, - bridgeTokenOut: leg1BridgeTokenOut, + bridgeTokenOut: _bridgeTokenOut( + leg1BridgeTokenOut.token, + leg1BridgeTokenOut.amount + ), sourceChainId: SOURCE_CHAIN_ID }); (address hopFulfillmentAddress, ) = manager.computeFulfillmentAddress( @@ -5308,7 +5829,7 @@ contract DepositAddressManagerTest is Test { }); // Second hop with same leg1 params should fail (already claimed) - vm.expectRevert(bytes("DAM: already claimed")); + vm.expectRevert(DepositAddressManager.AlreadyClaimed.selector); vm.prank(RELAYER); manager.hopStart({ params: params, @@ -5339,7 +5860,10 @@ contract DepositAddressManagerTest is Test { DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ depositAddress: depositAddress, relaySalt: leg1RelaySalt, - bridgeTokenOut: leg1BridgeTokenOut, + bridgeTokenOut: _bridgeTokenOut( + leg1BridgeTokenOut.token, + leg1BridgeTokenOut.amount + ), sourceChainId: SOURCE_CHAIN_ID }); (address hopFulfillmentAddress, ) = manager.computeFulfillmentAddress( @@ -5349,10 +5873,10 @@ contract DepositAddressManagerTest is Test { // Fund with less than expected usdc.transfer(hopFulfillmentAddress, BRIDGE_AMOUNT / 2); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); PriceData memory leg1Price = _createSignedPriceData( address(usdc), @@ -5396,7 +5920,10 @@ contract DepositAddressManagerTest is Test { DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ depositAddress: depositAddress, relaySalt: leg1RelaySalt, - bridgeTokenOut: leg1BridgeTokenOut, + bridgeTokenOut: _bridgeTokenOut( + leg1BridgeTokenOut.token, + leg1BridgeTokenOut.amount + ), sourceChainId: SOURCE_CHAIN_ID }); (address hopFulfillmentAddress, ) = manager.computeFulfillmentAddress( @@ -5404,10 +5931,10 @@ contract DepositAddressManagerTest is Test { ); usdc.transfer(hopFulfillmentAddress, BRIDGE_AMOUNT); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); // Invalid signature PriceData memory leg1Price = PriceData({ @@ -5424,7 +5951,7 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - vm.expectRevert(bytes("DAM: leg1 price invalid")); + vm.expectRevert(DepositAddressManager.Leg1PriceInvalid.selector); vm.prank(RELAYER); manager.hopStart({ params: params, @@ -5452,10 +5979,10 @@ contract DepositAddressManagerTest is Test { amount: BRIDGE_AMOUNT }); - TokenAmount memory leg2BridgeTokenOut = TokenAmount({ - token: usdc, - amount: BRIDGE_AMOUNT - }); + BridgeTokenAmount memory leg2BridgeTokenOut = _bridgeTokenOut( + usdc, + BRIDGE_AMOUNT + ); DAFulfillmentParams memory fulfillment = DAFulfillmentParams({ depositAddress: depositAddress, @@ -5483,7 +6010,7 @@ contract DepositAddressManagerTest is Test { }); leg2Price.signature = _signPriceData(leg2Price, 0xBAD); - vm.expectRevert(bytes("DAM: leg2 price invalid")); + vm.expectRevert(DepositAddressManager.Leg2PriceInvalid.selector); vm.prank(RELAYER); manager.hopStart({ params: params, @@ -5511,9 +6038,10 @@ contract DepositAddressManagerTest is Test { // Create params with finalCallData - toAddress is now the adapter DAParams memory params = DAParams({ + destinationType: DestinationType.EVM, toChainId: DEST_CHAIN_ID, - toToken: usdc, - toAddress: address(adapter), + toToken: _addressBytes(address(usdc)), + toAddress: _addressBytes(address(adapter)), refundAddress: REFUND_ADDRESS, finalCallData: abi.encodeCall( MockDepositAdapter.deposit, @@ -5573,9 +6101,10 @@ contract DepositAddressManagerTest is Test { MockDepositAdapter adapter = new MockDepositAdapter(usdc); DAParams memory params = DAParams({ + destinationType: DestinationType.EVM, toChainId: DEST_CHAIN_ID, - toToken: usdc, - toAddress: address(adapter), + toToken: _addressBytes(address(usdc)), + toAddress: _addressBytes(address(adapter)), refundAddress: REFUND_ADDRESS, finalCallData: abi.encodeCall( MockDepositAdapter.deposit, @@ -5640,9 +6169,10 @@ contract DepositAddressManagerTest is Test { ); DAParams memory params = DAParams({ + destinationType: DestinationType.EVM, toChainId: DEST_CHAIN_ID, - toToken: usdc, - toAddress: address(partialAdapter), + toToken: _addressBytes(address(usdc)), + toAddress: _addressBytes(address(partialAdapter)), refundAddress: REFUND_ADDRESS, finalCallData: abi.encodeCall( PartialDepositAdapter.deposit, @@ -5713,9 +6243,10 @@ contract DepositAddressManagerTest is Test { MockDepositAdapter adapter = new MockDepositAdapter(usdc); DAParams memory params = DAParams({ + destinationType: DestinationType.EVM, toChainId: DEST_CHAIN_ID, - toToken: usdc, - toAddress: address(adapter), + toToken: _addressBytes(address(usdc)), + toAddress: _addressBytes(address(adapter)), refundAddress: REFUND_ADDRESS, finalCallData: abi.encodeCall( MockDepositAdapter.deposit, @@ -5744,10 +6275,10 @@ contract DepositAddressManagerTest is Test { block.timestamp ); - TokenAmount memory bridgeTokenOut = TokenAmount({ - token: usdc, - amount: PAYMENT_AMOUNT - }); + BridgeTokenAmount memory bridgeTokenOut = _bridgeTokenOut( + usdc, + PAYMENT_AMOUNT + ); Call[] memory calls = new Call[](0); diff --git a/packages/contract/test/utils/DummyDepositBridger.sol b/packages/contract/test/utils/DummyDepositBridger.sol index 4eb6b7a..718affd 100644 --- a/packages/contract/test/utils/DummyDepositBridger.sol +++ b/packages/contract/test/utils/DummyDepositBridger.sol @@ -1,59 +1,159 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.12; -import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import { - IDepositAddressBridger + IDepositAddressBridgeEvents, + IDepositAddressBridger, + BridgeRecipientMode } from "../../src/interfaces/IDepositAddressBridger.sol"; +import { + DestinationType, + BridgeTokenAmount +} from "../../src/DestinationUtils.sol"; import {TokenAmount} from "../../src/TokenUtils.sol"; import {IDaimoPayBridger} from "../../src/interfaces/IDaimoPayBridger.sol"; +import {DestinationUtils} from "../../src/DestinationUtils.sol"; /// @title DummyDepositAddressBridger /// @notice Minimal in-memory implementation of the IDepositAddressBridger used exclusively in Foundry tests. -/// It simply transfers `minOut` units of `outToken` from the caller to the destination address and -/// echoes the parameters via an event. No real bridging is performed. -contract DummyDepositAddressBridger is IDepositAddressBridger { +/// It burns bridge input tokens and echoes the parameters via events. +contract DummyDepositAddressBridger is + IDepositAddressBridger, + IDepositAddressBridgeEvents +{ using SafeERC20 for IERC20; + address public bridgeTokenInOverride; + DestinationType public lastDestinationType; + uint256 public lastToChainId; + bytes public lastToAddress; + bytes public lastTokenOut; + uint256 public lastAmount; + address public lastBridgerAdapter; + address public lastRefundAddress; + bool public bridgeRecipientModeOverrideSet; + BridgeRecipientMode public bridgeRecipientModeOverride; + + function setBridgeTokenInOverride(address token) external { + bridgeTokenInOverride = token; + } + + function setBridgeRecipientMode(BridgeRecipientMode mode) external { + bridgeRecipientModeOverrideSet = true; + bridgeRecipientModeOverride = mode; + } + + function clearBridgeRecipientMode() external { + bridgeRecipientModeOverrideSet = false; + } + // --------------------------------------------------------------------- // IDepositAddressBridger // --------------------------------------------------------------------- function getBridgeTokenIn( - uint256 /*toChainId*/, - TokenAmount calldata stableOut, - address bridgerAdapter - ) external pure override returns (address bridgeTokenIn, uint256 inAmount) { - bridgeTokenIn = address(stableOut.token); - inAmount = stableOut.amount; + DestinationType destinationType, + uint256, + /*toChainId*/ + BridgeTokenAmount calldata tokenOut, + address /*bridgerAdapter*/ + ) external view override returns (address bridgeTokenIn, uint256 inAmount) { + bridgeTokenIn = _getBridgeTokenIn(destinationType, tokenOut); + inAmount = tokenOut.amount; } function sendToChain( + DestinationType destinationType, uint256 toChainId, - address toAddress, - TokenAmount calldata stableOut, + bytes calldata toAddress, + bytes calldata fulfillmentAddress, + BridgeTokenAmount calldata tokenOut, address bridgerAdapter, address refundAddress, bytes calldata /* extraData */ ) external override { - // Burn the tokens, simulating a slow bridge - stableOut.token.safeTransferFrom( + address bridgeTokenIn = _getBridgeTokenIn(destinationType, tokenOut); + BridgeRecipientMode recipientMode = _bridgeRecipientMode( + destinationType + ); + bytes calldata bridgeRecipient = recipientMode == + BridgeRecipientMode.DIRECT + ? toAddress + : fulfillmentAddress; + + // Burn the tokens, simulating a slow bridge. + IERC20(bridgeTokenIn).safeTransferFrom( msg.sender, address(0xdead), - stableOut.amount + tokenOut.amount ); - emit IDaimoPayBridger.BridgeInitiated({ + lastDestinationType = destinationType; + lastToChainId = toChainId; + lastToAddress = bridgeRecipient; + lastTokenOut = tokenOut.token; + lastAmount = tokenOut.amount; + lastBridgerAdapter = bridgerAdapter; + lastRefundAddress = refundAddress; + + if (destinationType == DestinationType.EVM) { + emit IDaimoPayBridger.BridgeInitiated({ + fromAddress: msg.sender, + fromToken: bridgeTokenIn, + fromAmount: tokenOut.amount, + toChainId: toChainId, + toAddress: DestinationUtils.decodeEvmAddress(bridgeRecipient), + toToken: DestinationUtils.decodeEvmAddress(tokenOut.token), + toAmount: tokenOut.amount, + refundAddress: refundAddress + }); + } + + emit BridgeInitiatedBytes({ fromAddress: msg.sender, - fromToken: address(stableOut.token), - fromAmount: stableOut.amount, + fromToken: bridgeTokenIn, + fromAmount: tokenOut.amount, + destinationType: destinationType, toChainId: toChainId, - toAddress: toAddress, - toToken: address(stableOut.token), - toAmount: stableOut.amount, + toAddress: bridgeRecipient, + toToken: tokenOut.token, + toAmount: tokenOut.amount, refundAddress: refundAddress }); } + + function getBridgeRecipientMode( + DestinationType destinationType, + uint256, + /*toChainId*/ + BridgeTokenAmount calldata, + /*tokenOut*/ + address /*bridgerAdapter*/ + ) external view override returns (BridgeRecipientMode) { + return _bridgeRecipientMode(destinationType); + } + + function _getBridgeTokenIn( + DestinationType destinationType, + BridgeTokenAmount calldata tokenOut + ) private view returns (address) { + if (destinationType == DestinationType.EVM) { + return DestinationUtils.decodeEvmAddress(tokenOut.token); + } + require(bridgeTokenInOverride != address(0), "DDB: no bridge token"); + return bridgeTokenInOverride; + } + + function _bridgeRecipientMode( + DestinationType destinationType + ) private view returns (BridgeRecipientMode) { + if (bridgeRecipientModeOverrideSet) return bridgeRecipientModeOverride; + if (destinationType == DestinationType.EVM) { + return BridgeRecipientMode.FULFILLMENT; + } + return BridgeRecipientMode.DIRECT; + } }