Skip to content

Commit 8a1a16e

Browse files
committed
Add support for custom gas token chains
1 parent e21f255 commit 8a1a16e

File tree

3 files changed

+87
-16
lines changed

3 files changed

+87
-16
lines changed

packages/gas-estimation/scripts/exec.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { utils, providers } from 'ethers';
22
import { NodeInterface__factory } from '@arbitrum/sdk/dist/lib/abi/factories/NodeInterface__factory';
33
import { NODE_INTERFACE_ADDRESS } from '@arbitrum/sdk/dist/lib/dataEntities/constants';
4-
const { requireEnvVariables, addCustomNetworkFromFile } = require('arb-shared-dependencies');
4+
const {
5+
requireEnvVariables,
6+
addCustomNetworkFromFile,
7+
arbLog,
8+
} = require('arb-shared-dependencies');
59

610
// Importing configuration //
711
require('dotenv').config();
@@ -24,6 +28,8 @@ const destinationAddress = '0x1234563d5de0d7198451f87bcbf15aefd00d434d';
2428
const txData = '0x';
2529

2630
const gasEstimator = async () => {
31+
await arbLog('Gas estimation');
32+
2733
// ***************************
2834
// * Gas formula explanation *
2935
// ***************************
@@ -93,7 +99,9 @@ const gasEstimator = async () => {
9399
// -------------------------------------------------------------------------------
94100
// NOTE: This one might be a bit confusing, but parentChainGasEstimated (B in the formula) is calculated based on child-chain's gas fees
95101
const parentChainCost = parentChainGasEstimated.mul(childChainEstimatedPrice);
96-
const parentChainSize = parentChainCost.div(parentChainEstimatedPrice);
102+
const parentChainSize = parentChainEstimatedPrice.eq(0)
103+
? 0
104+
: parentChainCost.div(parentChainEstimatedPrice);
97105

98106
// Getting the result of the formula
99107
// ---------------------------------

packages/redeem-pending-retryable/contracts/parent-chain/GreeterParent.sol

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@ pragma solidity ^0.8.0;
33

44
import "@arbitrum/nitro-contracts/src/bridge/Inbox.sol";
55
import "@arbitrum/nitro-contracts/src/bridge/Outbox.sol";
6+
import "@arbitrum/nitro-contracts/src/bridge/IERC20Inbox.sol";
7+
import "@arbitrum/nitro-contracts/src/bridge/IERC20Bridge.sol";
8+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
9+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
610
import "../Greeter.sol";
711

812
contract GreeterParent is Greeter {
13+
using SafeERC20 for IERC20;
14+
915
address public childTarget;
10-
IInbox public inbox;
16+
address public inbox;
1117

1218
event RetryableTicketCreated(uint256 indexed ticketId);
1319

1420
constructor(string memory _greeting, address _childTarget, address _inbox) Greeter(_greeting) {
1521
childTarget = _childTarget;
16-
inbox = IInbox(_inbox);
22+
inbox = _inbox;
1723
}
1824

1925
function updateChildTarget(address _childTarget) public {
@@ -27,24 +33,55 @@ contract GreeterParent is Greeter {
2733
uint256 gasPriceBid
2834
) public payable returns (uint256) {
2935
bytes memory data = abi.encodeWithSelector(Greeter.setGreeting.selector, _greeting);
30-
uint256 ticketID = inbox.createRetryableTicket{ value: msg.value }(
31-
childTarget,
32-
0,
33-
maxSubmissionCost,
34-
msg.sender,
35-
msg.sender,
36-
maxGas,
37-
gasPriceBid,
38-
data
39-
);
36+
37+
// Find out if this chain uses a custom gas token
38+
address bridge = address(IInbox(inbox).bridge());
39+
address nativeToken;
40+
try IERC20Bridge(bridge).nativeToken() returns (address nativeTokenAddress) {
41+
nativeToken = nativeTokenAddress;
42+
} catch {}
43+
44+
uint256 ticketID;
45+
if (nativeToken == address(0)) {
46+
// Chain uses ETH as the gas token
47+
ticketID = IInbox(inbox).createRetryableTicket{ value: msg.value }(
48+
childTarget,
49+
0,
50+
maxSubmissionCost,
51+
msg.sender,
52+
msg.sender,
53+
maxGas,
54+
gasPriceBid,
55+
data
56+
);
57+
} else {
58+
// Chain uses a custom gas token
59+
// l2callvalue + maxSubmissionCost + execution fee
60+
uint256 tokenAmount = 0 + maxSubmissionCost + (maxGas * gasPriceBid);
61+
62+
IERC20(nativeToken).safeTransferFrom(msg.sender, address(this), tokenAmount);
63+
IERC20(nativeToken).approve(inbox, tokenAmount);
64+
65+
ticketID = IERC20Inbox(inbox).createRetryableTicket(
66+
childTarget,
67+
0,
68+
maxSubmissionCost,
69+
msg.sender,
70+
msg.sender,
71+
maxGas,
72+
gasPriceBid,
73+
tokenAmount,
74+
data
75+
);
76+
}
4077

4178
emit RetryableTicketCreated(ticketID);
4279
return ticketID;
4380
}
4481

4582
/// @notice only childTarget can update greeting
4683
function setGreeting(string memory _greeting) public override {
47-
IBridge bridge = inbox.bridge();
84+
IBridge bridge = IInbox(inbox).bridge();
4885
// this prevents reentrancies on Child-to-Parent transactions
4986
require(msg.sender == address(bridge), "NOT_BRIDGE");
5087
IOutbox outbox = IOutbox(bridge.activeOutbox());

packages/redeem-pending-retryable/scripts/exec-createFailedRetryable.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
getArbitrumNetwork,
1616
} = require('@arbitrum/sdk');
1717
const { getBaseFee } = require('@arbitrum/sdk/dist/lib/utils/lib');
18+
const { ERC20__factory } = require('@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory');
1819
require('dotenv').config();
1920
requireEnvVariables(['PRIVATE_KEY', 'CHAIN_RPC', 'PARENT_CHAIN_RPC']);
2021

@@ -164,13 +165,38 @@ const main = async () => {
164165
`Sending greeting to the child chain with ${parentToChildMessageGasParams.deposit.toString()} callValue for child chain's fees:`,
165166
);
166167
const maxGasLimit = 10;
168+
169+
/**
170+
* We now find out whether the child chain we are using is a custom gas token chain
171+
* We need to perform an additional approve call to transfer
172+
* the native tokens to pay for the gas of the retryable tickets.
173+
*/
174+
const isCustomGasTokenChain =
175+
childChainNetwork.nativeToken && childChainNetwork.nativeToken !== ethers.constants.AddressZero;
176+
177+
if (isCustomGasTokenChain) {
178+
// Approve the gas token to be sent to the contract
179+
console.log('Giving allowance to the greeter to transfer the chain native token');
180+
const nativeToken = new ethers.Contract(
181+
childChainNetwork.nativeToken,
182+
ERC20__factory.abi,
183+
parentChainWallet,
184+
);
185+
const approvalTransaction = await nativeToken.approve(
186+
greeterParent.address,
187+
ethers.utils.parseEther('1'),
188+
);
189+
const approvalTransactionReceipt = await approvalTransaction.wait();
190+
console.log(`Approval transaction receipt is: ${approvalTransactionReceipt.transactionHash}`);
191+
}
192+
167193
const setGreetingTransaction = await greeterParent.setGreetingInChild(
168194
newGreeting, // string memory _greeting,
169195
parentToChildMessageGasParams.maxSubmissionCost,
170196
maxGasLimit,
171197
gasPriceBid,
172198
{
173-
value: parentToChildMessageGasParams.deposit,
199+
value: isCustomGasTokenChain ? 0 : parentToChildMessageGasParams.deposit,
174200
},
175201
);
176202
const setGreetingTransactionReceipt = await setGreetingTransaction.wait();

0 commit comments

Comments
 (0)