Skip to content

Commit a01609e

Browse files
nicholaspaipxrl
authored andcommitted
improve(Arbitrum_CustomGasToken_Adapter): Handle non-18 custom gas token 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
1 parent 6c9d5b6 commit a01609e

File tree

1 file changed

+38
-4
lines changed

1 file changed

+38
-4
lines changed

contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ interface ArbitrumL1ERC20Bridge {
4242
* @return address of the native token.
4343
*/
4444
function nativeToken() external view returns (address);
45+
46+
/**
47+
* @dev number of decimals used by the native token
48+
* This is set on bridge initialization using nativeToken.decimals()
49+
* If the token does not have decimals() method, we assume it have 0 decimals
50+
*/
51+
function nativeTokenDecimals() external view returns (uint8);
4552
}
4653

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

154161
// L2 Gas price bid for immediate L2 execution attempt (queryable via standard eth*gasPrice RPC)
155-
uint256 public constant L2_GAS_PRICE = 5e9; // 5 gWei
162+
// The Arbitrum Inbox requires that this is specified in gWei (e.g. 1e9 = 1 gWei)
163+
uint256 public immutable L2_GAS_PRICE;
156164

157165
// Native token expected to be sent in L2 message. Should be 0 for all use cases of this constant, which
158166
// includes sending messages from L1 to L2 and sending Custom gas token ERC20's, which won't be the native token
@@ -193,6 +201,8 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
193201
* @param _l1ERC20GatewayRouter ERC20 gateway router contract to send tokens to Arbitrum.
194202
* @param _l2RefundL2Address L2 address to receive gas refunds on after a message is relayed.
195203
* @param _l1Usdc USDC address on L1.
204+
* @param _l2MaxSubmissionCost Max gas deducted from user's L2 balance to cover base fee.
205+
* @param _l2GasPrice Gas price bid for L2 execution. Should be set conservatively high to avoid stuck messages.
196206
* @param _cctpTokenMessenger TokenMessenger contract to bridge via CCTP.
197207
* @param _customGasTokenFunder Contract that funds the custom gas token.
198208
* @param _l2MaxSubmissionCost Amount of gas token allocated to pay for the base submission fee. The base
@@ -206,14 +216,16 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
206216
IERC20 _l1Usdc,
207217
ICCTPTokenMessenger _cctpTokenMessenger,
208218
FunderInterface _customGasTokenFunder,
209-
uint256 _l2MaxSubmissionCost
219+
uint256 _l2MaxSubmissionCost,
220+
uint256 _l2GasPrice
210221
) CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, CircleDomainIds.Arbitrum) {
211222
L1_INBOX = _l1ArbitrumInbox;
212223
L1_ERC20_GATEWAY_ROUTER = _l1ERC20GatewayRouter;
213224
L2_REFUND_L2_ADDRESS = _l2RefundL2Address;
214225
CUSTOM_GAS_TOKEN = IERC20(L1_INBOX.bridge().nativeToken());
215226
if (address(CUSTOM_GAS_TOKEN) == address(0)) revert InvalidCustomGasToken();
216227
L2_MAX_SUBMISSION_COST = _l2MaxSubmissionCost;
228+
L2_GAS_PRICE = _l2GasPrice;
217229
CUSTOM_GAS_TOKEN_FUNDER = _customGasTokenFunder;
218230
}
219231

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

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

317332
function _pullCustomGas(uint32 l2GasLimit) internal returns (uint256) {
@@ -320,4 +335,23 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter
320335
if (CUSTOM_GAS_TOKEN.balanceOf(address(this)) < requiredL1CallValue) revert InsufficientCustomGasToken();
321336
return requiredL1CallValue;
322337
}
338+
339+
function _from18ToNativeDecimals(uint256 amount) internal view returns (uint256) {
340+
uint8 nativeTokenDecimals = L1_INBOX.bridge().nativeTokenDecimals();
341+
if (nativeTokenDecimals == 18) {
342+
return amount;
343+
} else if (nativeTokenDecimals < 18) {
344+
// Round up the division result so that the L1 call value is always sufficient to cover the submission fee.
345+
uint256 reductionFactor = 10**(18 - nativeTokenDecimals);
346+
uint256 divFloor = amount / reductionFactor;
347+
uint256 mod = amount % reductionFactor;
348+
if (mod != 0) {
349+
return divFloor + 1;
350+
} else {
351+
return divFloor;
352+
}
353+
} else {
354+
return amount * 10**(nativeTokenDecimals - 18);
355+
}
356+
}
323357
}

0 commit comments

Comments
 (0)