Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 108 additions & 72 deletions contracts/SpokePool.sol

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions contracts/SpokePoolVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Address.sol";
import "./interfaces/V3SpokePoolInterface.sol";
import "./libraries/AddressConverters.sol";

/**
* @notice SpokePoolVerifier is a contract that verifies that the SpokePool exists on this chain before sending ETH to it.
Expand All @@ -14,6 +15,7 @@ import "./interfaces/V3SpokePoolInterface.sol";
*/
contract SpokePoolVerifier {
using Address for address;
using AddressToBytes32 for address;

error InvalidMsgValue();
error InvalidSpokePool();
Expand Down Expand Up @@ -42,12 +44,12 @@ contract SpokePoolVerifier {
*/
function deposit(
V3SpokePoolInterface spokePool,
address recipient,
address inputToken,
bytes32 recipient,
bytes32 inputToken,
uint256 inputAmount,
uint256 outputAmount,
uint256 destinationChainId,
address exclusiveRelayer,
bytes32 exclusiveRelayer,
uint32 quoteTimestamp,
uint32 fillDeadline,
uint32 exclusivityDeadline,
Expand All @@ -57,12 +59,12 @@ contract SpokePoolVerifier {
if (!address(spokePool).isContract()) revert InvalidSpokePool();
// Set msg.sender as the depositor so that msg.sender can speed up the deposit.
spokePool.depositV3{ value: msg.value }(
msg.sender,
msg.sender.toBytes32(),
recipient,
inputToken,
// @dev Setting outputToken to 0x0 to instruct fillers to use the equivalent token
// as the originToken on the destination chain.
address(0),
bytes32(0),
inputAmount,
outputAmount,
destinationChainId,
Expand Down
68 changes: 38 additions & 30 deletions contracts/SwapAndBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./Lockable.sol";
import "@uma/core/contracts/common/implementation/MultiCaller.sol";
import "./libraries/AddressConverters.sol";

/**
* @title SwapAndBridgeBase
Expand All @@ -14,6 +15,8 @@ import "@uma/core/contracts/common/implementation/MultiCaller.sol";
*/
abstract contract SwapAndBridgeBase is Lockable, MultiCaller {
using SafeERC20 for IERC20;
using AddressToBytes32 for address;
using Bytes32ToAddress for bytes32;

// This contract performs a low level call with arbirary data to an external contract. This is a large attack
// surface and we should whitelist which function selectors are allowed to be called on the exchange.
Expand All @@ -30,19 +33,19 @@ abstract contract SwapAndBridgeBase is Lockable, MultiCaller {
// until after the swap.
struct DepositData {
// Token received on destination chain.
address outputToken;
bytes32 outputToken;
// Amount of output token to be received by recipient.
uint256 outputAmount;
// The account credited with deposit who can submit speedups to the Across deposit.
address depositor;
bytes32 depositor;
// The account that will receive the output token on the destination chain. If the output token is
// wrapped native token, then if this is an EOA then they will receive native token on the destination
// chain and if this is a contract then they will receive an ERC20.
address recipient;
bytes32 recipient;
// The destination chain identifier.
uint256 destinationChainid;
// The account that can exclusively fill the deposit before the exclusivity deadline.
address exclusiveRelayer;
bytes32 exclusiveRelayer;
// Timestamp of the deposit used by system to charge fees. Must be within short window of time into the past
// relative to this chain's current time or deposit will revert.
uint32 quoteTimestamp;
Expand All @@ -56,17 +59,19 @@ abstract contract SwapAndBridgeBase is Lockable, MultiCaller {

event SwapBeforeBridge(
address exchange,
address indexed swapToken,
address indexed acrossInputToken,
bytes32 indexed swapToken,
bytes32 indexed acrossInputToken,
uint256 swapTokenAmount,
uint256 acrossInputAmount,
address indexed acrossOutputToken,
bytes32 indexed acrossOutputToken,
uint256 acrossOutputAmount
);

/****************************************
/**
*
* ERRORS *
****************************************/
*
*/
error MinimumExpectedInputAmount();
error LeftoverSrcTokens();
error InvalidFunctionSelector();
Expand Down Expand Up @@ -95,21 +100,21 @@ abstract contract SwapAndBridgeBase is Lockable, MultiCaller {
uint256 swapTokenAmount,
uint256 minExpectedInputTokenAmount,
DepositData calldata depositData,
IERC20 _swapToken,
IERC20 _acrossInputToken
bytes32 _swapToken,
bytes32 _acrossInputToken
) internal {
// Note: this check should never be impactful, but is here out of an abundance of caution.
// For example, if the exchange address in the contract is also an ERC20 token that is approved by some
// user on this contract, a malicious actor could call transferFrom to steal the user's tokens.
if (!allowedSelectors[bytes4(routerCalldata)]) revert InvalidFunctionSelector();

// Pull tokens from caller into this contract.
_swapToken.safeTransferFrom(msg.sender, address(this), swapTokenAmount);
IERC20(_swapToken.toAddress()).safeTransferFrom(msg.sender, address(this), swapTokenAmount);
// Swap and run safety checks.
uint256 srcBalanceBefore = _swapToken.balanceOf(address(this));
uint256 dstBalanceBefore = _acrossInputToken.balanceOf(address(this));
uint256 srcBalanceBefore = IERC20(_swapToken.toAddress()).balanceOf(address(this));
uint256 dstBalanceBefore = IERC20(_acrossInputToken.toAddress()).balanceOf(address(this));

_swapToken.safeIncreaseAllowance(EXCHANGE, swapTokenAmount);
IERC20(_swapToken.toAddress()).safeIncreaseAllowance(EXCHANGE, swapTokenAmount);
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = EXCHANGE.call(routerCalldata);
require(success, string(result));
Expand All @@ -131,39 +136,42 @@ abstract contract SwapAndBridgeBase is Lockable, MultiCaller {
* @param swapTokenBalanceBefore Balance of swapToken before swap.
* @param inputTokenBalanceBefore Amount of Across input token we held before swap
* @param minExpectedInputTokenAmount Minimum amount of received acrossInputToken that we'll bridge
**/
*
*/
function _checkSwapOutputAndDeposit(
uint256 swapTokenAmount,
uint256 swapTokenBalanceBefore,
uint256 inputTokenBalanceBefore,
uint256 minExpectedInputTokenAmount,
DepositData calldata depositData,
IERC20 _swapToken,
IERC20 _acrossInputToken
bytes32 _swapToken,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is not your change, but its funny to me that these two variables are _ and the others are not.

bytes32 _acrossInputToken
) internal {
// Sanity check that we received as many tokens as we require:
uint256 returnAmount = _acrossInputToken.balanceOf(address(this)) - inputTokenBalanceBefore;
uint256 returnAmount = IERC20(_acrossInputToken.toAddress()).balanceOf(address(this)) - inputTokenBalanceBefore;
// Sanity check that received amount from swap is enough to submit Across deposit with.
if (returnAmount < minExpectedInputTokenAmount) revert MinimumExpectedInputAmount();
// Sanity check that we don't have any leftover swap tokens that would be locked in this contract (i.e. check
// that we weren't partial filled).
if (swapTokenBalanceBefore - _swapToken.balanceOf(address(this)) != swapTokenAmount) revert LeftoverSrcTokens();
if (swapTokenBalanceBefore - IERC20(_swapToken.toAddress()).balanceOf(address(this)) != swapTokenAmount) {
revert LeftoverSrcTokens();
}

emit SwapBeforeBridge(
EXCHANGE,
address(_swapToken),
address(_acrossInputToken),
_swapToken,
_acrossInputToken,
swapTokenAmount,
returnAmount,
depositData.outputToken,
depositData.outputAmount
);
// Deposit the swapped tokens into Across and bridge them using remainder of input params.
_acrossInputToken.safeIncreaseAllowance(address(SPOKE_POOL), returnAmount);
IERC20(_acrossInputToken.toAddress()).safeIncreaseAllowance(address(SPOKE_POOL), returnAmount);
SPOKE_POOL.depositV3(
depositData.depositor,
depositData.recipient,
address(_acrossInputToken), // input token
_acrossInputToken, // input token
depositData.outputToken, // output token
returnAmount, // input amount.
depositData.outputAmount, // output amount
Expand All @@ -189,10 +197,10 @@ contract SwapAndBridge is SwapAndBridgeBase {
// This contract simply enables the caller to swap a token on this chain for another specified one
// and bridge it as the input token via Across. This simplification is made to make the code
// easier to reason about and solve a specific use case for Across.
IERC20 public immutable SWAP_TOKEN;
bytes32 public immutable SWAP_TOKEN;

// The token that will be bridged via Across as the inputToken.
IERC20 public immutable ACROSS_INPUT_TOKEN;
bytes32 public immutable ACROSS_INPUT_TOKEN;

/**
* @notice Construct a new SwapAndBridge contract.
Expand All @@ -206,8 +214,8 @@ contract SwapAndBridge is SwapAndBridgeBase {
V3SpokePoolInterface _spokePool,
address _exchange,
bytes4[] memory _allowedSelectors,
IERC20 _swapToken,
IERC20 _acrossInputToken
bytes32 _swapToken,
bytes32 _acrossInputToken
) SwapAndBridgeBase(_spokePool, _exchange, _allowedSelectors) {
SWAP_TOKEN = _swapToken;
ACROSS_INPUT_TOKEN = _acrossInputToken;
Expand Down Expand Up @@ -275,8 +283,8 @@ contract UniversalSwapAndBridge is SwapAndBridgeBase {
* @param depositData Specifies the Across deposit params we'll send after the swap.
*/
function swapAndBridge(
IERC20 swapToken,
IERC20 acrossInputToken,
bytes32 swapToken,
bytes32 acrossInputToken,
bytes calldata routerCalldata,
uint256 swapTokenAmount,
uint256 minExpectedInputTokenAmount,
Expand Down
10 changes: 5 additions & 5 deletions contracts/erc7683/ERC7683.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
/// @notice Tokens sent by the swapper as inputs to the order
struct Input {
/// @dev The address of the ERC20 token on the origin chain
address token;
bytes32 token;
/// @dev The amount of the token to be sent
uint256 amount;
}
Expand All @@ -13,11 +13,11 @@ struct Input {
struct Output {
/// @dev The address of the ERC20 token on the destination chain
/// @dev address(0) used as a sentinel for the native token
address token;
bytes32 token;
/// @dev The amount of the token to be sent
uint256 amount;
/// @dev The address to receive the output tokens
address recipient;
bytes32 recipient;
/// @dev The destination chain for this output
uint32 chainId;
}
Expand All @@ -30,7 +30,7 @@ struct CrossChainOrder {
address settlementContract;
/// @dev The address of the user who is initiating the swap,
/// whose input tokens will be taken and escrowed
address swapper;
bytes32 swapper;
/// @dev Nonce to be used as replay protection for the order
uint256 nonce;
/// @dev The chainId of the origin chain
Expand All @@ -53,7 +53,7 @@ struct ResolvedCrossChainOrder {
/// @dev The contract address that the order is meant to be settled by.
address settlementContract;
/// @dev The address of the user who is initiating the swap
address swapper;
bytes32 swapper;
/// @dev Nonce to be used as replay protection for the order
uint256 nonce;
/// @dev The chainId of the origin chain
Expand Down
8 changes: 4 additions & 4 deletions contracts/erc7683/ERC7683Across.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import { CrossChainOrder } from "./ERC7683.sol";

// Data unique to every CrossChainOrder settled on Across
struct AcrossOrderData {
address inputToken;
bytes32 inputToken;
uint256 inputAmount;
address outputToken;
bytes32 outputToken;
uint256 outputAmount;
uint32 destinationChainId;
address recipient;
bytes32 recipient;
uint32 exclusivityDeadlineOffset;
bytes message;
}

struct AcrossFillerData {
address exclusiveRelayer;
bytes32 exclusiveRelayer;
}

/**
Expand Down
17 changes: 10 additions & 7 deletions contracts/erc7683/ERC7683OrderDepositor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { Input, Output, CrossChainOrder, ResolvedCrossChainOrder, ISettlementContract } from "./ERC7683.sol";
import { AcrossOrderData, AcrossFillerData, ERC7683Permit2Lib } from "./ERC7683Across.sol";
import { Bytes32ToAddress } from "../libraries/AddressConverters.sol";

/**
* @notice ERC7683OrderDepositor processes an external order type and translates it into an AcrossV3 deposit.
Expand All @@ -18,6 +19,8 @@ abstract contract ERC7683OrderDepositor is ISettlementContract {
error WrongSettlementContract();
error WrongChainId();

using Bytes32ToAddress for bytes32;

// Permit2 contract for this network.
IPermit2 public immutable PERMIT2;

Expand Down Expand Up @@ -169,7 +172,7 @@ abstract contract ERC7683OrderDepositor is ISettlementContract {
) internal {
IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: acrossOrderData.inputToken,
token: acrossOrderData.inputToken.toAddress(),
amount: acrossOrderData.inputAmount
}),
nonce: order.nonce,
Expand All @@ -185,22 +188,22 @@ abstract contract ERC7683OrderDepositor is ISettlementContract {
PERMIT2.permitWitnessTransferFrom(
permit,
signatureTransferDetails,
order.swapper,
order.swapper.toAddress(),
ERC7683Permit2Lib.hashOrder(order, ERC7683Permit2Lib.hashOrderData(acrossOrderData)), // witness data hash
ERC7683Permit2Lib.PERMIT2_ORDER_TYPE, // witness data type string
signature
);
}

function _callDeposit(
address depositor,
address recipient,
address inputToken,
address outputToken,
bytes32 depositor,
bytes32 recipient,
bytes32 inputToken,
bytes32 outputToken,
uint256 inputAmount,
uint256 outputAmount,
uint256 destinationChainId,
address exclusiveRelayer,
bytes32 exclusiveRelayer,
uint32 quoteTimestamp,
uint32 fillDeadline,
uint32 exclusivityDeadline,
Expand Down
15 changes: 9 additions & 6 deletions contracts/erc7683/ERC7683OrderDepositorExternal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ERC7683OrderDepositor } from "./ERC7683OrderDepositor.sol";
import "../interfaces/V3SpokePoolInterface.sol";
import "../external/interfaces/IPermit2.sol";
import { Bytes32ToAddress } from "../libraries/AddressConverters.sol";

/**
* @notice ERC7683OrderDepositorExternal processes an external order type and translates it into an AcrossV3Deposit
Expand All @@ -13,6 +14,8 @@ import "../external/interfaces/IPermit2.sol";
*/
contract ERC7683OrderDepositorExternal is ERC7683OrderDepositor {
using SafeERC20 for IERC20;
using Bytes32ToAddress for bytes32;

V3SpokePoolInterface public immutable SPOKE_POOL;

constructor(
Expand All @@ -24,20 +27,20 @@ contract ERC7683OrderDepositorExternal is ERC7683OrderDepositor {
}

function _callDeposit(
address depositor,
address recipient,
address inputToken,
address outputToken,
bytes32 depositor,
bytes32 recipient,
bytes32 inputToken,
bytes32 outputToken,
uint256 inputAmount,
uint256 outputAmount,
uint256 destinationChainId,
address exclusiveRelayer,
bytes32 exclusiveRelayer,
uint32 quoteTimestamp,
uint32 fillDeadline,
uint32 exclusivityDeadline,
bytes memory message
) internal override {
IERC20(inputToken).safeIncreaseAllowance(address(SPOKE_POOL), inputAmount);
IERC20(inputToken.toAddress()).safeIncreaseAllowance(address(SPOKE_POOL), inputAmount);

SPOKE_POOL.depositV3(
depositor,
Expand Down
Loading
Loading