-
Notifications
You must be signed in to change notification settings - Fork 146
/
to-l2.ts
187 lines (171 loc) · 7.17 KB
/
to-l2.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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, ensureAllowance, toBN } from '../../network'
import { chainIdIsL2, estimateRetryableTxGas } from '../../cross-chain'
const logAutoRedeemReason = (autoRedeemRec) => {
if (autoRedeemRec == null) {
logger.info(`Auto redeem was not attempted.`)
return
}
logger.info(`Auto redeem reverted.`)
}
const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => {
logger.info(`Waiting for status of ${l1ToL2Message.retryableCreationId}`)
const res = await l1ToL2Message.waitForStatus()
logger.info('Getting auto redeem attempt')
const autoRedeemRec = await l1ToL2Message.getAutoRedeemAttempt()
const l2TxReceipt = res.status === L1ToL2MessageStatus.REDEEMED ? res.l2TxReceipt : autoRedeemRec
let l2TxHash = l2TxReceipt ? l2TxReceipt.transactionHash : 'null'
if (res.status === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) {
/** Message wasn't auto-redeemed! */
logger.warn('Funds were deposited on L2 but the retryable ticket was not redeemed')
logAutoRedeemReason(autoRedeemRec)
logger.info('Attempting to redeem...')
await l1ToL2Message.redeem(process.env.CI ? { gasLimit: 2_000_000 } : {})
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}`)
}
logger.info(`Transfer successful: ${l2TxHash}`)
}
export const sendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
logger.info(`>>> Sending tokens to L2 <<<\n`)
// parse provider
const l1Provider = cli.wallet.provider
// TODO: fix this hack for usage with hardhat
const l2Provider = cliArgs.l2Provider ? cliArgs.l2Provider : getProvider(cliArgs.l2ProviderUrl)
const l1ChainId = cli.chainId
const l2ChainId = (await l2Provider.getNetwork()).chainId
if (chainIdIsL2(l1ChainId) || !chainIdIsL2(l2ChainId)) {
throw new Error(
'Please use an L1 provider in --provider-url, and an L2 provider in --l2-provider-url',
)
}
// parse params
const { L1GraphTokenGateway: l1Gateway, GraphToken: l1GRT } = cli.contracts
const amount = toGRT(cliArgs.amount)
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 ${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 l1Gateway.getOutboundCalldata(
l1GRT.address,
cli.wallet.address,
recipient,
amount,
calldata,
)
const { maxGas, gasPriceBid, maxSubmissionCost } = await estimateRetryableTxGas(
l1Provider,
l2Provider,
l1GatewayAddress,
l2GatewayAddress,
depositCalldata,
{
maxGas: cliArgs.maxGas,
gasPriceBid: cliArgs.gasPriceBid,
maxSubmissionCost: cliArgs.maxSubmissionCost,
},
)
const ethValue = maxSubmissionCost.add(gasPriceBid.mul(maxGas))
logger.info(
`Using maxGas:${maxGas}, gasPriceBid:${gasPriceBid}, maxSubmissionCost:${maxSubmissionCost} = tx value: ${ethValue}`,
)
// build transaction
logger.info('Sending outbound transfer transaction')
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,
})
// 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)
}
}
}
export const redeemSendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
logger.info(`>>> Redeeming pending tokens on L2 <<<\n`)
const l2Provider = getProvider(cliArgs.l2ProviderUrl)
const l2ChainId = (await l2Provider.getNetwork()).chainId
if (chainIdIsL2(cli.chainId) || !chainIdIsL2(l2ChainId)) {
throw new Error(
'Please use an L1 provider in --provider-url, and an L2 provider in --l2-provider-url',
)
}
const l1Provider = cli.wallet.provider
const receipt = await l1Provider.getTransactionReceipt(cliArgs.txHash)
const l1Receipt = new L1TransactionReceipt(receipt)
const l1ToL2Messages = await l1Receipt.getL1ToL2Messages(cli.wallet.connect(l2Provider))
const l1ToL2Message = l1ToL2Messages[0]
logger.info('Checking message status in L2...')
await checkAndRedeemMessage(l1ToL2Message)
}
export const sendToL2Command = {
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 allowlisted in the bridge',
})
.coerce({
maxGas: toBN,
gasPriceBid: toBN,
maxSubmissionCost: toBN,
})
},
handler: async (argv: CLIArgs): Promise<void> => {
return sendToL2(await loadEnv(argv), argv)
},
}
export const redeemSendToL2Command = {
command: 'redeem-send-to-l2 <txHash>',
describe: 'Finish an L1-to-L2 Graph Token transaction if it failed to auto-redeem',
handler: async (argv: CLIArgs): Promise<void> => {
return redeemSendToL2(await loadEnv(argv), argv)
},
}