Skip to content

Commit

Permalink
Quark script to bridge using Across (#94)
Browse files Browse the repository at this point in the history
Implements a Quark script for bridging assets across chains using
Across.

---------

Co-authored-by: Coburn Berry <[email protected]>
  • Loading branch information
kevincheng96 and coburncoburn authored Nov 1, 2024
1 parent 5e42ad8 commit f3e00c4
Show file tree
Hide file tree
Showing 3 changed files with 605 additions and 0 deletions.
78 changes: 78 additions & 0 deletions src/AcrossScripts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.27;

import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";

import {IAcrossV3SpokePool} from "./interfaces/IAcrossV3SpokePool.sol";

contract AcrossActions {
// To handle non-standard ERC20 tokens (i.e. USDT)
using SafeERC20 for IERC20;

/**
* @notice Bridge an asset to the destination chain by depositing it into the Across v3 SpokePool
* @param spokePool The address of the Across v3 SpokePool contract
* @param depositor The account credited with the deposit
* @param recipient The account receiving funds on the destination chain. Can be an EOA or a contract. If
* the output token is the wrapped native token for the chain, then the recipient will receive native token if
* an EOA or wrapped native token if a contract
* @param inputToken The token pulled from the caller's account and locked into this contract to
* initiate the deposit. If this is equal to the wrapped native token then the caller can optionally pass in native
* token as msg.value, as long as msg.value = inputTokenAmount
* @param outputToken The token that the relayer will send to the recipient on the destination chain. Must be an
* ERC20
* @param inputAmount The amount of input tokens to pull from the caller's account and lock into this contract
* @param outputAmount The amount of output tokens that the relayer will send to the recipient on the destination
* @param destinationChainId The destination chain identifier
* @param exclusiveRelayer The relayer that will be exclusively allowed to fill this deposit before the
* exclusivity deadline timestamp. This must be a valid, non-zero address if the exclusivity deadline is
* greater than the current block.timestamp. If the exclusivity deadline is < currentTime, then this must be
* address(0), and vice versa if this is address(0)
* @param quoteTimestamp The HubPool timestamp that is used to determine the system fee paid by the depositor.
* This must be set to some time between [currentTime - depositQuoteTimeBuffer, currentTime]
* where currentTime is block.timestamp on this chain or this transaction will revert
* @param fillDeadline The deadline for the relayer to fill the deposit. After this destination chain timestamp,
* the fill will revert on the destination chain. Must be set between [currentTime, currentTime + fillDeadlineBuffer]
* where currentTime is block.timestamp on this chain or this transaction will revert
* @param exclusivityDeadline The deadline for the exclusive relayer to fill the deposit. After this
* destination chain timestamp, anyone can fill this deposit on the destination chain. If exclusiveRelayer is set
* to address(0), then this also must be set to 0, (and vice versa), otherwise this must be set >= current time.
* @param message The message to send to the recipient on the destination chain if the recipient is a contract
* If the message is not empty, the recipient contract must implement handleV3AcrossMessage() or the fill will revert
* @param useNativeToken Whether or not the native token (e.g. ETH) should be used as the input token
*/
function depositV3(
address spokePool,
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,
bool useNativeToken
) external payable {
IERC20(inputToken).forceApprove(spokePool, inputAmount);

IAcrossV3SpokePool(spokePool).depositV3{value: useNativeToken ? inputAmount : 0}({
depositor: depositor,
recipient: recipient,
inputToken: inputToken,
outputToken: outputToken,
inputAmount: inputAmount,
outputAmount: outputAmount,
destinationChainId: destinationChainId,
exclusiveRelayer: exclusiveRelayer,
quoteTimestamp: quoteTimestamp,
fillDeadline: fillDeadline,
exclusivityDeadline: exclusivityDeadline,
message: message
});
}
}
247 changes: 247 additions & 0 deletions src/interfaces/IAcrossV3SpokePool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.27;

/// @dev Interface for Across v3 Spoke Pool
/// Reference: https://github.com/across-protocol/contracts/blob/master/contracts/interfaces/V3SpokePoolInterface.sol

// Contains structs and functions used by SpokePool contracts to facilitate universal settlement.
interface IAcrossV3SpokePool {
/**
*
* 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();
error WrongERC7683OrderId();
error LowLevelCallFailed(bytes data);
}
Loading

0 comments on commit f3e00c4

Please sign in to comment.