Skip to content

Commit 753553c

Browse files
authored
Merge branch 'master' into add-ci-for-custom-gas-token-chains
2 parents 1eb2c5d + 1dcf270 commit 753553c

File tree

19 files changed

+835
-202
lines changed

19 files changed

+835
-202
lines changed

customNetwork.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"bridge": "0x5eCF728ffC5C5E802091875f96281B5aeECf6C49",
77
"inbox": "0x9f8c1c641336A371031499e3c362e40d58d0f254",
88
"outbox": "0x50143333b44Ea46255BEb67255C9Afd35551072F",
9-
"rollup": "0x7d98BA231d29D5C202981542C0291718A7358c63",
9+
"rollup": "0xe5Ab92C74CD297F0a1F2914cE37204FC5Bc4e82D",
1010
"sequencerInbox": "0x18d19C5d3E685f5be5b9C86E097f0E439285D216"
1111
},
1212
"explorerUrl": "",
@@ -44,7 +44,7 @@
4444
"bridge": "0xA584795e24628D9c067A6480b033C9E96281fcA3",
4545
"inbox": "0xDcA690902d3154886Ec259308258D10EA5450996",
4646
"outbox": "0xda243bD61B011024FC923164db75Dde198AC6175",
47-
"rollup": "0xfe808cD61B3fe45c67c47B17DB49B96Fb2BFDfae",
47+
"rollup": "0x47b238E195b638b8972Cb3649e5d6775c279245d",
4848
"sequencerInbox": "0x16c54EE2015CD824415c2077F4103f444E00A8cb"
4949
},
5050
"explorerUrl": "",
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "@arbitrum/nitro-contracts/src/bridge/IERC20Inbox.sol";
5+
import "@arbitrum/nitro-contracts/src/bridge/IERC20Bridge.sol";
6+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
8+
9+
contract CustomGasTokenDeposit {
10+
using SafeERC20 for IERC20;
11+
12+
IERC20Inbox public inbox;
13+
14+
event CustomGasTokenDeposited(uint256 indexed ticketId);
15+
event RetryableTicketCreated(uint256 indexed ticketId);
16+
17+
constructor(address _inbox) {
18+
inbox = IERC20Inbox(_inbox);
19+
}
20+
21+
function depositToChildChain(uint256 amount) public returns (uint256) {
22+
// Transfer the native token to this contract
23+
// and allow Inbox to transfer those tokens
24+
address bridge = address(inbox.bridge());
25+
address nativeToken = IERC20Bridge(bridge).nativeToken();
26+
27+
IERC20(nativeToken).safeTransferFrom(msg.sender, address(this), amount);
28+
IERC20(nativeToken).approve(address(inbox), amount);
29+
30+
uint256 ticketID = inbox.depositERC20(amount);
31+
32+
emit CustomGasTokenDeposited(ticketID);
33+
return ticketID;
34+
}
35+
36+
function moveFundsFromChildChainAliasToAnotherAddress(
37+
address to,
38+
uint256 l2callvalue,
39+
uint256 maxSubmissionCost,
40+
uint256 maxGas,
41+
uint256 gasPriceBid,
42+
uint256 tokenAmount
43+
) public returns (uint256) {
44+
// Transfer the native token to this contract
45+
// and allow Inbox to transfer those tokens
46+
address bridge = address(inbox.bridge());
47+
address nativeToken = IERC20Bridge(bridge).nativeToken();
48+
49+
IERC20(nativeToken).safeTransferFrom(msg.sender, address(this), tokenAmount);
50+
IERC20(nativeToken).approve(address(inbox), tokenAmount);
51+
52+
/**
53+
* We are using unsafeCreateRetryableTicket because the safe one will check if
54+
* the parent chain's msg.value can be used to pay for the child chain's callvalue, while in this case
55+
* we'll use child chain's balance to pay for the callvalue rather than parent chain's msg.value
56+
*/
57+
uint256 ticketID = inbox.unsafeCreateRetryableTicket(
58+
to,
59+
l2callvalue,
60+
maxSubmissionCost,
61+
msg.sender,
62+
msg.sender,
63+
maxGas,
64+
gasPriceBid,
65+
tokenAmount,
66+
""
67+
);
68+
69+
emit RetryableTicketCreated(ticketID);
70+
return ticketID;
71+
}
72+
}

packages/contract-deposit/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
},
99
"dependencies": {
1010
"@arbitrum/sdk": "^4.0.1",
11-
"@arbitrum/nitro-contracts": "2.1.0"
11+
"@arbitrum/nitro-contracts": "2.1.0",
12+
"@openzeppelin/contracts": "^4.8.3"
1213
}
1314
}

