Skip to content

Commit

Permalink
Arbitrum -> Mainnet ERC20 Bridge (bgd-labs#209)
Browse files Browse the repository at this point in the history
* feat: update bridge owner and README as well as add missing natspec

* chore: update inherit doc

* feat: add support for multiple proof burns

* feat: scaffold arb bridge

* chore: add missing natspec

* feat: add isTokenMapped function

* feat: add bridge for arbitrum

* Update README.md

* feat: add function to exit

* chore: scaffold opt

* feat: check against 0xeee

* chore: add TXs to README

* chore: add who can rescue

* chore: add tx to exit

* feat: forward eth

* feat: get gateway from token

* feat: make gateway a parameter

* chore: add test scripts

* feat: finish tests for arbitrum bridge

* chore: add README

* chore: move to own folder

* chore: cleanup

* chore: add missing natspec

* feat: update natspec

* chore: update test

* chore: add indexed

* improve natspec

* feat: update readme

* chore: update with details

* chore: update test

---------

Co-authored-by: Fermin 'Piscu' Carranza <fermin@llama.xyz>
  • Loading branch information
2 people authored and Luigy-Lemon committed Aug 6, 2024
1 parent 6cf1999 commit 842d7e9
Show file tree
Hide file tree
Showing 14 changed files with 604 additions and 64 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,5 @@ git-diff :

# Voting scripts
vote :; forge script scripts/VotingScripts.s.sol:VoteForProposal --rpc-url mainnet --broadcast --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv ${proposalId} ${support}

deploy-pk :; forge script ${contract} --rpc-url ${chain} $(if ${dry},--sender 0x25F2226B597E8F9514B3F68F00f494cF4f286491 -vvvv,--broadcast --private-key ${PRIVATE_KEY} --verify -vvvv --slow)
2 changes: 1 addition & 1 deletion lib/aave-address-book
Submodule aave-address-book updated 97 files
+32 −0 CHANGELOG.md
+1 −0 assets/aToken/bnbx.svg
+1 −0 assets/aToken/ethx.svg
+1 −0 assets/aToken/oseth.svg
+1 −0 assets/aToken/sfrxeth.svg
+1 −0 assets/aToken/steur.svg
+1 −0 assets/aToken/susde.svg
+1 −0 assets/aToken/usde.svg
+1 −0 assets/aToken/wbeth.svg
+1 −35 assets/aToken/weeth.svg
+1 −0 assets/stataToken/bnbx.svg
+1 −0 assets/stataToken/ethx.svg
+1 −0 assets/stataToken/oseth.svg
+1 −0 assets/stataToken/sfrxeth.svg
+1 −0 assets/stataToken/steur.svg
+1 −0 assets/stataToken/susde.svg
+1 −0 assets/stataToken/usde.svg
+1 −0 assets/stataToken/wbeth.svg
+1 −202 assets/stataToken/weeth.svg
+1 −0 assets/underlying/bnbx.svg
+1 −0 assets/underlying/ethx.svg
+1 −1 assets/underlying/eura.svg
+1 −0 assets/underlying/oseth.svg
+1 −0 assets/underlying/sfrxeth.svg
+1 −0 assets/underlying/steur.svg
+1 −0 assets/underlying/susde.svg
+1 −0 assets/underlying/usde.svg
+1 −0 assets/underlying/wbeth.svg
+1 −22 assets/underlying/weeth.svg
+3 −2 package.json
+1 −4 scripts/configs/abis.ts
+1 −0 scripts/configs/networks/arbitrum.ts
+1 −0 scripts/configs/networks/avalanche.ts
+1 −0 scripts/configs/networks/base.ts
+1 −0 scripts/configs/networks/bnb.ts
+1 −0 scripts/configs/networks/ethereum.ts
+1 −0 scripts/configs/networks/optimism.ts
+1 −0 scripts/configs/networks/polygon.ts
+2 −1 scripts/configs/pools/arbitrum.ts
+1 −0 scripts/configs/types.ts
+6 −6 src/AaveV2Avalanche.sol
+18 −18 src/AaveV2Ethereum.sol
+20 −20 src/AaveV2Polygon.sol
+21 −18 src/AaveV3Arbitrum.sol
+10 −10 src/AaveV3Avalanche.sol
+6 −6 src/AaveV3BNB.sol
+25 −4 src/AaveV3Base.sol
+59 −17 src/AaveV3Ethereum.sol
+6 −6 src/AaveV3Gnosis.sol
+14 −14 src/AaveV3Optimism.sol
+16 −16 src/AaveV3Polygon.sol
+2 −2 src/AaveV3Scroll.sol
+3 −0 src/MiscArbitrum.sol
+3 −0 src/MiscAvalanche.sol
+3 −0 src/MiscBNB.sol
+3 −0 src/MiscBase.sol
+3 −0 src/MiscEthereum.sol
+3 −0 src/MiscOptimism.sol
+3 −0 src/MiscPolygon.sol
+1 −1 src/common/IStakeToken.sol
+1 −1 src/ts/AaveAddressBook.ts
+3 −3 src/ts/AaveV2Avalanche.ts
+10 −10 src/ts/AaveV2Ethereum.ts
+10 −10 src/ts/AaveV2Polygon.ts
+12 −9 src/ts/AaveV3Arbitrum.ts
+5 −5 src/ts/AaveV3Avalanche.ts
+3 −3 src/ts/AaveV3BNB.ts
+11 −2 src/ts/AaveV3Base.ts
+27 −9 src/ts/AaveV3Ethereum.ts
+3 −3 src/ts/AaveV3Gnosis.ts
+7 −7 src/ts/AaveV3Optimism.ts
+8 −8 src/ts/AaveV3Polygon.ts
+1 −1 src/ts/AaveV3Scroll.ts
+3 −0 src/ts/MiscArbitrum.ts
+3 −0 src/ts/MiscAvalanche.ts
+3 −0 src/ts/MiscBNB.ts
+3 −0 src/ts/MiscBase.ts
+3 −0 src/ts/MiscEthereum.ts
+3 −0 src/ts/MiscOptimism.ts
+3 −0 src/ts/MiscPolygon.ts
+0 −153 src/ts/abis/IAggregatedStakeToken.ts
+60 −103 src/ts/abis/IGovernanceDataHelper.ts
+12 −20 src/ts/abis/IMetaDelegateHelper.ts
+35 −59 src/ts/abis/IPayloadsControllerDataHelper.ts
+768 −1,275 src/ts/abis/IStakeToken.ts
+64 −83 src/ts/abis/IStaticATokenFactory.ts
+463 −631 src/ts/abis/IStaticATokenLM.ts
+103 −183 src/ts/abis/IUiPoolDataProvider.ts
+30 −52 src/ts/abis/IVotingMachineDataHelper.ts
+73 −7 src/ts/tokenlist.ts
+73 −7 tokenlist.json
+64 −57 ui/package-lock.json
+2 −2 ui/package.json
+72 −11 ui/src/app/page.tsx
+45 −25 ui/src/components/Search.tsx
+31 −18 ui/src/components/SearchResult.tsx
+2 −2 ui/src/types.ts
2 changes: 1 addition & 1 deletion lib/solidity-utils
27 changes: 24 additions & 3 deletions scripts/DeployBridges.s.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {EthereumScript, PolygonScript} from 'src/ScriptUtils.sol';
import {AavePolEthERC20Bridge} from 'src/bridges/AavePolEthERC20Bridge.sol';
import {AavePolEthPlasmaBridge} from 'src/bridges/polygon/AavePolEthPlasmaBridge.sol';
import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
import {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol';
import {GovernanceV3Arbitrum} from 'aave-address-book/GovernanceV3Arbitrum.sol';

import {AaveV3Arbitrum, AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';

import {ArbitrumScript, EthereumScript, PolygonScript} from 'src/ScriptUtils.sol';
import {AaveArbEthERC20Bridge} from 'src/bridges/arbitrum/AaveArbEthERC20Bridge.sol';
import {AavePolEthERC20Bridge} from 'src/bridges/polygon/AavePolEthERC20Bridge.sol';
import {AavePolEthPlasmaBridge} from 'src/bridges/polygon/AavePolEthPlasmaBridge.sol';

contract DeployEthereum is EthereumScript {
function run() external broadcast {
Expand Down Expand Up @@ -35,3 +42,17 @@ contract DeployPlasmaPolygon is PolygonScript {
new AavePolEthPlasmaBridge{salt: salt}(0x3765A685a401622C060E5D700D9ad89413363a91);
}
}

contract DeployArbBridgeEthereum is EthereumScript {
function run() external broadcast {
bytes32 salt = 'Aave Treasury Bridge';
new AaveArbEthERC20Bridge{salt: salt}(0x3765A685a401622C060E5D700D9ad89413363a91);
}
}

contract DeployArbBridgeArbitrum is ArbitrumScript {
function run() external broadcast {
bytes32 salt = 'Aave Treasury Bridge';
new AaveArbEthERC20Bridge{salt: salt}(0x3765A685a401622C060E5D700D9ad89413363a91);
}
}
116 changes: 116 additions & 0 deletions src/bridges/arbitrum/AaveArbEthERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
import {Ownable} from 'solidity-utils/contracts/oz-common/Ownable.sol';
import {Rescuable} from 'solidity-utils/contracts/utils/Rescuable.sol';
import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol';

import {ChainIds} from '../../ChainIds.sol';
import {IAaveArbEthERC20Bridge} from './IAaveArbEthERC20Bridge.sol';

/// @notice The L1 Outbox to exit a bridge transaction on Mainnet
interface IL1Outbox {
/// @notice Executes a transaction by providing a generated proof
/// @param proof The proof to exit with
/// @param index The index of the transaction in the block
/// @param l2sender The executor of the L2 transaction
/// @param to The L1 gateway address that the L2 transaction was sent to
/// @param l2block The L2 block where the transaction took place
/// @param l1block The L1 block where the transaction took place
/// @param l2timestamp The L2 timestamp when the transaction took place
/// @param value The value sent with the transaction
/// @param data Any extra data sent with the transaction
function executeTransaction(
bytes32[] calldata proof,
uint256 index,
address l2sender,
address to,
uint256 l2block,
uint256 l1block,
uint256 l2timestamp,
uint256 value,
bytes calldata data
) external;
}

/// @notice L2 Gateway to initiate bridge
interface IL2Gateway {
/// @notice Executes a burn transaction to initiate a bridge
/// @param tokenAddress The L11 address of the token to burn
/// @param recipient Receiver of the bridged tokens
/// @param amount The amount of tokens to bridge
/// @param data Any extra data to include in the burn transaction
function outboundTransfer(
address tokenAddress,
address recipient,
uint256 amount,
bytes calldata data
) external;
}

/// @author efecarranza.eth
/// @notice Contract to bridge ERC20 tokens from Arbitrum to Mainnet
contract AaveArbEthERC20Bridge is Ownable, Rescuable, IAaveArbEthERC20Bridge {
using SafeERC20 for IERC20;

/// @inheritdoc IAaveArbEthERC20Bridge
address public constant MAINNET_OUTBOX = 0x0B9857ae2D4A3DBe74ffE1d7DF045bb7F96E4840;

/// @param _owner The owner of the contract upon deployment
constructor(address _owner) {
_transferOwnership(_owner);
}

/// @inheritdoc IAaveArbEthERC20Bridge
function bridge(
address token,
address l1Token,
address gateway,
uint256 amount
) external onlyOwner {
if (block.chainid != ChainIds.ARBITRUM) revert InvalidChain();

IERC20(token).forceApprove(gateway, amount);

IL2Gateway(gateway).outboundTransfer(l1Token, address(AaveV3Ethereum.COLLECTOR), amount, '');

emit Bridge(token, amount);
}

/// @inheritdoc IAaveArbEthERC20Bridge
function exit(
bytes32[] calldata proof,
uint256 index,
address l2sender,
address destinationGateway,
uint256 l2block,
uint256 l1block,
uint256 l2timestamp,
uint256 value,
bytes calldata data
) external {
if (block.chainid != ChainIds.MAINNET) revert InvalidChain();

IL1Outbox(MAINNET_OUTBOX).executeTransaction(
proof,
index,
l2sender,
destinationGateway,
l2block,
l1block,
l2timestamp,
value,
data
);

emit Exit(l2sender, destinationGateway, l2block, l1block, value, data);
}

/// @inheritdoc Rescuable
function whoCanRescue() public view override returns (address) {
return owner();
}
}
63 changes: 63 additions & 0 deletions src/bridges/arbitrum/IAaveArbEthERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IAaveArbEthERC20Bridge {
/// @notice This function is not supported on this chain
error InvalidChain();

/// @notice Emitted when bridging a token from Arbitrum to Mainnet
event Bridge(address indexed token, uint256 amount);

/// @notice Emitted when finalizing the transfer on Mainnet
/// @param l2sender The address sending the transaction from the L2
/// @param to The address receiving the bridged funds
/// @param l2block The block number of the transaction on the L2
/// @param l1block The block number of the transaction on the L1
/// @param value The value being bridged from the L2
/// @param data Data being sent from the L2
event Exit(
address l2sender,
address to,
uint256 l2block,
uint256 l1block,
uint256 value,
bytes data
);

/// @notice Returns the address of the Mainnet contract to exit the bridge from
function MAINNET_OUTBOX() external view returns (address);

/// This function withdraws an ERC20 token from Arbitrum to Mainnet. exit() needs
/// to be called on mainnet with the corresponding burnProof in order to complete.
/// @notice Arbitrum only. Function will revert if called from other network.
/// @param token Arbitrum address of ERC20 token to withdraw.
/// @param l1token Mainnet address of ERC20 token to withdraw.
/// @param gateway The L2 gateway address to bridge through
/// @param amount Amount of tokens to withdraw
function bridge(address token, address l1token, address gateway, uint256 amount) external;

/// This function completes the withdrawal process from Arbitrum to Mainnet.
/// Burn proof is generated via API. Please see README.md
/// @notice Mainnet only. Function will revert if called from other network.
/// @param proof[] Burn proof generated via API.
/// @param index The index of the burn transaction.
/// @param l2sender The address sending the transaction from the L2
/// @param destinationGateway The L1 gateway address receiving the bridged funds
/// @param l2block The block number of the transaction on the L2
/// @param l1block The block number of the transaction on the L1
/// @param l2timestamp The timestamp of the transaction on the L2
/// @param value The value being bridged from the L2
/// @param data Data being sent from the L2
function exit(
bytes32[] calldata proof,
uint256 index,
address l2sender,
address destinationGateway,
uint256 l2block,
uint256 l1block,
uint256 l2timestamp,
uint256 value,
bytes calldata data
) external;
}
84 changes: 84 additions & 0 deletions src/bridges/arbitrum/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Aave Arbitrum -> Mainnet ERC20 Bridge

Arbitrum does offer a way to bridge directly from their network to Mainnet using their standard ArbERC20 tokens. There is a 'gateway' address that is used to bridge to mainnet. It can be called from the tokens themselves, however, it requires some domain knowledge. This contract facilitates the bridging of tokens in order to make managing Aave DAO's treasury easier.

The same contract exists on both chains with the same address, so this contract will receive funds from the Arbitrum Collector, then call to bridge the received tokens. After the Arbitrum rollup happens, the "burn proof" will be generated via API. At this point, the mainnet contract can be called. `exit()` will give the mainnet collector contract the tokens that were bridged over from Arbitrum.

In order to generate the proof for an exit, as well as the other required data, please install the `aave-cli` tool which can be found [here](https://github.com/bgd-labs/aave-cli).

In order to generate the proof, run the following command:

`aave-cli arbitrum-bridge-exit [TX_HASH] [INDEX] [ARBITRUM_BLOCK_OF_TX]`

Where the TX_HASH is the hash where the bridge was initiated on Arbitrum, the INDEX is the withdrawal index (ie: if there are 3 tokens bridged in the same transaction, the indexes will be 0, 1 and 2), and ARBITRUM_BLOCK_OF_TX, which is the block the bridge transaction happened at.

## Functions

```
function bridge(
address token,
address l1Token,
address gateway,
uint256 amount
) external;
```

Callable on Arbitrum to withdraw ERC20 token. It withdraws `amount` of passed `token` to mainnet. The gateway address can be found either in the token's read methods where it's called `l1gateway()` or `gateway()`, depending on the token. You can also refer to the Arbitrum [docs](https://docs.arbitrum.io/build-decentralized-apps/token-bridging/token-bridge-erc20#other-flavors-of-gateways) and the relevant [addresses](https://docs.arbitrum.io/build-decentralized-apps/reference/useful-addresses).

```
function exit(
bytes32[] calldata proof,
uint256 index,
address l2sender,
address to,
uint256 l2block,
uint256 l1block,
uint256 l2timestamp,
uint256 value,
bytes calldata data
) external
```

Callable on Mainnet to finish the withdrawal process. Callable ~7 days after `bridge()` is called and proof is available via `aave-cli`. Arbitrum currently doesn't support getting this information via API (we have requested this feature).

`function emergencyTokenTransfer(address erc20Token, address to, uint256 amount) external;`

Callable on Arbitrum. Withdraws tokens from bridge contract back to Aave Collector on Arbitrum.

## Burn Proof Generation

After you have called `bridge()` Arbitrum, it will take ~7 days for the withdrawal to be available (it's a rollup solution after all). Once it's available, a user can withdraw on Mainnet.

Here's a sample transaction: https://arbiscan.io/tx/0x726c0b903d77088af36e06dfe6fd40df318ba83b8a93726fa30fab018cb43357

https://arbiscan.io/tx/0x726c0b903d77088af36e06dfe6fd40df318ba83b8a93726fa30fab018cb43357#eventlog

The relevant log in this example is #7, from address `0x0000000000000000000000000000000000000064` which is an Arbitrum pre-compiled contract. Foundry does not currently support pre-compiled contracts in any networks other than Mainnet, so unfortunately a script cannot be used to run test commands. You can use the arbiscan.io UI as an alternative.

The exit transaction is here: https://etherscan.io/tx/0xa34c3725cc95773eedf96b03e9672ad77940b27fc5b1b94441e6587dec014ecd

You can see the input data which was retrieved via the Aave CLI tool.

## Deployed Addresses

Mainnet: [0x0335ffa9af5ce05590d6c9a75b645470e07744a9](https://etherscan.io/address/0x0335ffa9af5ce05590d6c9a75b645470e07744a9)
Arbitrum: [0x0335ffa9af5ce05590d6c9a75b645470e07744a9](https://arbiscan.io/address/0x0335ffa9af5ce05590d6c9a75b645470e07744a9)

Confirmed Bridges:

| Token | Can Bridge | Burn | Exit |
| ---------------- | ---------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| USDC (Native) | NO | N/A | N/A |
| USDC.e (Bridged) | YES | [Tx](https://arbiscan.io/tx/0x4d64e1200e55d745428af2ad57af72c345dd47e736227b8ad1fefc9a6dde0bbe) | [Tx](https://etherscan.io/tx/0x43f861ded0892ea3f67bba45b331d3fc816546d1a6a4de59dce455d37d15c2e3) |
| WETH | YES | [Tx](https://arbiscan.io/tx/0xa466214026874d294dc1b2ec188ce29f44eda24917729841b96c9dbd53be3f4b) | [Tx](https://etherscan.io/tx/0x082ac47de76e638afd89f0c0dc9dd6a79f0bec61daa5f9280842fbfd583d18e5) |
| WBTC | YES | [Tx](https://arbiscan.io/tx/0x3f05e30984c67b21a9bce4866336bf0da6f90a29a9346f1f121f5adeb773c3df) | [Tx](https://etherscan.io/tx/0x0e8875142024a5243d48262b12df051eec32c04d5bf9512b0f92c7c8e27cecb8) |
| wstETH | NO | N/A | N/A |
| DAI | YES | [Tx](https://arbiscan.io/tx/0x1ce3cf0f0e6dc01fc2e78105cd3c0a24b3d517cef83b8e54c8321cdd177381c6) | [Tx](https://etherscan.io/tx/0xb2901150d654c2751d9624afbaf70386d38415d47e8b4fee512c09ea7503e38f) |
| EURS | NO | N/A | N/A |
| AAVE | YES | [Tx]() | [Tx]() |
| MAI | YES | [Tx]() | [Tx]() |
| rETH | YES | [Tx]() | [Tx]() |
| LUSD | YES | [Tx]() | [Tx]() |
| FRAX | YES | [Tx]() | [Tx]() |
| ARB | YES | [Tx]() | [Tx]() |
| weETH | YES | [Tx]() | [Tx]() |
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {Rescuable} from 'solidity-utils/contracts/utils/Rescuable.sol';
import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol';
import {AaveV2Polygon} from 'aave-address-book/AaveV2Polygon.sol';

import {ChainIds} from '../ChainIds.sol';
import {ChainIds} from '../../ChainIds.sol';
import {IAavePolEthERC20Bridge} from './IAavePolEthERC20Bridge.sol';

interface IRootChainManager {
Expand Down
File renamed without changes.
Loading

0 comments on commit 842d7e9

Please sign in to comment.