Skip to content

Sync: l2-testnet > pcv/l2-bridge #704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dac80e2
chore: update graph config files
tmigone Sep 2, 2022
5d5cf12
fix: update configs for L1 and L2 networks including testnets
tmigone Sep 5, 2022
7da14f8
fix: fix e2e deployment tests
tmigone Sep 5, 2022
efc8c5a
feat: add arbitrum localhost config and split e2e tests into l1/l2/co…
tmigone Sep 5, 2022
00a2472
fix: some more details with the Nitro changes in the CLI
pcarranzav Aug 1, 2022
a18d490
feat: add support for local nitro networks (#674)
tmigone Aug 5, 2022
000ff41
fix: correctly get the L2 retryable in send-to-l2, and other improvem…
pcarranzav Aug 9, 2022
4144bc8
feat: add a parameter to send to l2 with calldata
abarmat Aug 9, 2022
3929a2b
chore: improve command documentation
abarmat Aug 10, 2022
6a9134b
feat: add a hardhat task to sync controller contracts
pcarranzav Aug 11, 2022
65bb15a
fix: sync task check if l2 reservoir contracts exists before using it
tmigone Sep 5, 2022
007cb9f
fix: update arbitrum config files
tmigone Sep 6, 2022
750a5b1
feat: add e2e deployment tests for L2s
tmigone Sep 7, 2022
4931d6e
feat: add bridge tests
tmigone Sep 7, 2022
0c5a30b
fix(e2e): l1 gateway throwing incorrect error
tmigone Sep 7, 2022
790b01d
chore: modify e2e script to run on nitro L1 testnode
tmigone Sep 7, 2022
7bd7061
feat(gre): desambiguate chainIds using secondary network name
tmigone Sep 7, 2022
da5d46d
fix: couple e2e fixes
tmigone Sep 8, 2022
0b3f80b
fix(e2e): fixes to run e2e tests on nitro testnodes
tmigone Sep 8, 2022
b0dee92
refactor: clean up send tokens to l2 cli
abarmat Aug 19, 2022
d769d9f
fix: skip l2 retryable check if l1 transaction failed
abarmat Aug 20, 2022
6191bc5
fix(cli): add ethValue to l1 to l2 message
tmigone Sep 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,26 @@ Scenarios are defined by an optional script and a test file:
- They run before the test file.
- Test file
- Should be named e2e/scenarios/{scenario-name}.test.ts.
- Standard chai/mocha/hardhat/ethers test file.
- Standard chai/mocha/hardhat/ethers test file.

## Setting up Arbitrum's testnodes

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.

```bash
git clone https://github.com/offchainlabs/nitro
cd nitro
git submodule update --init --recursive

# Apply any changes you might want, see below for more info, and then start the testnodes
./test-node.bash --init
```

**Useful information**
- L1 RPC: [http://localhost:8545](http://localhost:8545/)
- L2 RPC: [http://localhost:8547](http://localhost:8547/)
- Blockscout explorer (L2 only): [http://localhost:4000/](http://localhost:4000/)
- Prefunded genesis key (L1 and L2): `e887f7d17d07cc7b8004053fb8826f6657084e88904bb61590e498ca04704cf2`

**Enable automine on L1**
In `docker-compose.yml` file edit the `geth` service command by removing the `--dev.period 1` flag.
25 changes: 11 additions & 14 deletions cli/commands/bridge/to-l1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ import { loadEnv, CLIArgs, CLIEnvironment } from '../../env'
import { logger } from '../../logging'
import { getAddressBook } from '../../address-book'
import { getProvider, sendTransaction, toGRT } from '../../network'
import { chainIdIsL2 } from '../../utils'
import { chainIdIsL2 } from '../../cross-chain'
import { loadAddressBookContract } from '../../contracts'
import {
L2TransactionReceipt,
getL2Network,
L2ToL1MessageStatus,
L2ToL1MessageWriter,
} from '@arbitrum/sdk'
import { L2TransactionReceipt, L2ToL1MessageStatus, L2ToL1MessageWriter } from '@arbitrum/sdk'
import { L2GraphTokenGateway } from '../../../build/types/L2GraphTokenGateway'
import { BigNumber } from 'ethers'
import { JsonRpcProvider } from '@ethersproject/providers'
import { providers } from 'ethers'
import { L2GraphToken } from '../../../build/types/L2GraphToken'

const FOURTEEN_DAYS_IN_SECONDS = 24 * 3600 * 14

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

const gateway = loadAddressBookContract('L2GraphTokenGateway', l2AddressBook, l2Wallet)
const l2GRT = loadAddressBookContract('L2GraphToken', l2AddressBook, l2Wallet)
const l2GRT = loadAddressBookContract('L2GraphToken', l2AddressBook, l2Wallet) as L2GraphToken

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

const senderBalance = await l2GRT.balanceOf(cli.wallet.address)
if (senderBalance.lt(amount)) {
throw new Error('Sender balance is insufficient for the transfer')
}

const params = [l1GRTAddress, recipient, amount, '0x']
logger.info('Approving token transfer')
await sendTransaction(l2Wallet, l2GRT, 'approve', [gateway.address, amount])
Expand All @@ -99,9 +100,7 @@ export const startSendToL1 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Prom
params,
)
const l2Receipt = new L2TransactionReceipt(receipt)
const l2ToL1Message = (
await l2Receipt.getL2ToL1Messages(cli.wallet, await getL2Network(l2Provider))
)[0]
const l2ToL1Message = (await l2Receipt.getL2ToL1Messages(cli.wallet))[0]

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

const l2Receipt = new L2TransactionReceipt(receipt)
logger.info(`Getting L2 to L1 message...`)
const l2ToL1Message = (
await l2Receipt.getL2ToL1Messages(cli.wallet, await getL2Network(l2Provider))
)[0]
const l2ToL1Message = (await l2Receipt.getL2ToL1Messages(cli.wallet))[0]

if (wait) {
const retryDelayMs = cliArgs.retryDelaySeconds ? cliArgs.retryDelaySeconds * 1000 : 60000
Expand Down
152 changes: 93 additions & 59 deletions cli/commands/bridge/to-l2.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { Argv } from 'yargs'
import { utils } from 'ethers'
import { L1TransactionReceipt, L1ToL2MessageStatus, L1ToL2MessageWriter } from '@arbitrum/sdk'

import { loadEnv, CLIArgs, CLIEnvironment } from '../../env'
import { logger } from '../../logging'
import { getProvider, sendTransaction, toGRT } from '../../network'
import { utils } from 'ethers'
import { parseEther } from '@ethersproject/units'
import {
L1TransactionReceipt,
L1ToL2MessageStatus,
L1ToL2MessageWriter,
L1ToL2MessageGasEstimator,
} from '@arbitrum/sdk'
import { chainIdIsL2 } from '../../utils'
import { getProvider, sendTransaction, toGRT, ensureAllowance, toBN } from '../../network'
import { chainIdIsL2, estimateRetryableTxGas } from '../../cross-chain'

const logAutoRedeemReason = (autoRedeemRec) => {
if (autoRedeemRec == null) {
Expand All @@ -32,7 +28,12 @@ const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => {
logAutoRedeemReason(autoRedeemRec)
logger.info('Attempting to redeem...')
await l1ToL2Message.redeem()
l2TxHash = (await l1ToL2Message.getSuccessfulRedeem()).transactionHash
const redeemAttempt = await l1ToL2Message.getSuccessfulRedeem()
if (redeemAttempt.status == L1ToL2MessageStatus.REDEEMED) {
l2TxHash = redeemAttempt.l2TxReceipt ? redeemAttempt.l2TxReceipt.transactionHash : 'null'
} else {
throw new Error(`Unexpected L1ToL2MessageStatus after redeem attempt: ${res.status}`)
}
} else if (res.status != L1ToL2MessageStatus.REDEEMED) {
throw new Error(`Unexpected L1ToL2MessageStatus ${res.status}`)
}
Expand All @@ -41,75 +42,78 @@ const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => {

export const sendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
logger.info(`>>> Sending tokens to L2 <<<\n`)

// parse provider
const l1Provider = cli.wallet.provider
const l2Provider = getProvider(cliArgs.l2ProviderUrl)
const l1ChainId = cli.chainId
const l2ChainId = (await l2Provider.getNetwork()).chainId

if (chainIdIsL2(cli.chainId) || !chainIdIsL2(l2ChainId)) {
if (chainIdIsL2(l1ChainId) || !chainIdIsL2(l2ChainId)) {
throw new Error(
'Please use an L1 provider in --provider-url, and an L2 provider in --l2-provider-url',
)
}
const gateway = cli.contracts['L1GraphTokenGateway']
const l1GRT = cli.contracts['GraphToken']
const l1GRTAddress = l1GRT.address

// parse params
const { L1GraphTokenGateway: l1Gateway, GraphToken: l1GRT } = cli.contracts
const amount = toGRT(cliArgs.amount)
const recipient = cliArgs.recipient ? cliArgs.recipient : cli.wallet.address
const l2Dest = await gateway.l2Counterpart()
const recipient = cliArgs.recipient ?? cli.wallet.address
const l1GatewayAddress = l1Gateway.address
const l2GatewayAddress = await l1Gateway.l2Counterpart()
const calldata = cliArgs.calldata ?? '0x'

// transport tokens
logger.info(`Will send ${cliArgs.amount} GRT to ${recipient}`)
logger.info(`Using L1 gateway ${gateway.address} and L2 gateway ${l2Dest}`)
logger.info(`Using L1 gateway ${l1GatewayAddress} and L2 gateway ${l2GatewayAddress}`)
await ensureAllowance(cli.wallet, l1GatewayAddress, l1GRT, amount)

// estimate L2 ticket
// See https://github.com/OffchainLabs/arbitrum/blob/master/packages/arb-ts/src/lib/bridge.ts
const depositCalldata = await gateway.getOutboundCalldata(
l1GRTAddress,
const depositCalldata = await l1Gateway.getOutboundCalldata(
l1GRT.address,
cli.wallet.address,
recipient,
amount,
'0x',
calldata,
)

// Comment from Offchain Labs' implementation:
// we add a 0.05 ether "deposit" buffer to pay for execution in the gas estimation
logger.info('Estimating retryable ticket gas:')
const baseFee = (await cli.wallet.provider.getBlock('latest')).baseFeePerGas
const gasEstimator = new L1ToL2MessageGasEstimator(l2Provider)
const gasParams = await gasEstimator.estimateMessage(
gateway.address,
l2Dest,
const { maxGas, gasPriceBid, maxSubmissionCost } = await estimateRetryableTxGas(
l1Provider,
l2Provider,
l1GatewayAddress,
l2GatewayAddress,
depositCalldata,
parseEther('0'),
baseFee,
gateway.address,
gateway.address,
{
maxGas: cliArgs.maxGas,
gasPriceBid: cliArgs.gasPriceBid,
maxSubmissionCost: cliArgs.maxSubmissionCost,
},
)
const maxGas = gasParams.maxGasBid
const gasPriceBid = gasParams.maxGasPriceBid
const maxSubmissionPrice = gasParams.maxSubmissionPriceBid
const ethValue = maxSubmissionCost.add(gasPriceBid.mul(maxGas))
logger.info(
`Using max gas: ${maxGas}, gas price bid: ${gasPriceBid}, max submission price: ${maxSubmissionPrice}`,
`Using maxGas:${maxGas}, gasPriceBid:${gasPriceBid}, maxSubmissionCost:${maxSubmissionCost} = tx value: ${ethValue}`,
)

const ethValue = maxSubmissionPrice.add(gasPriceBid.mul(maxGas))
logger.info(`tx value: ${ethValue}`)
const data = utils.defaultAbiCoder.encode(['uint256', 'bytes'], [maxSubmissionPrice, '0x'])

const params = [l1GRTAddress, recipient, amount, maxGas, gasPriceBid, data]
logger.info('Approving token transfer')
await sendTransaction(cli.wallet, l1GRT, 'approve', [gateway.address, amount])
// build transaction
logger.info('Sending outbound transfer transaction')
const receipt = await sendTransaction(cli.wallet, gateway, 'outboundTransfer', params, {
const txData = utils.defaultAbiCoder.encode(['uint256', 'bytes'], [maxSubmissionCost, calldata])
const txParams = [l1GRT.address, recipient, amount, maxGas, gasPriceBid, txData]
const txReceipt = await sendTransaction(cli.wallet, l1Gateway, 'outboundTransfer', txParams, {
value: ethValue,
})
const l1Receipt = new L1TransactionReceipt(receipt)
const l1ToL2Message = await l1Receipt.getL1ToL2Message(cli.wallet.connect(l2Provider))

logger.info('Waiting for message to propagate to L2...')
try {
await checkAndRedeemMessage(l1ToL2Message)
} catch (e) {
logger.error('Auto redeem failed')
logger.error(e)
logger.error('You can re-attempt using redeem-send-to-l2 with the following txHash:')
logger.error(receipt.transactionHash)
// get l2 ticket status
if (txReceipt.status == 1) {
logger.info('Waiting for message to propagate to L2...')
const l1Receipt = new L1TransactionReceipt(txReceipt)
const l1ToL2Messages = await l1Receipt.getL1ToL2Messages(cli.wallet.connect(l2Provider))
const l1ToL2Message = l1ToL2Messages[0]
try {
await checkAndRedeemMessage(l1ToL2Message)
} catch (e) {
logger.error('Auto redeem failed')
logger.error(e)
logger.error('You can re-attempt using redeem-send-to-l2 with the following txHash:')
logger.error(txReceipt.transactionHash)
}
}
}

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

export const sendToL2Command = {
command: 'send-to-l2 <amount> [recipient]',
command: 'send-to-l2 <amount> [recipient] [calldata]',
describe: 'Perform an L1-to-L2 Graph Token transaction',
builder: (yargs: Argv): Argv => {
return yargs
.option('max-gas', {
description: 'Max gas for the L2 redemption attempt',
requiresArg: true,
type: 'string',
})
.option('gas-price-bid', {
description: 'Gas price for the L2 redemption attempt',
requiresArg: true,
type: 'string',
})
.option('max-submission-cost', {
description: 'Max submission cost for the retryable ticket',
requiresArg: true,
type: 'string',
})
.positional('amount', { description: 'Amount to send (will be converted to wei)' })
.positional('recipient', {
description: 'Receiving address in L2. Same to L1 address if empty',
})
.positional('calldata', {
description: 'Calldata to pass to the recipient. Must be whitelisted in the bridge',
})
.coerce({
maxGas: toBN,
gasPriceBid: toBN,
maxSubmissionCost: toBN,
})
},
handler: async (argv: CLIArgs): Promise<void> => {
return sendToL2(await loadEnv(argv), argv)
},
Expand Down
14 changes: 11 additions & 3 deletions cli/commands/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
sendTransaction,
} from '../network'
import { loadEnv, CLIArgs, CLIEnvironment } from '../env'
import { chainIdIsL2 } from '../cross-chain'
import { confirm } from '../helpers'
import { chainIdIsL2 } from '../utils'

const { EtherSymbol } = constants
const { formatEther } = utils
Expand Down Expand Up @@ -73,8 +73,8 @@ export const migrate = async (
if (!sure) return

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

if (chainId == 1337) {
await (cli.wallet.provider as providers.JsonRpcProvider).send('evm_setAutomine', [autoMine])
await setAutoMine(cli.wallet.provider as providers.JsonRpcProvider, autoMine)
}
}

const setAutoMine = async (provider: providers.JsonRpcProvider, automine: boolean) => {
try {
await provider.send('evm_setAutomine', [automine])
} catch (error) {
logger.warn('The method evm_setAutomine does not exist/is not available!')
}
}

Expand Down
2 changes: 1 addition & 1 deletion cli/commands/protocol/configure-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { loadEnv, CLIArgs, CLIEnvironment } from '../../env'
import { logger } from '../../logging'
import { getAddressBook } from '../../address-book'
import { sendTransaction } from '../../network'
import { chainIdIsL2, l1ToL2ChainIdMap, l2ToL1ChainIdMap } from '../../utils'
import { chainIdIsL2, l1ToL2ChainIdMap, l2ToL1ChainIdMap } from '../../cross-chain'

export const configureL1Bridge = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
logger.info(`>>> Setting L1 Bridge Configuration <<<\n`)
Expand Down
2 changes: 1 addition & 1 deletion cli/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BaseContract, providers, Signer } from 'ethers'

import { AddressBook } from './address-book'
import { chainIdIsL2 } from './cross-chain'
import { logger } from './logging'
import { getContractAt } from './network'

Expand All @@ -25,7 +26,6 @@ import { L1GraphTokenGateway } from '../build/types/L1GraphTokenGateway'
import { L2GraphToken } from '../build/types/L2GraphToken'
import { L2GraphTokenGateway } from '../build/types/L2GraphTokenGateway'
import { BridgeEscrow } from '../build/types/BridgeEscrow'
import { chainIdIsL2 } from './utils'

export interface NetworkContracts {
EpochManager: EpochManager
Expand Down
Loading