Skip to content

Commit 8c8f6aa

Browse files
tmigonepcarranzavabarmat
authored
Sync: l2-testnet > pcv/l2-bridge (#704)
Signed-off-by: Tomás Migone <[email protected]> Co-authored-by: Pablo Carranza Vélez <[email protected]> Co-authored-by: Ariel Barmat <[email protected]>
1 parent b510d8e commit 8c8f6aa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1146
-231
lines changed

TESTING.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,26 @@ Scenarios are defined by an optional script and a test file:
8282
- They run before the test file.
8383
- Test file
8484
- Should be named e2e/scenarios/{scenario-name}.test.ts.
85-
- Standard chai/mocha/hardhat/ethers test file.
85+
- Standard chai/mocha/hardhat/ethers test file.
86+
87+
## Setting up Arbitrum's testnodes
88+
89+
Arbitrum provides a quick way of setting up L1 and L2 testnodes for local development and testing. The following steps will guide you through the process of setting them up. Note that a local installation of Docker and Docker Compose is required.
90+
91+
```bash
92+
git clone https://github.com/offchainlabs/nitro
93+
cd nitro
94+
git submodule update --init --recursive
95+
96+
# Apply any changes you might want, see below for more info, and then start the testnodes
97+
./test-node.bash --init
98+
```
99+
100+
**Useful information**
101+
- L1 RPC: [http://localhost:8545](http://localhost:8545/)
102+
- L2 RPC: [http://localhost:8547](http://localhost:8547/)
103+
- Blockscout explorer (L2 only): [http://localhost:4000/](http://localhost:4000/)
104+
- Prefunded genesis key (L1 and L2): `e887f7d17d07cc7b8004053fb8826f6657084e88904bb61590e498ca04704cf2`
105+
106+
**Enable automine on L1**
107+
In `docker-compose.yml` file edit the `geth` service command by removing the `--dev.period 1` flag.

cli/commands/bridge/to-l1.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@ import { loadEnv, CLIArgs, CLIEnvironment } from '../../env'
22
import { logger } from '../../logging'
33
import { getAddressBook } from '../../address-book'
44
import { getProvider, sendTransaction, toGRT } from '../../network'
5-
import { chainIdIsL2 } from '../../utils'
5+
import { chainIdIsL2 } from '../../cross-chain'
66
import { loadAddressBookContract } from '../../contracts'
7-
import {
8-
L2TransactionReceipt,
9-
getL2Network,
10-
L2ToL1MessageStatus,
11-
L2ToL1MessageWriter,
12-
} from '@arbitrum/sdk'
7+
import { L2TransactionReceipt, L2ToL1MessageStatus, L2ToL1MessageWriter } from '@arbitrum/sdk'
138
import { L2GraphTokenGateway } from '../../../build/types/L2GraphTokenGateway'
149
import { BigNumber } from 'ethers'
1510
import { JsonRpcProvider } from '@ethersproject/providers'
1611
import { providers } from 'ethers'
12+
import { L2GraphToken } from '../../../build/types/L2GraphToken'
1713

1814
const FOURTEEN_DAYS_IN_SECONDS = 24 * 3600 * 14
1915

@@ -82,12 +78,17 @@ export const startSendToL1 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Prom
8278
const l2AddressBook = getAddressBook(cliArgs.addressBook, l2ChainId.toString())
8379

8480
const gateway = loadAddressBookContract('L2GraphTokenGateway', l2AddressBook, l2Wallet)
85-
const l2GRT = loadAddressBookContract('L2GraphToken', l2AddressBook, l2Wallet)
81+
const l2GRT = loadAddressBookContract('L2GraphToken', l2AddressBook, l2Wallet) as L2GraphToken
8682

8783
const l1Gateway = cli.contracts['L1GraphTokenGateway']
8884
logger.info(`Will send ${cliArgs.amount} GRT to ${recipient}`)
8985
logger.info(`Using L2 gateway ${gateway.address} and L1 gateway ${l1Gateway.address}`)
9086

87+
const senderBalance = await l2GRT.balanceOf(cli.wallet.address)
88+
if (senderBalance.lt(amount)) {
89+
throw new Error('Sender balance is insufficient for the transfer')
90+
}
91+
9192
const params = [l1GRTAddress, recipient, amount, '0x']
9293
logger.info('Approving token transfer')
9394
await sendTransaction(l2Wallet, l2GRT, 'approve', [gateway.address, amount])
@@ -99,9 +100,7 @@ export const startSendToL1 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Prom
99100
params,
100101
)
101102
const l2Receipt = new L2TransactionReceipt(receipt)
102-
const l2ToL1Message = (
103-
await l2Receipt.getL2ToL1Messages(cli.wallet, await getL2Network(l2Provider))
104-
)[0]
103+
const l2ToL1Message = (await l2Receipt.getL2ToL1Messages(cli.wallet))[0]
105104

106105
logger.info(`The transaction generated an L2 to L1 message in outbox with eth block number:`)
107106
logger.info(l2ToL1Message.event.ethBlockNum.toString())
@@ -157,9 +156,7 @@ export const finishSendToL1 = async (
157156

158157
const l2Receipt = new L2TransactionReceipt(receipt)
159158
logger.info(`Getting L2 to L1 message...`)
160-
const l2ToL1Message = (
161-
await l2Receipt.getL2ToL1Messages(cli.wallet, await getL2Network(l2Provider))
162-
)[0]
159+
const l2ToL1Message = (await l2Receipt.getL2ToL1Messages(cli.wallet))[0]
163160

164161
if (wait) {
165162
const retryDelayMs = cliArgs.retryDelaySeconds ? cliArgs.retryDelaySeconds * 1000 : 60000

cli/commands/bridge/to-l2.ts

Lines changed: 93 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1+
import { Argv } from 'yargs'
2+
import { utils } from 'ethers'
3+
import { L1TransactionReceipt, L1ToL2MessageStatus, L1ToL2MessageWriter } from '@arbitrum/sdk'
4+
15
import { loadEnv, CLIArgs, CLIEnvironment } from '../../env'
26
import { logger } from '../../logging'
3-
import { getProvider, sendTransaction, toGRT } from '../../network'
4-
import { utils } from 'ethers'
5-
import { parseEther } from '@ethersproject/units'
6-
import {
7-
L1TransactionReceipt,
8-
L1ToL2MessageStatus,
9-
L1ToL2MessageWriter,
10-
L1ToL2MessageGasEstimator,
11-
} from '@arbitrum/sdk'
12-
import { chainIdIsL2 } from '../../utils'
7+
import { getProvider, sendTransaction, toGRT, ensureAllowance, toBN } from '../../network'
8+
import { chainIdIsL2, estimateRetryableTxGas } from '../../cross-chain'
139

1410
const logAutoRedeemReason = (autoRedeemRec) => {
1511
if (autoRedeemRec == null) {
@@ -32,7 +28,12 @@ const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => {
3228
logAutoRedeemReason(autoRedeemRec)
3329
logger.info('Attempting to redeem...')
3430
await l1ToL2Message.redeem()
35-
l2TxHash = (await l1ToL2Message.getSuccessfulRedeem()).transactionHash
31+
const redeemAttempt = await l1ToL2Message.getSuccessfulRedeem()
32+
if (redeemAttempt.status == L1ToL2MessageStatus.REDEEMED) {
33+
l2TxHash = redeemAttempt.l2TxReceipt ? redeemAttempt.l2TxReceipt.transactionHash : 'null'
34+
} else {
35+
throw new Error(`Unexpected L1ToL2MessageStatus after redeem attempt: ${res.status}`)
36+
}
3637
} else if (res.status != L1ToL2MessageStatus.REDEEMED) {
3738
throw new Error(`Unexpected L1ToL2MessageStatus ${res.status}`)
3839
}
@@ -41,75 +42,78 @@ const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => {
4142

4243
export const sendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
4344
logger.info(`>>> Sending tokens to L2 <<<\n`)
45+
46+
// parse provider
47+
const l1Provider = cli.wallet.provider
4448
const l2Provider = getProvider(cliArgs.l2ProviderUrl)
49+
const l1ChainId = cli.chainId
4550
const l2ChainId = (await l2Provider.getNetwork()).chainId
46-
47-
if (chainIdIsL2(cli.chainId) || !chainIdIsL2(l2ChainId)) {
51+
if (chainIdIsL2(l1ChainId) || !chainIdIsL2(l2ChainId)) {
4852
throw new Error(
4953
'Please use an L1 provider in --provider-url, and an L2 provider in --l2-provider-url',
5054
)
5155
}
52-
const gateway = cli.contracts['L1GraphTokenGateway']
53-
const l1GRT = cli.contracts['GraphToken']
54-
const l1GRTAddress = l1GRT.address
56+
57+
// parse params
58+
const { L1GraphTokenGateway: l1Gateway, GraphToken: l1GRT } = cli.contracts
5559
const amount = toGRT(cliArgs.amount)
56-
const recipient = cliArgs.recipient ? cliArgs.recipient : cli.wallet.address
57-
const l2Dest = await gateway.l2Counterpart()
60+
const recipient = cliArgs.recipient ?? cli.wallet.address
61+
const l1GatewayAddress = l1Gateway.address
62+
const l2GatewayAddress = await l1Gateway.l2Counterpart()
63+
const calldata = cliArgs.calldata ?? '0x'
5864

65+
// transport tokens
5966
logger.info(`Will send ${cliArgs.amount} GRT to ${recipient}`)
60-
logger.info(`Using L1 gateway ${gateway.address} and L2 gateway ${l2Dest}`)
67+
logger.info(`Using L1 gateway ${l1GatewayAddress} and L2 gateway ${l2GatewayAddress}`)
68+
await ensureAllowance(cli.wallet, l1GatewayAddress, l1GRT, amount)
69+
70+
// estimate L2 ticket
6171
// See https://github.com/OffchainLabs/arbitrum/blob/master/packages/arb-ts/src/lib/bridge.ts
62-
const depositCalldata = await gateway.getOutboundCalldata(
63-
l1GRTAddress,
72+
const depositCalldata = await l1Gateway.getOutboundCalldata(
73+
l1GRT.address,
6474
cli.wallet.address,
6575
recipient,
6676
amount,
67-
'0x',
77+
calldata,
6878
)
69-
70-
// Comment from Offchain Labs' implementation:
71-
// we add a 0.05 ether "deposit" buffer to pay for execution in the gas estimation
72-
logger.info('Estimating retryable ticket gas:')
73-
const baseFee = (await cli.wallet.provider.getBlock('latest')).baseFeePerGas
74-
const gasEstimator = new L1ToL2MessageGasEstimator(l2Provider)
75-
const gasParams = await gasEstimator.estimateMessage(
76-
gateway.address,
77-
l2Dest,
79+
const { maxGas, gasPriceBid, maxSubmissionCost } = await estimateRetryableTxGas(
80+
l1Provider,
81+
l2Provider,
82+
l1GatewayAddress,
83+
l2GatewayAddress,
7884
depositCalldata,
79-
parseEther('0'),
80-
baseFee,
81-
gateway.address,
82-
gateway.address,
85+
{
86+
maxGas: cliArgs.maxGas,
87+
gasPriceBid: cliArgs.gasPriceBid,
88+
maxSubmissionCost: cliArgs.maxSubmissionCost,
89+
},
8390
)
84-
const maxGas = gasParams.maxGasBid
85-
const gasPriceBid = gasParams.maxGasPriceBid
86-
const maxSubmissionPrice = gasParams.maxSubmissionPriceBid
91+
const ethValue = maxSubmissionCost.add(gasPriceBid.mul(maxGas))
8792
logger.info(
88-
`Using max gas: ${maxGas}, gas price bid: ${gasPriceBid}, max submission price: ${maxSubmissionPrice}`,
93+
`Using maxGas:${maxGas}, gasPriceBid:${gasPriceBid}, maxSubmissionCost:${maxSubmissionCost} = tx value: ${ethValue}`,
8994
)
9095

91-
const ethValue = maxSubmissionPrice.add(gasPriceBid.mul(maxGas))
92-
logger.info(`tx value: ${ethValue}`)
93-
const data = utils.defaultAbiCoder.encode(['uint256', 'bytes'], [maxSubmissionPrice, '0x'])
94-
95-
const params = [l1GRTAddress, recipient, amount, maxGas, gasPriceBid, data]
96-
logger.info('Approving token transfer')
97-
await sendTransaction(cli.wallet, l1GRT, 'approve', [gateway.address, amount])
96+
// build transaction
9897
logger.info('Sending outbound transfer transaction')
99-
const receipt = await sendTransaction(cli.wallet, gateway, 'outboundTransfer', params, {
98+
const txData = utils.defaultAbiCoder.encode(['uint256', 'bytes'], [maxSubmissionCost, calldata])
99+
const txParams = [l1GRT.address, recipient, amount, maxGas, gasPriceBid, txData]
100+
const txReceipt = await sendTransaction(cli.wallet, l1Gateway, 'outboundTransfer', txParams, {
100101
value: ethValue,
101102
})
102-
const l1Receipt = new L1TransactionReceipt(receipt)
103-
const l1ToL2Message = await l1Receipt.getL1ToL2Message(cli.wallet.connect(l2Provider))
104-
105-
logger.info('Waiting for message to propagate to L2...')
106-
try {
107-
await checkAndRedeemMessage(l1ToL2Message)
108-
} catch (e) {
109-
logger.error('Auto redeem failed')
110-
logger.error(e)
111-
logger.error('You can re-attempt using redeem-send-to-l2 with the following txHash:')
112-
logger.error(receipt.transactionHash)
103+
// get l2 ticket status
104+
if (txReceipt.status == 1) {
105+
logger.info('Waiting for message to propagate to L2...')
106+
const l1Receipt = new L1TransactionReceipt(txReceipt)
107+
const l1ToL2Messages = await l1Receipt.getL1ToL2Messages(cli.wallet.connect(l2Provider))
108+
const l1ToL2Message = l1ToL2Messages[0]
109+
try {
110+
await checkAndRedeemMessage(l1ToL2Message)
111+
} catch (e) {
112+
logger.error('Auto redeem failed')
113+
logger.error(e)
114+
logger.error('You can re-attempt using redeem-send-to-l2 with the following txHash:')
115+
logger.error(txReceipt.transactionHash)
116+
}
113117
}
114118
}
115119

@@ -135,8 +139,38 @@ export const redeemSendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Pro
135139
}
136140

137141
export const sendToL2Command = {
138-
command: 'send-to-l2 <amount> [recipient]',
142+
command: 'send-to-l2 <amount> [recipient] [calldata]',
139143
describe: 'Perform an L1-to-L2 Graph Token transaction',
144+
builder: (yargs: Argv): Argv => {
145+
return yargs
146+
.option('max-gas', {
147+
description: 'Max gas for the L2 redemption attempt',
148+
requiresArg: true,
149+
type: 'string',
150+
})
151+
.option('gas-price-bid', {
152+
description: 'Gas price for the L2 redemption attempt',
153+
requiresArg: true,
154+
type: 'string',
155+
})
156+
.option('max-submission-cost', {
157+
description: 'Max submission cost for the retryable ticket',
158+
requiresArg: true,
159+
type: 'string',
160+
})
161+
.positional('amount', { description: 'Amount to send (will be converted to wei)' })
162+
.positional('recipient', {
163+
description: 'Receiving address in L2. Same to L1 address if empty',
164+
})
165+
.positional('calldata', {
166+
description: 'Calldata to pass to the recipient. Must be whitelisted in the bridge',
167+
})
168+
.coerce({
169+
maxGas: toBN,
170+
gasPriceBid: toBN,
171+
maxSubmissionCost: toBN,
172+
})
173+
},
140174
handler: async (argv: CLIArgs): Promise<void> => {
141175
return sendToL2(await loadEnv(argv), argv)
142176
},

cli/commands/migrate.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
sendTransaction,
1212
} from '../network'
1313
import { loadEnv, CLIArgs, CLIEnvironment } from '../env'
14+
import { chainIdIsL2 } from '../cross-chain'
1415
import { confirm } from '../helpers'
15-
import { chainIdIsL2 } from '../utils'
1616

1717
const { EtherSymbol } = constants
1818
const { formatEther } = utils
@@ -73,8 +73,8 @@ export const migrate = async (
7373
if (!sure) return
7474

7575
if (chainId == 1337) {
76-
await (cli.wallet.provider as providers.JsonRpcProvider).send('evm_setAutomine', [true])
7776
allContracts = ['EthereumDIDRegistry', ...allContracts]
77+
await setAutoMine(cli.wallet.provider as providers.JsonRpcProvider, true)
7878
} else if (chainIdIsL2(chainId)) {
7979
allContracts = l2Contracts
8080
}
@@ -169,7 +169,15 @@ export const migrate = async (
169169
logger.info(`Sent ${nTx} transaction${nTx === 1 ? '' : 's'} & spent ${EtherSymbol} ${spent}`)
170170

171171
if (chainId == 1337) {
172-
await (cli.wallet.provider as providers.JsonRpcProvider).send('evm_setAutomine', [autoMine])
172+
await setAutoMine(cli.wallet.provider as providers.JsonRpcProvider, autoMine)
173+
}
174+
}
175+
176+
const setAutoMine = async (provider: providers.JsonRpcProvider, automine: boolean) => {
177+
try {
178+
await provider.send('evm_setAutomine', [automine])
179+
} catch (error) {
180+
logger.warn('The method evm_setAutomine does not exist/is not available!')
173181
}
174182
}
175183

cli/commands/protocol/configure-bridge.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { loadEnv, CLIArgs, CLIEnvironment } from '../../env'
22
import { logger } from '../../logging'
33
import { getAddressBook } from '../../address-book'
44
import { sendTransaction } from '../../network'
5-
import { chainIdIsL2, l1ToL2ChainIdMap, l2ToL1ChainIdMap } from '../../utils'
5+
import { chainIdIsL2, l1ToL2ChainIdMap, l2ToL1ChainIdMap } from '../../cross-chain'
66

77
export const configureL1Bridge = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
88
logger.info(`>>> Setting L1 Bridge Configuration <<<\n`)

cli/contracts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BaseContract, providers, Signer } from 'ethers'
22

33
import { AddressBook } from './address-book'
4+
import { chainIdIsL2 } from './cross-chain'
45
import { logger } from './logging'
56
import { getContractAt } from './network'
67

@@ -25,7 +26,6 @@ import { L1GraphTokenGateway } from '../build/types/L1GraphTokenGateway'
2526
import { L2GraphToken } from '../build/types/L2GraphToken'
2627
import { L2GraphTokenGateway } from '../build/types/L2GraphTokenGateway'
2728
import { BridgeEscrow } from '../build/types/BridgeEscrow'
28-
import { chainIdIsL2 } from './utils'
2929

3030
export interface NetworkContracts {
3131
EpochManager: EpochManager

0 commit comments

Comments
 (0)