Skip to content

Commit

Permalink
improve(Arbitrum_CustomGasToken_Adapter): Handle non-18 custom gas to…
Browse files Browse the repository at this point in the history
…ken precision (#589)

* improve(Arbitrum_CustomGasToken_Adapter): Handle non-18 custom gas token precision

Based on this [Inbox](https://github.com/OffchainLabs/nitro-contracts/blob/fbbcef09c95f69decabaced3da683f987902f3e2/src/bridge/AbsInbox.sol#L237) code which implies that the `tokenTotalFeeAmount` passed into the [ERC20Inbox](https://github.com/OffchainLabs/nitro-contracts/blob/fbbcef09c95f69decabaced3da683f987902f3e2/src/bridge/ERC20Inbox.sol#L85) should be in the custom gas token's precision while the `maxSubmissionCost`, `gasPrice`, and `gasLimit` should all be same as for normal Arbitrum messages

* Update contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

* Update contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

* Update Arbitrum_CustomGasToken_Adapter.sol

* support deecimals > 18

* divCeil instead of divFLoor
  • Loading branch information
nicholaspai authored Sep 19, 2024
1 parent 5c779a9 commit bf55da0
Showing 1 changed file with 38 additions and 4 deletions.
42 changes: 38 additions & 4 deletions contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ interface ArbitrumL1ERC20Bridge {
* @return address of the native token.
*/
function nativeToken() external view returns (address);

/**
* @dev number of decimals used by the native token
* This is set on bridge initialization using nativeToken.decimals()
* If the token does not have decimals() method, we assume it have 0 decimals
*/
function nativeTokenDecimals() external view returns (uint8);
}

/**
Expand Down Expand Up @@ -148,11 +155,12 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
// ticket’s calldata in the retry buffer. (current base submission fee is queryable via
// ArbRetryableTx.getSubmissionPrice). ArbRetryableTicket precompile interface exists at L2 address
// 0x000000000000000000000000000000000000006E.
// @dev Unlike in Arbitrum_Adapter, this is immutable because we don't know what precision the custom gas token has.
// The Arbitrum Inbox requires that this uses 18 decimal precision.
uint256 public immutable L2_MAX_SUBMISSION_COST;

Check warning on line 159 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 159 in contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

// L2 Gas price bid for immediate L2 execution attempt (queryable via standard eth*gasPrice RPC)
uint256 public constant L2_GAS_PRICE = 5e9; // 5 gWei
// 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 163 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 163 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
Expand Down Expand Up @@ -193,6 +201,8 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
* @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 _customGasTokenFunder Contract that funds the custom gas token.
* @param _l2MaxSubmissionCost Amount of gas token allocated to pay for the base submission fee. The base
Expand All @@ -206,14 +216,16 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
IERC20 _l1Usdc,
ICCTPTokenMessenger _cctpTokenMessenger,
FunderInterface _customGasTokenFunder,
uint256 _l2MaxSubmissionCost
uint256 _l2MaxSubmissionCost,
uint256 _l2GasPrice
) CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, CircleDomainIds.Arbitrum) {
L1_INBOX = _l1ArbitrumInbox;
L1_ERC20_GATEWAY_ROUTER = _l1ERC20GatewayRouter;
L2_REFUND_L2_ADDRESS = _l2RefundL2Address;
CUSTOM_GAS_TOKEN = IERC20(L1_INBOX.bridge().nativeToken());
if (address(CUSTOM_GAS_TOKEN) == address(0)) revert InvalidCustomGasToken();
L2_MAX_SUBMISSION_COST = _l2MaxSubmissionCost;
L2_GAS_PRICE = _l2GasPrice;
CUSTOM_GAS_TOKEN_FUNDER = _customGasTokenFunder;
}

Expand All @@ -236,6 +248,7 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
RELAY_MESSAGE_L2_GAS_LIMIT, // maxGas Max gas deducted from user's L2 balance to cover L2 execution
L2_GAS_PRICE, // gasPriceBid price bid for L2 execution
requiredL1TokenTotalFeeAmount, // tokenTotalFeeAmount amount of fees to be deposited in native token.
// This should be in the precision of the custom gas token.
message // data ABI encoded data of L2 message
);
emit MessageRelayed(target, message);
Expand Down Expand Up @@ -270,6 +283,7 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
// Must use Inbox to bridge custom gas token.
// Source: https://github.com/OffchainLabs/token-bridge-contracts/blob/5bdf33259d2d9ae52ddc69bc5a9cbc558c4c40c7/contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol#L33
if (l1Token == address(CUSTOM_GAS_TOKEN)) {
// amount and requiredL1TokenTotalFeeAmount are in the precision of the custom gas token.
uint256 amountToBridge = amount + requiredL1TokenTotalFeeAmount;
CUSTOM_GAS_TOKEN.safeIncreaseAllowance(address(L1_INBOX), amountToBridge);
L1_INBOX.createRetryableTicket(
Expand Down Expand Up @@ -307,11 +321,12 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter

/**
* @notice Returns required amount of gas token to send a message via the Inbox.
* @dev Should return a value in the same precision as the gas token's precision.
* @param l2GasLimit L2 gas limit for the message.
* @return amount of gas token that this contract needs to hold in order for relayMessage to succeed.
*/
function getL1CallValue(uint32 l2GasLimit) public view returns (uint256) {
return L2_MAX_SUBMISSION_COST + L2_GAS_PRICE * l2GasLimit;
return _from18ToNativeDecimals(L2_MAX_SUBMISSION_COST + L2_GAS_PRICE * l2GasLimit);
}

function _pullCustomGas(uint32 l2GasLimit) internal returns (uint256) {
Expand All @@ -320,4 +335,23 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
if (CUSTOM_GAS_TOKEN.balanceOf(address(this)) < requiredL1CallValue) revert InsufficientCustomGasToken();
return requiredL1CallValue;
}

function _from18ToNativeDecimals(uint256 amount) internal view returns (uint256) {
uint8 nativeTokenDecimals = L1_INBOX.bridge().nativeTokenDecimals();
if (nativeTokenDecimals == 18) {
return amount;
} else if (nativeTokenDecimals < 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 divFloor = amount / reductionFactor;
uint256 mod = amount % reductionFactor;
if (mod != 0) {
return divFloor + 1;
} else {
return divFloor;
}
} else {
return amount * 10**(nativeTokenDecimals - 18);
}
}
}

0 comments on commit bf55da0

Please sign in to comment.