Skip to content

Commit 71243c1

Browse files
committed
Add more tutorials
1 parent 8a1a16e commit 71243c1

File tree

15 files changed

+735
-182
lines changed

15 files changed

+735
-182
lines changed
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: 65 additions & 21 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,37 @@ 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+
if (isCustomGasTokenChain) {
86+
// Approve the gas token to be sent to the contract
87+
console.log('Giving allowance to the contract to transfer the chain native token');
88+
const nativeToken = new ethers.Contract(
89+
childChainNetwork.nativeToken,
90+
ERC20__factory.abi,
91+
parentChainWallet,
92+
);
93+
const approvalTransaction = await nativeToken.approve(
94+
depositContract.address,
95+
ethers.utils.parseEther('1'),
96+
);
97+
const approvalTransactionReceipt = await approvalTransaction.wait();
98+
console.log(`Approval transaction receipt is: ${approvalTransactionReceipt.transactionHash}`);
99+
100+
depositTx = await depositContract.depositToChildChain(ethers.utils.parseEther('0.01'));
101+
} else {
102+
depositTx = await depositContract.depositToChildChain({
103+
value: ethers.utils.parseEther('0.01'),
104+
});
105+
}
106+
const depositReceipt = await depositTx.wait();
78107

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

81110
console.log(
82111
'Waiting for the execution of the deposit in the child chain. This may take up to 10-15 minutes ⏰',
83112
);
84113

85-
const parentChainDepositTxReceipt = new ParentEthDepositTransactionReceipt(ethDepositRec);
114+
const parentChainDepositTxReceipt = new ParentEthDepositTransactionReceipt(depositReceipt);
86115
const childChainDepositResult = await parentChainDepositTxReceipt.waitForChildTransactionReceipt(
87116
childChainProvider,
88117
);
@@ -103,7 +132,7 @@ const main = async () => {
103132
);
104133
} else {
105134
throw new Error(
106-
`Deposit to the child chain failed, EthDepositStatus is ${
135+
`Deposit to the child chain failed, DepositStatus is ${
107136
EthDepositStatus[childChainDepositResult.message.status]
108137
}`,
109138
);
@@ -186,16 +215,31 @@ const main = async () => {
186215
/**
187216
* Call the contract's method to transfer the funds from the alias to the address you set
188217
*/
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-
);
218+
let setTransferTx;
219+
if (isCustomGasTokenChain) {
220+
// We don't need to give allowance to the contract now since we already gave plenty in the
221+
// previous step
222+
223+
setTransferTx = await depositContract.moveFundsFromChildChainAliasToAnotherAddress(
224+
transferTo,
225+
ethers.utils.parseEther('0.01'), // because we deposited 0.01 ether, so we also transfer 0.01 ether out here.
226+
parentToChildMessageGasParams.maxSubmissionCost,
227+
parentToChildMessageGasParams.gasLimit,
228+
gasPriceBid,
229+
depositAmount,
230+
);
231+
} else {
232+
setTransferTx = await depositContract.moveFundsFromChildChainAliasToAnotherAddress(
233+
transferTo,
234+
ethers.utils.parseEther('0.01'), // because we deposited 0.01 ether, so we also transfer 0.01 ether out here.
235+
parentToChildMessageGasParams.maxSubmissionCost,
236+
parentToChildMessageGasParams.gasLimit,
237+
gasPriceBid,
238+
{
239+
value: depositAmount,
240+
},
241+
);
242+
}
199243
const setTransferRec = await setTransferTx.wait();
200244

201245
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)