diff --git a/.gas-snapshot b/.gas-snapshot index 9d84617e..8d99b218 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,6 +1,7 @@ ApproveAndSwapTest:testSwap() (gas: 285500) ApproveAndSwapTest:testSwapFailsIfWeExpectedTooMuch() (gas: 364666) ApproveAndSwapTest:testSwapFailsWithNoApproval() (gas: 122231) +CCTPBridge:testBridgeToBase() (gas: 146874) CometClaimRewardsTest:testClaimComp() (gas: 131265) CometRepayAndWithdrawMultipleAssetsTest:testInvalidInput() (gas: 68171) CometRepayAndWithdrawMultipleAssetsTest:testRepayAndWithdrawMultipleAssets() (gas: 153860) diff --git a/src/BridgeScripts.sol b/src/BridgeScripts.sol new file mode 100644 index 00000000..cd223c08 --- /dev/null +++ b/src/BridgeScripts.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; +import {ITokenMessenger} from "./interfaces/ITokenMessenger.sol"; + +contract CCTPBridgeActions { + function bridgeUSDC( + address tokenMessenger, + uint256 amount, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken + ) external { + IERC20(burnToken).approve(tokenMessenger, amount); + ITokenMessenger(tokenMessenger).depositForBurn(amount, destinationDomain, mintRecipient, burnToken); + } +} diff --git a/src/interfaces/ITokenMessenger.sol b/src/interfaces/ITokenMessenger.sol new file mode 100644 index 00000000..99c94ed1 --- /dev/null +++ b/src/interfaces/ITokenMessenger.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +interface ITokenMessenger { + function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) + external + returns (uint64 _nonce); +} diff --git a/test/CCTPBridgeActions.t.sol b/test/CCTPBridgeActions.t.sol new file mode 100644 index 00000000..933d528c --- /dev/null +++ b/test/CCTPBridgeActions.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "forge-std/StdUtils.sol"; +import "forge-std/StdMath.sol"; + +import {CodeJar} from "codejar/src/CodeJar.sol"; + +import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; +import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; + +import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; + +import {YulHelper} from "./lib/YulHelper.sol"; +import {SignatureHelper} from "./lib/SignatureHelper.sol"; +import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; + +import "src/BridgeScripts.sol"; + +/** + * Tests for CCTP Bridge + */ +contract CCTPBridge is Test { + QuarkWalletProxyFactory public factory; + uint256 alicePrivateKey = 0xa11ce; + address alice = vm.addr(alicePrivateKey); + + // Contracts address on mainnet + address constant tokenMessenger = 0xBd3fa81B58Ba92a82136038B25aDec7066af3155; + address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + // Domain value for the CCTP bridge, DIFFERENT from chain id + uint32 constant mainnetDomain = 0; + uint32 constant baseDomain = 6; + + function setUp() public { + // Fork setup + vm.createSelectFork( + string.concat( + "https://node-provider.compound.finance/ethereum-mainnet/", vm.envString("NODE_PROVIDER_BYPASS_KEY") + ), + 18429607 // 2023-10-25 13:24:00 PST + ); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + } + + function testBridgeToBase() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + bytes memory cctpBridgeScript = new YulHelper().getCode("BridgeScripts.sol/CCTPBridgeActions.json"); + + deal(USDC, address(wallet), 1_000_000e6); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + cctpBridgeScript, + abi.encodeCall( + CCTPBridgeActions.bridgeUSDC, + (tokenMessenger, 500_000e6, 6, bytes32(uint256(uint160(address(wallet)))), USDC) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + + assertEq(IERC20(USDC).balanceOf(address(wallet)), 1_000_000e6); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 500_000e6); + } +}