Skip to content

Commit

Permalink
Merge branch 'master' into svm-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
md0x committed Nov 7, 2024
2 parents 3e7cb6c + d7550b8 commit 18df9a0
Show file tree
Hide file tree
Showing 22 changed files with 804 additions and 393 deletions.
46 changes: 34 additions & 12 deletions contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,20 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
// The Arbitrum Inbox requires that this is specified in gWei (e.g. 1e9 = 1 gWei)
uint256 public immutable L2_GAS_PRICE;

Check warning on line 52 in contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

Check warning on line 52 in contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

// Native token expected to be sent in L2 message. Should be 0 for all use cases of this constant, which
// includes sending messages from L1 to L2 and sending Custom gas token ERC20's, which won't be the native token
// on the L2 by definition.
// Native token expected to be sent in L2 message. Should be 0 for most use cases of this constant. This
// constant is unused when sending the native gas token over the inbox since the inbox interprets `l2CallValue`
// as the amount of the L2 native token to send.
uint256 public constant L2_CALL_VALUE = 0;

// Gas limit for L2 execution of a cross chain token transfer sent via the inbox.
uint32 public constant RELAY_TOKENS_L2_GAS_LIMIT = 300_000;
// Gas limit for L2 execution of a message sent via the inbox.
uint32 public constant RELAY_MESSAGE_L2_GAS_LIMIT = 2_000_000;

// The number of decimals of precision for the custom gas token. This is defined in the constructor and not dynamically fetched since decimals are
// not part of the standard ERC20 interface.
uint8 public immutable NATIVE_TOKEN_DECIMALS;

Check warning on line 66 in contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

Check warning on line 66 in contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

// This address on L2 receives extra gas token that is left over after relaying a message via the inbox.
address public immutable L2_REFUND_L2_ADDRESS;

Check warning on line 69 in contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

Check warning on line 69 in contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

Expand All @@ -83,31 +87,35 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter

error InvalidCustomGasToken();
error InsufficientCustomGasToken();
error InvalidNativeTokenDecimals();

/**
* @notice Constructs new Adapter.
* @param _l1ArbitrumInbox Inbox helper contract to send messages to Arbitrum.
* @param _l1ERC20GatewayRouter ERC20 gateway router contract to send tokens to Arbitrum.
* @param _l2RefundL2Address L2 address to receive gas refunds on after a message is relayed.
* @param _l1Usdc USDC address on L1.
* @param _l2MaxSubmissionCost Max gas deducted from user's L2 balance to cover base fee.
* @param _l2GasPrice Gas price bid for L2 execution. Should be set conservatively high to avoid stuck messages.
* @param _cctpTokenMessenger TokenMessenger contract to bridge via CCTP.
* @param _cctpDomainId Circle CCTP domain identifier for the destination chain.
* @param _nativeTokenDecimals Number of decimals corresponding to the L2's gas/fee token.
* @param _customGasTokenFunder Contract that funds the custom gas token.
* @param _l2MaxSubmissionCost Amount of gas token allocated to pay for the base submission fee. The base
* submission fee is a parameter unique to Arbitrum retryable transactions. This value is hardcoded
* and used for all messages sent by this adapter.
* @param _l2GasPrice Gas price bid for L2 execution. Should be set conservatively high to avoid stuck messages.
*/
constructor(
ArbitrumL1InboxLike _l1ArbitrumInbox,
ArbitrumL1ERC20GatewayLike _l1ERC20GatewayRouter,
address _l2RefundL2Address,
IERC20 _l1Usdc,
ICCTPTokenMessenger _cctpTokenMessenger,
uint32 _cctpDomainId,
uint8 _nativeTokenDecimals,
FunderInterface _customGasTokenFunder,
uint256 _l2MaxSubmissionCost,
uint256 _l2GasPrice
) CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, CircleDomainIds.Arbitrum) {
) CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, _cctpDomainId) {
L1_INBOX = _l1ArbitrumInbox;
L1_ERC20_GATEWAY_ROUTER = _l1ERC20GatewayRouter;
L2_REFUND_L2_ADDRESS = _l2RefundL2Address;
Expand All @@ -116,6 +124,8 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
L2_MAX_SUBMISSION_COST = _l2MaxSubmissionCost;
L2_GAS_PRICE = _l2GasPrice;
CUSTOM_GAS_TOKEN_FUNDER = _customGasTokenFunder;
if (_nativeTokenDecimals == 0) revert InvalidNativeTokenDecimals();
NATIVE_TOKEN_DECIMALS = _nativeTokenDecimals;
}

