From 713db56bf84e939d49239e36e58975e23f1cc065 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 3 Oct 2024 22:13:03 -0700 Subject: [PATCH] pay contract: across bridger --- .../contract/src/DaimoPayAcrossBridger.sol | 105 ++++++++ .../vendor/across/V3SpokePoolInterface.sol | 238 ++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 packages/contract/src/DaimoPayAcrossBridger.sol create mode 100644 packages/contract/vendor/across/V3SpokePoolInterface.sol diff --git a/packages/contract/src/DaimoPayAcrossBridger.sol b/packages/contract/src/DaimoPayAcrossBridger.sol new file mode 100644 index 000000000..dbfdb40e0 --- /dev/null +++ b/packages/contract/src/DaimoPayAcrossBridger.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.12; + +import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import "openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; + +import "./interfaces/IDaimoPayBridger.sol"; +import "./DaimoFastCCTP.sol"; +import "../vendor/across/V3SpokePoolInterface.sol"; + +/// @title Bridger implementation for Across Protocol +/// @author The Daimo team +/// @custom:security-contact security@daimo.com +/// +/// Bridges assets from to a destination chain using Across Protocol. +contract DaimoPayAcrossBridger is + IDaimoPayBridger, + Ownable2StepUpgradeable, + UUPSUpgradeable +{ + using SafeERC20 for IERC20; + + // SpokePool contract address for this chain. + V3SpokePoolInterface public immutable spokePool; + + event BridgeInitiated( + address indexed sender, + IERC20 indexed tokenIn, + uint256 amountIn, + uint256 toChainID + ); + + constructor() { + _disableInitializers(); + } + + // ----- ADMIN FUNCTIONS ----- + + /// Initialize. Specify owner (not msg.sender) to allow CREATE3 deployment. + function init( + address _initialOwner, + V3SpokePoolInterface _spokePool + ) public initializer { + __Ownable_init(_initialOwner); + + spokePool = _spokePool; + } + + /// UUPSUpsgradeable: only allow owner to upgrade + function _authorizeUpgrade(address) internal view override onlyOwner {} + + /// UUPSUpgradeable: expose implementation + function implementation() public view returns (address) { + return ERC1967Utils.getImplementation(); + } + + // ----- PUBLIC FUNCTIONS ----- + + /// Initiates a bridge to a destination chain using Across Protocol. + function sendToChain( + IERC20 tokenIn, + uint256 amountIn, + uint256 toChainID, + bytes calldata extraData + ) public { + // Parse remaining arguments from extraData + ( + address outputToken, + uint256 outputAmount, + address exclusiveRelayer, + uint32 quoteTimestamp, + uint32 fillDeadline, + uint32 exclusivityDeadline, + bytes memory message + ) = abi.decode(extraData, (address, uint256, address, uint32, uint32, uint32, bytes)); + + // Move input token from caller to this contract and approve the + // SpokePool contract. + tokenIn.safeTransferFrom({ + from: msg.sender, + to: address(this), + value: amountIn + }); + tokenIn.forceApprove({spender: address(spokePool), value: amountIn}); + + spokePool.depositV3({ + depositor: address(this), + recipient: msg.sender, + inputToken: address(tokenIn), + outputToken: outputToken, + inputAmount: amountIn, + outputAmount: outputAmount, + destinationChainId: toChainID, + exclusiveRelayer: exclusiveRelayer, + quoteTimestamp: quoteTimestamp, + fillDeadline: fillDeadline, + exclusivityDeadline: exclusivityDeadline, + message: message + }); + + emit BridgeInitiated(msg.sender, tokenIn, amountIn, toChainID); + } +} diff --git a/packages/contract/vendor/across/V3SpokePoolInterface.sol b/packages/contract/vendor/across/V3SpokePoolInterface.sol new file mode 100644 index 000000000..abe7a197a --- /dev/null +++ b/packages/contract/vendor/across/V3SpokePoolInterface.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +// Contains structs and functions used by SpokePool contracts to facilitate universal settlement. +interface V3SpokePoolInterface { + /************************************** + * ENUMS * + **************************************/ + + // Fill status tracks on-chain state of deposit, uniquely identified by relayHash. + enum FillStatus { + Unfilled, + RequestedSlowFill, + Filled + } + // Fill type is emitted in the FilledRelay event to assist Dataworker with determining which types of + // fills to refund (e.g. only fast fills) and whether a fast fill created a sow fill excess. + enum FillType { + FastFill, + // Fast fills are normal fills that do not replace a slow fill request. + ReplacedSlowFill, + // Replaced slow fills are fast fills that replace a slow fill request. This type is used by the Dataworker + // to know when to send excess funds from the SpokePool to the HubPool because they can no longer be used + // for a slow fill execution. + SlowFill + // Slow fills are requested via requestSlowFill and executed by executeSlowRelayLeaf after a bundle containing + // the slow fill is validated. + } + + /************************************** + * STRUCTS * + **************************************/ + + // This struct represents the data to fully specify a **unique** relay submitted on this chain. + // This data is hashed with the chainId() and saved by the SpokePool to prevent collisions and protect against + // replay attacks on other chains. If any portion of this data differs, the relay is considered to be + // completely distinct. + struct V3RelayData { + // The address that made the deposit on the origin chain. + address depositor; + // The recipient address on the destination chain. + address recipient; + // This is the exclusive relayer who can fill the deposit before the exclusivity deadline. + address exclusiveRelayer; + // Token that is deposited on origin chain by depositor. + address inputToken; + // Token that is received on destination chain by recipient. + address outputToken; + // The amount of input token deposited by depositor. + uint256 inputAmount; + // The amount of output token to be received by recipient. + uint256 outputAmount; + // Origin chain id. + uint256 originChainId; + // The id uniquely identifying this deposit on the origin chain. + uint32 depositId; + // The timestamp on the destination chain after which this deposit can no longer be filled. + uint32 fillDeadline; + // The timestamp on the destination chain after which any relayer can fill the deposit. + uint32 exclusivityDeadline; + // Data that is forwarded to the recipient. + bytes message; + } + + // Contains parameters passed in by someone who wants to execute a slow relay leaf. + struct V3SlowFill { + V3RelayData relayData; + uint256 chainId; + uint256 updatedOutputAmount; + } + + // Contains information about a relay to be sent along with additional information that is not unique to the + // relay itself but is required to know how to process the relay. For example, "updatedX" fields can be used + // by the relayer to modify fields of the relay with the depositor's permission, and "repaymentChainId" is specified + // by the relayer to determine where to take a relayer refund, but doesn't affect the uniqueness of the relay. + struct V3RelayExecutionParams { + V3RelayData relay; + bytes32 relayHash; + uint256 updatedOutputAmount; + address updatedRecipient; + bytes updatedMessage; + uint256 repaymentChainId; + } + + // Packs together parameters emitted in FilledV3Relay because there are too many emitted otherwise. + // Similar to V3RelayExecutionParams, these parameters are not used to uniquely identify the deposit being + // filled so they don't have to be unpacked by all clients. + struct V3RelayExecutionEventInfo { + address updatedRecipient; + bytes updatedMessage; + uint256 updatedOutputAmount; + FillType fillType; + } + + /************************************** + * EVENTS * + **************************************/ + + event V3FundsDeposited( + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 indexed destinationChainId, + uint32 indexed depositId, + uint32 quoteTimestamp, + uint32 fillDeadline, + uint32 exclusivityDeadline, + address indexed depositor, + address recipient, + address exclusiveRelayer, + bytes message + ); + + event RequestedSpeedUpV3Deposit( + uint256 updatedOutputAmount, + uint32 indexed depositId, + address indexed depositor, + address updatedRecipient, + bytes updatedMessage, + bytes depositorSignature + ); + + event FilledV3Relay( + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 repaymentChainId, + uint256 indexed originChainId, + uint32 indexed depositId, + uint32 fillDeadline, + uint32 exclusivityDeadline, + address exclusiveRelayer, + address indexed relayer, + address depositor, + address recipient, + bytes message, + V3RelayExecutionEventInfo relayExecutionInfo + ); + + event RequestedV3SlowFill( + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 indexed originChainId, + uint32 indexed depositId, + uint32 fillDeadline, + uint32 exclusivityDeadline, + address exclusiveRelayer, + address depositor, + address recipient, + bytes message + ); + + /************************************** + * FUNCTIONS * + **************************************/ + + 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; + + function depositV3Now( + address depositor, + address recipient, + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 destinationChainId, + address exclusiveRelayer, + uint32 fillDeadlineOffset, + uint32 exclusivityDeadline, + bytes calldata message + ) external payable; + + function speedUpV3Deposit( + address depositor, + uint32 depositId, + uint256 updatedOutputAmount, + address updatedRecipient, + bytes calldata updatedMessage, + bytes calldata depositorSignature + ) external; + + function fillV3Relay(V3RelayData calldata relayData, uint256 repaymentChainId) external; + + function fillV3RelayWithUpdatedDeposit( + V3RelayData calldata relayData, + uint256 repaymentChainId, + uint256 updatedOutputAmount, + address updatedRecipient, + bytes calldata updatedMessage, + bytes calldata depositorSignature + ) external; + + function requestV3SlowFill(V3RelayData calldata relayData) external; + + function executeV3SlowRelayLeaf( + V3SlowFill calldata slowFillLeaf, + uint32 rootBundleId, + bytes32[] calldata proof + ) external; + + /************************************** + * ERRORS * + **************************************/ + + error DisabledRoute(); + error InvalidQuoteTimestamp(); + error InvalidFillDeadline(); + error InvalidExclusiveRelayer(); + error InvalidExclusivityDeadline(); + error MsgValueDoesNotMatchInputAmount(); + error NotExclusiveRelayer(); + error NoSlowFillsInExclusivityWindow(); + error RelayFilled(); + error InvalidSlowFillRequest(); + error ExpiredFillDeadline(); + error InvalidMerkleProof(); + error InvalidChainId(); + error InvalidMerkleLeaf(); + error ClaimedMerkleLeaf(); + error InvalidPayoutAdjustmentPct(); +} \ No newline at end of file