diff --git a/contracts/wrappers/AcrossIntentWrapper.sol b/contracts/wrappers/AcrossIntentWrapper.sol new file mode 100644 index 0000000..61cc0a1 --- /dev/null +++ b/contracts/wrappers/AcrossIntentWrapper.sol @@ -0,0 +1,68 @@ +pragma solidity ^0.8.27; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface AcrossV3SpokePool { + function depositV3( + address depositor, + address recipient, + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 destinationChainId, + address exclusiveRelayer, + uint32 quoteTimestamp, + uint32 fillDeadline, + uint32 exclusivityDeadline, + bytes calldata message + ) external payable; +} + +contract AcrossIntentWrapper { + error InputAmountMismatch(uint256 balance, uint256 inputAmount); + + function depositV3Composable ( + address pool, // pool to call + address depositor, + address recipient, + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputRatioPacked, // MS 128bytes: output ratio = (quotedInputAmount * OUTPUT_RATIO_PRECISION) / quotedOutputAmount ; LS 128bytes: OUTPUT_RATIO_PRECISION + uint256 destinationChainId, + address exclusiveRelayer, + uint32 quoteTimestamp, + uint32 fillDeadline, + uint32 exclusivityDeadline, + bytes calldata message + ) external payable { + // 1. Approve input amount to the pool + uint256 balance = IERC20(inputToken).balanceOf(address(this)); + require (balance == inputAmount, InputAmountMismatch(balance, inputAmount)); + IERC20(inputToken).approve(pool, inputAmount); + + // 2. Unpack the output ratio + uint256 outputRatio = outputRatioPacked >> 128; + uint256 outputRatioPrecision = outputRatioPacked & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff; + + // 3. Get the output amount + uint256 outputAmount = inputAmount * outputRatio / outputRatioPrecision; + + // 4. Call the pool with the runtime amount + AcrossV3SpokePool(pool).depositV3{value: msg.value}( + depositor, + recipient, + inputToken, + outputToken, + inputAmount, + outputAmount, + destinationChainId, + exclusiveRelayer, + quoteTimestamp, + fillDeadline, + exclusivityDeadline, + message + ); + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 4643c6d..0ffde8d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -34,5 +34,10 @@ tab_width = 4 wrap_comments = true +[rpc_endpoints] + optimism = "${OPTIMISM_MAINNET_RPC_URL}" + optimism-sepolia = "${OPTIMISM_SEPOLIA_RPC_URL}" + base = "${BASE_RPC_URL}" + base-sepolia = "${BASE_SEPOLIA_RPC_URL}" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/scripts/foundry-deploy/DeployAcrossIntentWrapper.s.sol b/scripts/foundry-deploy/DeployAcrossIntentWrapper.s.sol new file mode 100644 index 0000000..cb91817 --- /dev/null +++ b/scripts/foundry-deploy/DeployAcrossIntentWrapper.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {AcrossIntentWrapper} from "../../contracts/wrappers/AcrossIntentWrapper.sol"; +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; + +contract DeployAcrossIntentWrapper is Script { + + function setUp() public { + + } + + function run() public { + vm.startBroadcast(); + + AcrossIntentWrapper acrossIntentWrapper = new AcrossIntentWrapper(); + + vm.stopBroadcast(); + } +} + +// OP mainnet: 0xB9F82b52Afde09D0E2CC0748a66D1Df76C18A1c7 (with emit) \ No newline at end of file