/**
Expand Down Expand Up @@ -175,9 +185,12 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
// amount and requiredL1TokenTotalFeeAmount are in the precision of the custom gas token.
uint256 amountToBridge = amount + requiredL1TokenTotalFeeAmount;
CUSTOM_GAS_TOKEN.safeIncreaseAllowance(address(L1_INBOX), amountToBridge);
// Both `l2CallValue` and `tokenTotalFeeAmount` are rounded in the conversion to/from native/18 decimals. `l2CallValue` is rounded down by the call to _fromNativeTo18Decimals()
// in cases where the token's decimals exceeds 18, since we would rather round down and be forced to donate a small amount of the l2 token to the spoke pool to cover the roundoff
// error than overshoot a transfer amount. `amountToBridge` is rounded up since we would rather overpay for gas and be refunded on l2 than underpay and risk stuck cross-chain messages.
L1_INBOX.createRetryableTicket(
to, // destAddr destination L2 contract address
L2_CALL_VALUE, // l2CallValue call value for retryable L2 message
_fromNativeTo18Decimals(amount), // l2CallValue call value for retryable L2 message
L2_MAX_SUBMISSION_COST, // maxSubmissionCost Max gas deducted from user's L2 balance to cover base fee
L2_REFUND_L2_ADDRESS, // excessFeeRefundAddress maxgas * gasprice - execution cost gets credited here on L2
L2_REFUND_L2_ADDRESS, // callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
Expand Down Expand Up @@ -226,12 +239,11 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
}

function _from18ToNativeDecimals(uint256 amount) internal view returns (uint256) {
uint8 nativeTokenDecimals = L1_INBOX.bridge().nativeTokenDecimals();
if (nativeTokenDecimals == 18) {
if (NATIVE_TOKEN_DECIMALS == 18) {
return amount;
} else if (nativeTokenDecimals < 18) {
} else if (NATIVE_TOKEN_DECIMALS < 18) {
// Round up the division result so that the L1 call value is always sufficient to cover the submission fee.
uint256 reductionFactor = 10**(18 - nativeTokenDecimals);
uint256 reductionFactor = 10**(18 - NATIVE_TOKEN_DECIMALS);
uint256 divFloor = amount / reductionFactor;
uint256 mod = amount % reductionFactor;
if (mod != 0) {
Expand All @@ -240,7 +252,17 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
return divFloor;
}
} else {
return amount * 10**(nativeTokenDecimals - 18);
return amount * 10**(NATIVE_TOKEN_DECIMALS - 18);
}
}

function _fromNativeTo18Decimals(uint256 amount) internal view returns (uint256) {
if (NATIVE_TOKEN_DECIMALS == 18) {
return amount;
} else if (NATIVE_TOKEN_DECIMALS < 18) {
return amount * 10**(18 - NATIVE_TOKEN_DECIMALS);
} else {
return amount / 10**(NATIVE_TOKEN_DECIMALS - 18);
}
}
}
8 changes: 5 additions & 3 deletions contracts/erc7683/ERC7683.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct GaslessCrossChainOrder {
/// @dev Nonce to be used as replay protection for the order
uint256 nonce;
/// @dev The chainId of the origin chain
uint64 originChainId;
uint256 originChainId;
/// @dev The timestamp by which the order must be opened
uint32 openDeadline;
/// @dev The timestamp by which the order must be filled on the destination chain
Expand Down Expand Up @@ -47,11 +47,13 @@ struct ResolvedCrossChainOrder {
/// @dev The address of the user who is initiating the transfer
bytes32 user;
/// @dev The chainId of the origin chain
uint64 originChainId;
uint256 originChainId;
/// @dev The timestamp by which the order must be opened
uint32 openDeadline;
/// @dev The timestamp by which the order must be filled on the destination chain(s)
uint32 fillDeadline;
/// @dev The unique identifier for this order within this settlement system
bytes32 orderId;
/// @dev The max outputs that the filler will send. It's possible the actual amount depends on the state of the destination
/// chain (destination dutch auction, for instance), so these outputs should be considered a cap on filler liabilities.
Output[] maxSpent;
Expand All @@ -74,7 +76,7 @@ struct Output {
/// @dev The address to receive the output tokens
bytes32 recipient;
/// @dev The destination chain for this output
uint64 chainId;
uint256 chainId;
}

/// @title FillInstruction type
Expand Down
2 changes: 1 addition & 1 deletion contracts/erc7683/ERC7683Across.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ library ERC7683Permit2Lib {
"bytes32 originSettler,",
"bytes32 user,",
"uint256 nonce,",
"uint32 originChainId,",
"uint256 originChainId,",
"uint32 openDeadline,",
"uint32 fillDeadline,",
"bytes32 orderDataType,",
Expand Down
63 changes: 33 additions & 30 deletions contracts/erc7683/ERC7683OrderDepositor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

import "../external/interfaces/IPermit2.sol";
import { V3SpokePoolInterface } from "../interfaces/V3SpokePoolInterface.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
Expand Down Expand Up @@ -218,23 +219,23 @@ abstract contract ERC7683OrderDepositor is IOriginSettler {
});

FillInstruction[] memory fillInstructions = new FillInstruction[](1);
V3SpokePoolInterface.V3RelayData memory relayData;
relayData.depositor = order.user;
relayData.recipient = acrossOrderData.recipient;
relayData.exclusiveRelayer = acrossOriginFillerData.exclusiveRelayer;
relayData.inputToken = acrossOrderData.inputToken;
relayData.outputToken = acrossOrderData.outputToken;
relayData.inputAmount = acrossOrderData.inputAmount;
relayData.outputAmount = acrossOrderData.outputAmount;
relayData.originChainId = block.chainid;
relayData.depositId = _currentDepositId();
relayData.fillDeadline = order.fillDeadline;
relayData.exclusivityDeadline = acrossOrderData.exclusivityPeriod;
relayData.message = acrossOrderData.message;
fillInstructions[0] = FillInstruction({
destinationChainId: acrossOrderData.destinationChainId,
destinationSettler: _destinationSettler(acrossOrderData.destinationChainId),
originData: abi.encode(
order.user,
acrossOrderData.recipient,
acrossOriginFillerData.exclusiveRelayer,
acrossOrderData.inputToken,
acrossOrderData.outputToken,
acrossOrderData.inputAmount,
acrossOrderData.outputAmount,
block.chainid,
_currentDepositId(),
order.fillDeadline,
acrossOrderData.exclusivityPeriod,
acrossOrderData.message
)
originData: abi.encode(relayData)
});

resolvedOrder = ResolvedCrossChainOrder({
Expand All @@ -244,7 +245,8 @@ abstract contract ERC7683OrderDepositor is IOriginSettler {
fillDeadline: order.fillDeadline,
minReceived: minReceived,
maxSpent: maxSpent,
fillInstructions: fillInstructions
fillInstructions: fillInstructions,
orderId: keccak256(abi.encode(relayData, acrossOrderData.destinationChainId))
});
}

Expand Down Expand Up @@ -281,23 +283,23 @@ abstract contract ERC7683OrderDepositor is IOriginSettler {
});

FillInstruction[] memory fillInstructions = new FillInstruction[](1);
V3SpokePoolInterface.V3RelayData memory relayData;
relayData.depositor = msg.sender.toBytes32();
relayData.recipient = acrossOrderData.recipient;
relayData.exclusiveRelayer = acrossOrderData.exclusiveRelayer;
relayData.inputToken = acrossOrderData.inputToken;
relayData.outputToken = acrossOrderData.outputToken;
relayData.inputAmount = acrossOrderData.inputAmount;
relayData.outputAmount = acrossOrderData.outputAmount;
relayData.originChainId = block.chainid;
relayData.depositId = _currentDepositId();
relayData.fillDeadline = order.fillDeadline;
relayData.exclusivityDeadline = acrossOrderData.exclusivityPeriod;
relayData.message = acrossOrderData.message;
fillInstructions[0] = FillInstruction({
destinationChainId: acrossOrderData.destinationChainId,
destinationSettler: _destinationSettler(acrossOrderData.destinationChainId),
originData: abi.encode(
msg.sender,
acrossOrderData.recipient,
acrossOrderData.exclusiveRelayer,
acrossOrderData.inputToken,
acrossOrderData.outputToken,
acrossOrderData.inputAmount,
acrossOrderData.outputAmount,
block.chainid,
_currentDepositId(),
order.fillDeadline,
acrossOrderData.exclusivityPeriod,
acrossOrderData.message
)
originData: abi.encode(relayData)
});

resolvedOrder = ResolvedCrossChainOrder({
Expand All @@ -307,7 +309,8 @@ abstract contract ERC7683OrderDepositor is IOriginSettler {
fillDeadline: order.fillDeadline,
minReceived: minReceived,
maxSpent: maxSpent,
fillInstructions: fillInstructions
fillInstructions: fillInstructions,
orderId: keccak256(abi.encode(relayData, acrossOrderData.destinationChainId))
});
}

Expand Down
16 changes: 16 additions & 0 deletions deploy/052_deploy_donation_box.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DeployFunction } from "hardhat-deploy/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployer } = await hre.getNamedAccounts();

await hre.deployments.deploy("DonationBox", {
contract: "DonationBox",
from: deployer,
log: true,
skipIfAlreadyDeployed: true,
args: [],
});
};
module.exports = func;
func.tags = ["DonationBox"];
35 changes: 35 additions & 0 deletions deploy/053_deploy_alephzero_adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { L1_ADDRESS_MAP, ZERO_ADDRESS, AZERO, ARBITRUM_MAX_SUBMISSION_COST, AZERO_GAS_PRICE } from "./consts";
import { DeployFunction } from "hardhat-deploy/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployer } = await hre.getNamedAccounts();
const chainId = parseInt(await hre.getChainId());

// This address receives gas refunds on the L2 after messages are relayed. Currently
// set to the Risk Labs relayer address. The deployer should change this if necessary.
const l2RefundAddress = "0x07aE8551Be970cB1cCa11Dd7a11F47Ae82e70E67";

const args = [
L1_ADDRESS_MAP[chainId].l1AlephZeroInbox,
L1_ADDRESS_MAP[chainId].l1AlephZeroERC20GatewayRouter,
l2RefundAddress,
ZERO_ADDRESS,
ZERO_ADDRESS,
4294967295, // No Circle CCTP domain ID, so it's set to (2**32)-1
AZERO.decimals,
L1_ADDRESS_MAP[chainId].donationBox,
ARBITRUM_MAX_SUBMISSION_COST,
AZERO_GAS_PRICE,
];
const instance = await hre.deployments.deploy("Arbitrum_CustomGasToken_Adapter", {
from: deployer,
log: true,
skipIfAlreadyDeployed: false,
args,
});
await hre.run("verify:verify", { address: instance.address, constructorArguments: args });
};

module.exports = func;
func.tags = ["ArbitrumCustomGasTokenAdapter", "mainnet"];
6 changes: 6 additions & 0 deletions deploy/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../utils";
export const USDC = TOKEN_SYMBOLS_MAP.USDC.addresses;
export const WETH = TOKEN_SYMBOLS_MAP.WETH.addresses;
export const WMATIC = TOKEN_SYMBOLS_MAP.WMATIC.addresses;
export const AZERO = TOKEN_SYMBOLS_MAP.AZERO;

export const QUOTE_TIME_BUFFER = 3600;
export const FILL_DEADLINE_BUFFER = 6 * 3600;
export const ARBITRUM_MAX_SUBMISSION_COST = "10000000000000000";
export const AZERO_GAS_PRICE = "240000000000";
export const MOCK_ADMIN = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D";

export const L1_ADDRESS_MAP: { [key: number]: { [contractName: string]: string } } = {
Expand Down Expand Up @@ -49,6 +52,9 @@ export const L1_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
zoraStandardBridge: "0x3e2Ea9B92B7E48A52296fD261dc26fd995284631",
worldChainCrossDomainMessenger: "0xf931a81D18B1766d15695ffc7c1920a62b7e710a",
worldChainStandardBridge: "0x470458C91978D2d929704489Ad730DC3E3001113",
l1AlephZeroInbox: "0x56D8EC76a421063e1907503aDd3794c395256AEb",
l1AlephZeroERC20GatewayRouter: "0xeBb17f398ed30d02F2e8733e7c1e5cf566e17812",
donationBox: "0x90285a96F5955A7279EF0C1e89A1B4f66d8E4dA7",
},
[CHAIN_IDs.SEPOLIA]: {
optimismCrossDomainMessenger: "0x58Cc85b8D04EA49cC6DBd3CbFFd00B4B8D6cb3ef",
Expand Down
2 changes: 2 additions & 0 deletions programs/svm-spoke/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub enum CommonError {
DepositsArePaused,
#[msg("Fills are currently paused!")]
FillsArePaused,
#[msg("Insufficient spoke pool balance to execute leaf")]
InsufficientSpokePoolBalanceToExecuteLeaf,
}

// SVM specific errors.
Expand Down
Loading

0 comments on commit 18df9a0

Please sign in to comment.