@@ -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