-
Notifications
You must be signed in to change notification settings - Fork 163
Arbitrum bridge + rewards distribution #552
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
Closed
Closed
Changes from all commits
Commits
Show all changes
74 commits
Select commit
Hold shift + click to select a range
f5e17bc
feat: add Arbitrum GRT bridge
pcarranzav 0495b9c
feat: allow callhooks from whitelisted addresses in the Arbitrum GRT …
pcarranzav 359d254
fix: call bridge callhooks after minting tokens
pcarranzav 00e7d3a
feat: add commands to configure and use the Arbitrum bridge on the CLI
pcarranzav 497a7d8
fix: Take a snapshot of GRT supply when signal is updated
pcarranzav 1bf6515
test: add a test for rewards distribution accrual with multiple alloc…
abarmat ebf51fe
test: Add a test for two simultaneous allocations with a GRT burn in …
pcarranzav dbeaa7c
fix: Typo fix in tests
pcarranzav 403be94
feat: implement distribution of rewards to L1 and L2 using a Reservoir
pcarranzav 57dbbb8
fix: document potential drip reverts if issuance rate is updated [L-01]
pcarranzav b5967ac
fix: document drip revert when l2RewardsFraction changed [L-02]
pcarranzav d5fa0a8
fix: rename variables related to supply to issuanceBase to make it cl…
pcarranzav 494df53
fix: use issuanceBase check to prevent calling initialSnapshot twice …
pcarranzav a15bc1d
test: fix tests after not allowing initialSnapshot to be called twice
pcarranzav 29702d6
fix: hardcode L2 initial supply to 0 [L-05]
pcarranzav 68cc8d3
fix: add more input validation [L-07]
pcarranzav 06275b7
fix: remove redundant callhook whitelist on L2 [L-08]
pcarranzav 395d858
fix: allow bigger msg.value than needed [L-09]
pcarranzav 2f41f81
fix: document the need for drip after a param update [L-06]
pcarranzav e8544e8
fix: validate L2Reservoir address on L1Reservoir [L-07]
pcarranzav 0b51643
fix: rename normalizedSupply to l2IssuanceBase in L2 message to avoid…
pcarranzav f561a34
fix: remove silent failure if rewardsManager is not set [L-12]
pcarranzav 061ab3f
fix: document non-expiring permits on L2GraphToken [M-02]
pcarranzav 88fac68
Merge branch 'pcv/552-m02-document-non-expiring-permits' into pcv/571…
pcarranzav f2641f1
test: remove unneeded L2 callhook whitelist entry
pcarranzav 2582b10
fix: document the need for providing the gateways with allowance [N-01]
pcarranzav bb7f2e7
fix: remove unused event in L2GraphTokenGateway [N-02]
pcarranzav 0ea0d30
fix: move nonce change to reduce gas on reverted permit call [N-03]
pcarranzav 31e07c4
fix: general code improvements [N-05]
pcarranzav 14ada45
fix: add some missing parameters in docstrings [N-06]
pcarranzav 8742cc8
fix: use internal function to consistently set dripInterval [N-07]
pcarranzav 3002d3e
fix: remove named returns [N-08]
pcarranzav 5c1f877
fix: update some outdated docstrings and comments [N-09]
pcarranzav bd23616
fix: document why some variables are not set during initialization [N…
pcarranzav 0f5fc9b
fix: add missing getters to Managed [N-11]
pcarranzav 46a8444
fix: use Arbitrum's AddressAliasHelper instead of reimplementing it […
pcarranzav 505a204
fix: separate contracts into different files [N-13]
pcarranzav b7b2a6c
fix: rename some variables for clarity [N-14]
pcarranzav 1f798d8
fix: document the need to retry tickets if drip is received out-of-or…
pcarranzav 012e3d2
fix: various typos [N-16]
pcarranzav 1e171db
fix: remove unneeded ERC20Upgradeable inheritance [N-17]
pcarranzav 65829af
fix: remove some unnecessary bits of code [N-19]
pcarranzav a11501f
fix: replace MAX_UINT256 with type().max [N-18] [N-20]
pcarranzav 960cc04
fix: remove an unused import [N-21]
pcarranzav 3a6c3b5
fix: check before adding/removing whitelisted addresses [N-23]
pcarranzav 37d70a5
test: remove repeated addToCallhookWhitelist
pcarranzav 9e7686f
fix: use SafeMath more consistently
pcarranzav 8426b4c
fix: add more docs on the design behind callhook reverts [L-13]
pcarranzav 7bacfcb
Merge branch 'dev' into pcv/arb-bridge-merge-dev
pcarranzav eb23e6a
fix: correct some mistakes from the merge conflict resolution
pcarranzav df5599f
Merge branch 'dev' into pcv/arb-bridge-merge-dev
pcarranzav 7648ea2
feat: add support for Arbitrum Nitro on CLI commands
pcarranzav 500e86b
fix: change to the new Goerli Arb testnet
pcarranzav 4b9491f
feat: keeper reward for reservoir drip through token issuance
pcarranzav 56de47a
fix: don't use tx.origin as it will not work
pcarranzav a33bd30
fix: only allow indexers, their operators, or whitelisted addresses t…
pcarranzav 435abc2
test: fix rewards and reservoir tests after restricting drip callers
pcarranzav c4af2f6
test: add a test for the keeper reward delivery in L2
pcarranzav 873b600
fix: provide part of the keeper reward to L2 redeemer
pcarranzav 634477a
fix: clean up comments about redeemer
pcarranzav b542943
fix: more documentation details
pcarranzav 6ef2faa
fix: use safe math for minDripInterval
pcarranzav 1a6df5d
fix: validate input when granting/revoking drip permission
pcarranzav 120753f
fix: docs and inheritance for IArbTxWithRedeemer
pcarranzav 00d4547
fix: remove minDripInterval from the drip keeper reward calculation […
pcarranzav d36aab9
fix: use L2 alias of l1ReservoirAddress when comparing getCurrentRede…
pcarranzav 2eea58c
fix: don't include keeper reward twice when computing what to send to…
pcarranzav 448f506
test: add test to ensure no DoS if l2RewardsFraction is zeroed [H-04]
pcarranzav cc51cb7
test: optimize functions to advance blocks and fix some race conditions
pcarranzav 0f8fb06
fix: add some missing validation on reservoirs [M-01]
pcarranzav 365e307
fix: add some missing docstrings [L-04]
pcarranzav 91a0219
fix: use a single-condition requires for the drip auth check [L-05]
pcarranzav 80cc2f2
fix: add indexed params to dripper change events [N-01]
pcarranzav b99d31f
fix: use explicit imports in relevant reservoir contracts [N-02]
pcarranzav File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| { | ||
| "source": "https://developer.offchainlabs.com/docs/useful_addresses", | ||
| "1": { | ||
| "L1GatewayRouter": { | ||
| "address": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef" | ||
| }, | ||
| "IInbox": { | ||
| "address": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f" | ||
| } | ||
| }, | ||
| "4": { | ||
| "L1GatewayRouter": { | ||
| "address": "0x70C143928eCfFaf9F5b406f7f4fC28Dc43d68380" | ||
| }, | ||
| "IInbox": { | ||
| "address": "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e" | ||
| } | ||
| }, | ||
| "5": { | ||
| "L1GatewayRouter": { | ||
| "address": "0x4c7708168395aEa569453Fc36862D2ffcDaC588c" | ||
| }, | ||
| "IInbox": { | ||
| "address": "0x6BEbC4925716945D46F0Ec336D5C2564F419682C" | ||
| } | ||
| }, | ||
| "42161": { | ||
| "L2GatewayRouter": { | ||
| "address": "0x5288c571Fd7aD117beA99bF60FE0846C4E84F933" | ||
| } | ||
| }, | ||
| "421611": { | ||
| "L2GatewayRouter": { | ||
| "address": "0x9413AD42910c1eA60c737dB5f58d1C504498a3cD" | ||
| } | ||
| }, | ||
| "421613": { | ||
| "L2GatewayRouter": { | ||
| "address": "0xE5B9d8d42d656d1DcB8065A6c012FE3780246041" | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import yargs, { Argv } from 'yargs' | ||
|
|
||
| import { redeemSendToL2Command, sendToL2Command } from './to-l2' | ||
| import { startSendToL1Command, finishSendToL1Command, waitFinishSendToL1Command } from './to-l1' | ||
| import { cliOpts } from '../../defaults' | ||
|
|
||
| export const bridgeCommand = { | ||
| command: 'bridge', | ||
| describe: 'Graph token bridge actions.', | ||
| builder: (yargs: Argv): yargs.Argv => { | ||
| return yargs | ||
| .option('-l', cliOpts.l2ProviderUrl) | ||
| .command(sendToL2Command) | ||
| .command(redeemSendToL2Command) | ||
| .command(startSendToL1Command) | ||
| .command(finishSendToL1Command) | ||
| .command(waitFinishSendToL1Command) | ||
| }, | ||
| handler: (): void => { | ||
| yargs.showHelp() | ||
| }, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| 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 { loadAddressBookContract } from '../../contracts' | ||
| import { | ||
| L2TransactionReceipt, | ||
| getL2Network, | ||
| L2ToL1MessageStatus, | ||
| L2ToL1MessageWriter, | ||
| } from '@arbitrum/sdk' | ||
| import { L2GraphTokenGateway } from '../../../build/types/L2GraphTokenGateway' | ||
| import { BigNumber } from 'ethers' | ||
| import { JsonRpcProvider } from '@ethersproject/providers' | ||
| import { providers } from 'ethers' | ||
|
|
||
| const FOURTEEN_DAYS_IN_SECONDS = 24 * 3600 * 14 | ||
|
|
||
| const BLOCK_SEARCH_THRESHOLD = 6 * 3600 | ||
| const searchForArbBlockByTimestamp = async ( | ||
| l2Provider: JsonRpcProvider, | ||
| timestamp: number, | ||
| ): Promise<number> => { | ||
| let step = 131072 | ||
| let block = await l2Provider.getBlock('latest') | ||
| while (block.timestamp > timestamp) { | ||
| while (block.number - step < 0) { | ||
| step = Math.round(step / 2) | ||
| } | ||
| block = await l2Provider.getBlock(block.number - step) | ||
| } | ||
| while (step > 1 && Math.abs(block.timestamp - timestamp) > BLOCK_SEARCH_THRESHOLD) { | ||
| step = Math.round(step / 2) | ||
| if (block.timestamp - timestamp > 0) { | ||
| block = await l2Provider.getBlock(block.number - step) | ||
| } else { | ||
| block = await l2Provider.getBlock(block.number + step) | ||
| } | ||
| } | ||
| return block.number | ||
| } | ||
|
|
||
| const wait = (ms: number): Promise<void> => { | ||
| return new Promise((res) => setTimeout(res, ms)) | ||
| } | ||
|
|
||
| const waitUntilOutboxEntryCreatedWithCb = async ( | ||
| msg: L2ToL1MessageWriter, | ||
| provider: providers.Provider, | ||
| retryDelay: number, | ||
| callback: () => void, | ||
| ) => { | ||
| let done = false | ||
| while (!done) { | ||
| const status = await msg.status(provider) | ||
| if (status == L2ToL1MessageStatus.CONFIRMED || status == L2ToL1MessageStatus.EXECUTED) { | ||
| done = true | ||
| } else { | ||
| callback() | ||
| await wait(retryDelay) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export const startSendToL1 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => { | ||
| logger.info(`>>> Sending tokens to L1 <<<\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 l1GRT = cli.contracts['GraphToken'] | ||
| const l1GRTAddress = l1GRT.address | ||
| const amount = toGRT(cliArgs.amount) | ||
| const recipient = cliArgs.recipient ? cliArgs.recipient : cli.wallet.address | ||
| const l2Wallet = cli.wallet.connect(l2Provider) | ||
| const l2AddressBook = getAddressBook(cliArgs.addressBook, l2ChainId.toString()) | ||
|
|
||
| const gateway = loadAddressBookContract('L2GraphTokenGateway', l2AddressBook, l2Wallet) | ||
| const l2GRT = loadAddressBookContract('L2GraphToken', l2AddressBook, l2Wallet) | ||
|
|
||
| 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 params = [l1GRTAddress, recipient, amount, '0x'] | ||
| logger.info('Approving token transfer') | ||
| await sendTransaction(l2Wallet, l2GRT, 'approve', [gateway.address, amount]) | ||
| logger.info('Sending outbound transfer transaction') | ||
| const receipt = await sendTransaction( | ||
| l2Wallet, | ||
| gateway, | ||
| 'outboundTransfer(address,address,uint256,bytes)', | ||
| params, | ||
| ) | ||
| const l2Receipt = new L2TransactionReceipt(receipt) | ||
| const l2ToL1Message = ( | ||
| await l2Receipt.getL2ToL1Messages(cli.wallet, await getL2Network(l2Provider)) | ||
| )[0] | ||
|
|
||
| logger.info(`The transaction generated an L2 to L1 message in outbox with eth block number:`) | ||
| logger.info(l2ToL1Message.event.ethBlockNum.toString()) | ||
| logger.info( | ||
| `After the dispute period is finalized (in ~1 week), you can finalize this by calling`, | ||
| ) | ||
| logger.info(`finish-send-to-l1 with the following txhash:`) | ||
| logger.info(l2Receipt.transactionHash) | ||
| } | ||
|
|
||
| export const finishSendToL1 = async ( | ||
| cli: CLIEnvironment, | ||
| cliArgs: CLIArgs, | ||
| wait: boolean, | ||
| ): Promise<void> => { | ||
| logger.info(`>>> Finishing transaction sending tokens to L1 <<<\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 l2AddressBook = getAddressBook(cliArgs.addressBook, l2ChainId.toString()) | ||
|
|
||
| const gateway = loadAddressBookContract( | ||
| 'L2GraphTokenGateway', | ||
| l2AddressBook, | ||
| l2Provider, | ||
| ) as L2GraphTokenGateway | ||
| let txHash: string | ||
| if (cliArgs.txHash) { | ||
| txHash = cliArgs.txHash | ||
| } else { | ||
| logger.info( | ||
| `Looking for withdrawals initiated by ${cli.wallet.address} in roughly the last 14 days`, | ||
| ) | ||
| const fromBlock = await searchForArbBlockByTimestamp( | ||
| l2Provider, | ||
| Math.round(Date.now() / 1000) - FOURTEEN_DAYS_IN_SECONDS, | ||
| ) | ||
| const filt = gateway.filters.WithdrawalInitiated(null, cli.wallet.address) | ||
| const allEvents = await gateway.queryFilter(filt, BigNumber.from(fromBlock).toHexString()) | ||
| if (allEvents.length == 0) { | ||
| throw new Error('No withdrawals found') | ||
| } | ||
| txHash = allEvents[allEvents.length - 1].transactionHash | ||
| } | ||
| logger.info(`Getting receipt from transaction ${txHash}`) | ||
| const receipt = await l2Provider.getTransactionReceipt(txHash) | ||
|
|
||
| const l2Receipt = new L2TransactionReceipt(receipt) | ||
| logger.info(`Getting L2 to L1 message...`) | ||
| const l2ToL1Message = ( | ||
| await l2Receipt.getL2ToL1Messages(cli.wallet, await getL2Network(l2Provider)) | ||
| )[0] | ||
|
|
||
| if (wait) { | ||
| const retryDelayMs = cliArgs.retryDelaySeconds ? cliArgs.retryDelaySeconds * 1000 : 60000 | ||
| logger.info('Waiting for outbox entry to be created, this can take a full week...') | ||
| await waitUntilOutboxEntryCreatedWithCb(l2ToL1Message, l2Provider, retryDelayMs, () => { | ||
| logger.info('Still waiting...') | ||
| }) | ||
| } else { | ||
| const status = await l2ToL1Message.status(l2Provider) | ||
| if (status == L2ToL1MessageStatus.EXECUTED) { | ||
| throw new Error('Message already executed!') | ||
| } else if (status != L2ToL1MessageStatus.CONFIRMED) { | ||
| throw new Error( | ||
| `Transaction is not confirmed, status is ${status} when it should be ${L2ToL1MessageStatus.CONFIRMED}. Has the dispute period passed?`, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| logger.info('Executing outbox transaction') | ||
| const tx = await l2ToL1Message.execute(l2Provider) | ||
| const outboxExecuteReceipt = await tx.wait() | ||
| logger.info('Transaction succeeded! tx hash:') | ||
| logger.info(outboxExecuteReceipt.transactionHash) | ||
| } | ||
|
|
||
| export const startSendToL1Command = { | ||
| command: 'start-send-to-l1 <amount> [recipient]', | ||
| describe: 'Start an L2-to-L1 Graph Token transaction', | ||
| handler: async (argv: CLIArgs): Promise<void> => { | ||
| return startSendToL1(await loadEnv(argv), argv) | ||
| }, | ||
| } | ||
|
|
||
| export const finishSendToL1Command = { | ||
| command: 'finish-send-to-l1 [txHash]', | ||
| describe: | ||
| 'Finish an L2-to-L1 Graph Token transaction. L2 dispute period must have completed. ' + | ||
| 'If txHash is not specified, the last withdrawal from the main account in the past 14 days will be redeemed.', | ||
| handler: async (argv: CLIArgs): Promise<void> => { | ||
| return finishSendToL1(await loadEnv(argv), argv, false) | ||
| }, | ||
| } | ||
|
|
||
| export const waitFinishSendToL1Command = { | ||
| command: 'wait-finish-send-to-l1 [txHash] [retryDelaySeconds]', | ||
| describe: | ||
| "Wait for an L2-to-L1 Graph Token transaction's dispute period to complete (which takes about a week), and then finalize it. " + | ||
| 'If txHash is not specified, the last withdrawal from the main account in the past 14 days will be redeemed.', | ||
| handler: async (argv: CLIArgs): Promise<void> => { | ||
| return finishSendToL1(await loadEnv(argv), argv, true) | ||
| }, | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.