diff --git a/foundry.toml b/foundry.toml index dd87c33..d607fc2 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc_version = "0.8.13" +solc_version = "0.8.20" src = "src" out = "out" libs = ["lib"] diff --git a/src/CCTPAndTokenBase.sol b/src/CCTPAndTokenBase.sol index 989ad38..8f19c3c 100644 --- a/src/CCTPAndTokenBase.sol +++ b/src/CCTPAndTokenBase.sol @@ -7,7 +7,7 @@ import "./interfaces/ITokenBridge.sol"; import {IERC20} from "./interfaces/IERC20.sol"; import "./interfaces/CCTPInterfaces/ITokenMessenger.sol"; import "./interfaces/CCTPInterfaces/IMessageTransmitter.sol"; - +import "./lib/wormhole.lib.sol"; import "./Utils.sol"; import "./TokenBase.sol"; import "./CCTPBase.sol"; @@ -156,10 +156,6 @@ abstract contract CCTPAndTokenSender is CCTPAndTokenBase { ); } - function addressToBytes32CCTP(address addr) private pure returns (bytes32) { - return bytes32(uint256(uint160(addr))); - } - // TokenBridge Sender functions, taken from "./TokenBase.sol" /** @@ -449,7 +445,7 @@ abstract contract CCTPAndTokenReceiver is CCTPAndTokenBase { // Implement this function to handle in-bound deliveries that include a TokenBridge transfer function receivePayloadAndTokens( bytes memory payload, - TokenReceived[] memory receivedTokens, + WormholeLib.TokenReceived[] memory receivedTokens, bytes32 sourceAddress, uint16 sourceChain, bytes32 deliveryHash diff --git a/src/TokenBase.sol b/src/TokenBase.sol index 6e96c49..117fa58 100644 --- a/src/TokenBase.sol +++ b/src/TokenBase.sol @@ -5,8 +5,8 @@ import "./interfaces/IWormholeReceiver.sol"; import "./interfaces/IWormholeRelayer.sol"; import "./interfaces/ITokenBridge.sol"; import {IERC20} from "./interfaces/IERC20.sol"; -import {Base} from "./WormholeRelayerSDK.sol"; - +import {Base} from "./Base.sol"; +import "./lib/wormhole.lib.sol"; import "./Utils.sol"; abstract contract TokenBase is Base { @@ -156,13 +156,6 @@ abstract contract TokenSender is TokenBase { } abstract contract TokenReceiver is TokenBase { - struct TokenReceived { - bytes32 tokenHomeAddress; - uint16 tokenHomeChain; - address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format) - uint256 amount; - uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places - } function getDecimals( address tokenAddress @@ -243,7 +236,7 @@ abstract contract TokenReceiver is TokenBase { // Implement this function to handle in-bound deliveries that include a TokenBridge transfer function receivePayloadAndTokens( bytes memory payload, - TokenReceived[] memory receivedTokens, + WormholeLib.TokenReceived[] memory receivedTokens, bytes32 sourceAddress, uint16 sourceChain, bytes32 deliveryHash diff --git a/src/Utils.sol b/src/Utils.sol index 1f93441..4ddf3d9 100644 --- a/src/Utils.sol +++ b/src/Utils.sol @@ -14,3 +14,7 @@ function fromWormholeFormat(bytes32 whFormatAddress) pure returns (address) { } return address(uint160(uint256(whFormatAddress))); } + +function addressToBytes32CCTP(address addr) pure returns (bytes32) { + return toWormholeFormat(addr); +} \ No newline at end of file diff --git a/src/lib/wormhole.lib.sol b/src/lib/wormhole.lib.sol new file mode 100644 index 0000000..2e9af4a --- /dev/null +++ b/src/lib/wormhole.lib.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.8.13; + + +library WormholeLib{ + struct TokenReceived { + bytes32 tokenHomeAddress; + uint16 tokenHomeChain; + address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format) + uint256 amount; + uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places + } +} \ No newline at end of file diff --git a/test/CCTPAndTokenBridge_Init.test.sol b/test/CCTPAndTokenBridge_Init.test.sol new file mode 100644 index 0000000..746e519 --- /dev/null +++ b/test/CCTPAndTokenBridge_Init.test.sol @@ -0,0 +1,252 @@ +pragma solidity ^0.8.20; + +import "../src/initializable-sdk/WormholeRelayerSDK.sol"; +import "../src/initializable-sdk/interfaces/IWormholeReceiver.sol"; +import "../src/initializable-sdk/interfaces/IWormholeRelayer.sol"; +import "../src/initializable-sdk/interfaces/IERC20.sol"; + +import "../src/initializable-sdk/testing/WormholeRelayerTest.sol"; + +import "../src/initializable-sdk/Utils.sol"; + +contract CCTPAndTokenBridgeToy is CCTPAndTokenSender, CCTPAndTokenReceiver { + uint256 constant GAS_LIMIT = 250_000; + + constructor( + ) + { + setCCTPDomain(5, 7); + setCCTPDomain(6, 1); + } + + function init( + address _wormholeRelayer, + address _tokenBridge, + address _wormhole, + address _circleMessageTransmitter, + address _circleTokenMessenger, + address _USDC + ) public { + _initCCTPTokenBase( + _wormholeRelayer, + _tokenBridge, + _wormhole, + _circleMessageTransmitter, + _circleTokenMessenger, + _USDC + ); + } + + function quoteCrossChainDeposit( + uint16 targetChain + ) public view returns (uint256 cost) { + // Cost of delivering token and payload to targetChain + (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( + targetChain, + 0, + GAS_LIMIT + ); + } + + function sendCrossChainDeposit( + uint16 targetChain, + address recipient, + uint256 amount, + address token + ) public payable { + uint256 cost = quoteCrossChainDeposit(targetChain); + require( + msg.value == cost, + "msg.value must be quoteCrossChainDeposit(targetChain)" + ); + + IERC20(token).transferFrom(msg.sender, address(this), amount); + + bytes memory payload = abi.encode(recipient, amount); + sendTokenWithPayloadToEvm( + targetChain, + fromWormholeFormat(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to + payload, + 0, // receiver value + GAS_LIMIT, + token, + amount + ); + } + + function sendCrossChainUSDCDeposit( + uint16 targetChain, + address recipient, + uint256 amount + ) public payable { + uint256 cost = quoteCrossChainDeposit(targetChain); + require( + msg.value == cost, + "msg.value must be quoteCrossChainDeposit(targetChain)" + ); + + IERC20(USDC).transferFrom(msg.sender, address(this), amount); + + bytes memory payload = abi.encode(recipient, amount); + sendUSDCWithPayloadToEvm( + targetChain, + fromWormholeFormat(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to + payload, + 0, // receiver value + GAS_LIMIT, + amount + ); + } + + function receivePayloadAndUSDC( + bytes memory payload, + uint256 amount, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 // deliveryHash + ) + internal + override + onlyWormholeRelayer + isRegisteredSender(sourceChain, sourceAddress) + { + (address recipient, uint256 expectedAmount) = abi.decode( + payload, + (address, uint256) + ); + require(amount == expectedAmount, "amount != payload.expectedAmount"); + IERC20(USDC).transfer(recipient, amount); + } + + function receivePayloadAndTokens( + bytes memory payload, + TokenReceived[] memory receivedTokens, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 // deliveryHash + ) + internal + override + onlyWormholeRelayer + isRegisteredSender(sourceChain, sourceAddress) + { + require(receivedTokens.length == 1, "Expected 1 token transfers"); + address recipient = abi.decode(payload, (address)); + IERC20(receivedTokens[0].tokenAddress).transfer( + recipient, + receivedTokens[0].amount + ); + } +} + +contract WormholeSDKTest is WormholeRelayerBasicTest { + CCTPAndTokenBridgeToy CCTPAndTokenBridgeToySource; + CCTPAndTokenBridgeToy CCTPAndTokenBridgeToyTarget; + ERC20Mock USDCSource; + ERC20Mock USDCTarget; + ERC20Mock public token; + + constructor() { + setTestnetForkChains(5, 6); + } + + function setUpSource() public override { + USDCSource = ERC20Mock(address(sourceChainInfo.USDC)); + mintUSDC(sourceChain, address(this), 5000e18); + CCTPAndTokenBridgeToySource = new CCTPAndTokenBridgeToy(); + CCTPAndTokenBridgeToySource.init( + address(relayerSource), + address(tokenBridgeSource), + address(wormholeSource), + address(sourceChainInfo.circleMessageTransmitter), + address(sourceChainInfo.circleTokenMessenger), + address(USDCSource) + ); + token = createAndAttestToken(sourceChain); + } + + function setUpTarget() public override { + USDCTarget = ERC20Mock(address(targetChainInfo.USDC)); + mintUSDC(targetChain, address(this), 5000e18); + CCTPAndTokenBridgeToyTarget = new CCTPAndTokenBridgeToy(); + CCTPAndTokenBridgeToyTarget.init( + address(relayerTarget), + address(tokenBridgeTarget), + address(wormholeTarget), + address(targetChainInfo.circleMessageTransmitter), + address(targetChainInfo.circleTokenMessenger), + address(USDCTarget) + ); + } + + function setUpGeneral() public override { + vm.selectFork(sourceFork); + CCTPAndTokenBridgeToySource.setRegisteredSender( + targetChain, + toWormholeFormat(address(CCTPAndTokenBridgeToyTarget)) + ); + + vm.selectFork(targetFork); + CCTPAndTokenBridgeToyTarget.setRegisteredSender( + sourceChain, + toWormholeFormat(address(CCTPAndTokenBridgeToySource)) + ); + } + + function testSendUSDC() public { + vm.selectFork(sourceFork); + + uint256 amount = 100e6; + USDCSource.approve(address(CCTPAndTokenBridgeToySource), amount); + + vm.selectFork(targetFork); + address recipient = 0x1234567890123456789012345678901234567890; + + vm.selectFork(sourceFork); + uint256 cost = CCTPAndTokenBridgeToySource.quoteCrossChainDeposit( + targetChain + ); + + vm.recordLogs(); + CCTPAndTokenBridgeToySource.sendCrossChainUSDCDeposit{value: cost}( + targetChain, + recipient, + amount + ); + performDelivery(true); + + vm.selectFork(targetFork); + assertEq(IERC20(USDCTarget).balanceOf(recipient), amount); + } + + function testSendToken() public { + vm.selectFork(sourceFork); + + uint256 amount = 19e17; + token.approve(address(CCTPAndTokenBridgeToySource), amount); + + vm.selectFork(targetFork); + address recipient = 0x1234567890123456789012345678901234567890; + + vm.selectFork(sourceFork); + uint256 cost = CCTPAndTokenBridgeToySource.quoteCrossChainDeposit( + targetChain + ); + + vm.recordLogs(); + CCTPAndTokenBridgeToySource.sendCrossChainDeposit{value: cost}( + targetChain, + recipient, + amount, + address(token) + ); + performDelivery(); + + vm.selectFork(targetFork); + address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset( + sourceChain, + toWormholeFormat(address(token)) + ); + assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount); + } +}