packages/contract-deposit/scripts/exec.js

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const {
1818
ParentEthDepositTransactionReceipt,
1919
} = require('@arbitrum/sdk');
2020
const { getBaseFee } = require('@arbitrum/sdk/dist/lib/utils/lib');
21+
const { ERC20__factory } = require('@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory');
2122
require('dotenv').config();
2223
requireEnvVariables(['PRIVATE_KEY', 'CHAIN_RPC', 'PARENT_CHAIN_RPC', 'TransferTo']);
2324

@@ -50,15 +51,24 @@ const main = async () => {
5051
const ethBridger = new EthBridger(childChainNetwork);
5152
const inboxAddress = ethBridger.childNetwork.ethBridge.inbox;
5253

54+
/**
55+
* We find out whether the child chain we are using is a custom gas token chain
56+
* We need to perform an additional approve call to transfer
57+
* the native tokens to pay for the gas of the retryable tickets.
58+
*/
59+
const isCustomGasTokenChain =
60+
childChainNetwork.nativeToken && childChainNetwork.nativeToken !== ethers.constants.AddressZero;
61+
5362
/**
5463
* We deploy EthDeposit contract to the parent chain first and send eth to
5564
* the child chain via this contract.
5665
* Funds will deposit to the contract's alias address first.
5766
*/
58-
const DepositContract = await (
59-
await hre.ethers.getContractFactory('EthDeposit')
60-
).connect(parentChainWallet);
61-
console.log('Deploying EthDeposit contract...');
67+
const depositContractName = isCustomGasTokenChain ? 'CustomGasTokenDeposit' : 'EthDeposit';
68+
const DepositContract = (await hre.ethers.getContractFactory(depositContractName)).connect(
69+
parentChainWallet,
70+
);
71+
console.log(`Deploying ${depositContractName} contract...`);
6272
const depositContract = await DepositContract.deploy(inboxAddress);
6373
await depositContract.deployed();
6474
console.log(`deployed to ${depositContract.address}`);
@@ -71,18 +81,41 @@ const main = async () => {
7181

7282
console.log(`Sending deposit transaction...`);
7383

74-
const ethDepositTx = await depositContract.depositToChildChain({
75-
value: ethers.utils.parseEther('0.01'),
76-
});
77-
const ethDepositRec = await ethDepositTx.wait();
84+
let depositTx;
85+
let nativeTokenDecimals = 18; // We default to 18 decimals for ETH and most of ERC-20 tokens
86+
if (isCustomGasTokenChain) {
87+
// Approve the gas token to be sent to the contract
88+
console.log('Giving allowance to the contract to transfer the chain native token');
89+
const nativeToken = new ethers.Contract(
90+
childChainNetwork.nativeToken,
91+
ERC20__factory.abi,
92+
parentChainWallet,
93+
);
94+
nativeTokenDecimals = await nativeToken.decimals();
95+
const approvalTransaction = await nativeToken.approve(
96+
depositContract.address,
97+
ethers.utils.parseUnits('1', nativeTokenDecimals),
98+
);
99+
const approvalTransactionReceipt = await approvalTransaction.wait();
100+
console.log(`Approval transaction receipt is: ${approvalTransactionReceipt.transactionHash}`);
101+
102+
depositTx = await depositContract.depositToChildChain(
103+
ethers.utils.parseUnits('0.01', nativeTokenDecimals),
104+
);
105+
} else {
106+
depositTx = await depositContract.depositToChildChain({
107+
value: ethers.utils.parseEther('0.01'), // Here we know we are using ETH, so we can use parseEther
108+
});
109+
}
110+
const depositReceipt = await depositTx.wait();
78111

79-
console.log(`Deposit txn confirmed on the parent chain! 🙌 ${ethDepositRec.transactionHash}`);
112+
console.log(`Deposit txn confirmed on the parent chain! 🙌 ${depositReceipt.transactionHash}`);
80113

81114
console.log(
82115
'Waiting for the execution of the deposit in the child chain. This may take up to 10-15 minutes ⏰',
83116
);
84117

85-
const parentChainDepositTxReceipt = new ParentEthDepositTransactionReceipt(ethDepositRec);
118+
const parentChainDepositTxReceipt = new ParentEthDepositTransactionReceipt(depositReceipt);
86119
const childChainDepositResult = await parentChainDepositTxReceipt.waitForChildTransactionReceipt(
87120
childChainProvider,
88121
);
@@ -103,7 +136,7 @@ const main = async () => {
103136
);
104137
} else {
105138
throw new Error(
106-
`Deposit to the child chain failed, EthDepositStatus is ${
139+
`Deposit to the child chain failed, DepositStatus is ${
107140
EthDepositStatus[childChainDepositResult.message.status]
108141
}`,
109142
);
@@ -147,7 +180,7 @@ const main = async () => {
147180
{
148181
from: contractAliasAddress,
149182
to: transferTo,
150-
l2CallValue: ethers.utils.parseEther('0.01'), // because we deposited 0.01 ether, so we also transfer 0.01 ether out here.
183+
l2CallValue: ethers.utils.parseUnits('0.01', nativeTokenDecimals), // because we deposited 0.01 ether, so we also transfer 0.01 ether out here.
151184
excessFeeRefundAddress: depositContract.address,
152185
callValueRefundAddress: depositContract.address,
153186
data: [],
@@ -175,7 +208,9 @@ const main = async () => {
175208
* we need to subtract it here so the transaction in the parent chain doesn't pay l2callvalue
176209
* and instead uses the alias balance on the child chain directly.
177210
*/
178-
const depositAmount = parentToChildMessageGasParams.deposit.sub(ethers.utils.parseEther('0.01'));
211+
const depositAmount = parentToChildMessageGasParams.deposit.sub(
212+
ethers.utils.parseUnits('0.01', nativeTokenDecimals),
213+
);
179214

180215
console.log(
181216
`Transfer funds txn needs ${ethers.utils.formatEther(
@@ -186,16 +221,31 @@ const main = async () => {
186221
/**
187222
* Call the contract's method to transfer the funds from the alias to the address you set
188223
*/
189-
const setTransferTx = await depositContract.moveFundsFromChildChainAliasToAnotherAddress(
190-
transferTo,
191-
ethers.utils.parseEther('0.01'), // because we deposited 0.01 ether, so we also transfer 0.01 ether out here.
192-
parentToChildMessageGasParams.maxSubmissionCost,
193-
parentToChildMessageGasParams.gasLimit,
194-
gasPriceBid,
195-
{
196-
value: depositAmount,
197-
},
198-
);
224+
let setTransferTx;
225+
if (isCustomGasTokenChain) {
226+
// We don't need to give allowance to the contract now since we already gave plenty in the
227+
// previous step
228+
229+
setTransferTx = await depositContract.moveFundsFromChildChainAliasToAnotherAddress(
230+
transferTo,
231+
ethers.utils.parseUnits('0.01', nativeTokenDecimals), // because we deposited 0.01 ether, so we also transfer 0.01 ether out here.
232+
parentToChildMessageGasParams.maxSubmissionCost,
233+
parentToChildMessageGasParams.gasLimit,
234+
gasPriceBid,
235+
depositAmount,
236+
);
237+
} else {
238+
setTransferTx = await depositContract.moveFundsFromChildChainAliasToAnotherAddress(
239+
transferTo,
240+
ethers.utils.parseEther('0.01'), // because we deposited 0.01 ether, so we also transfer 0.01 ether out here.
241+
parentToChildMessageGasParams.maxSubmissionCost,
242+
parentToChildMessageGasParams.gasLimit,
243+
gasPriceBid,
244+
{
245+
value: depositAmount,
246+
},
247+
);
248+
}
199249
const setTransferRec = await setTransferTx.wait();
200250

201251
console.log(

packages/custom-gateway-bridging/contracts/ChildChainCustomGateway.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pragma solidity ^0.8.0;
33

44
import "./interfaces/ICustomGateway.sol";
55
import "./CrosschainMessenger.sol";
6-
import "./interfaces/IArbToken.sol";
6+
import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/arbitrum/IArbToken.sol";
77
import "@openzeppelin/contracts/access/Ownable.sol";
88

99
/**

packages/custom-gateway-bridging/contracts/ChildChainToken.sol

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity ^0.8.0;
33

4-
import "./interfaces/IArbToken.sol";
4+
import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/arbitrum/IArbToken.sol";
55
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
66

77
/**
88
* @title Example implementation of a custom ERC20 token to be deployed on L2
99
*/
1010
contract ChildChainToken is ERC20, IArbToken {
11-
address public l2GatewayAddress;
12-
address public override l1Address;
11+
address public gateway; // The child chain custom gateway contract
12+
address public override l1Address; // The address of the token on the parent chain
1313

1414
modifier onlyL2Gateway() {
15-
require(msg.sender == l2GatewayAddress, "NOT_GATEWAY");
15+
require(msg.sender == gateway, "NOT_GATEWAY");
1616
_;
1717
}
1818

1919
/**
2020
* @dev See {ERC20-constructor}
21-
* @param l2GatewayAddress_ address of the L2 custom gateway
22-
* @param l1TokenAddress_ address of the custom token deployed on L1
21+
* @param _gateway address of the L2 custom gateway
22+
* @param _l1Address address of the custom token deployed on L1
2323
*/
24-
constructor(address l2GatewayAddress_, address l1TokenAddress_) ERC20("L2CustomToken", "LCT") {
25-
l2GatewayAddress = l2GatewayAddress_;
26-
l1Address = l1TokenAddress_;
24+
constructor(address _gateway, address _l1Address) ERC20("L2CustomToken", "LCT") {
25+
gateway = _gateway;
26+
l1Address = _l1Address;
2727
}
2828

2929
/**

0 commit comments

Comments
 (0)