Skip to content

feat: ERC20MigrationOutbox #339

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

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
44 changes: 44 additions & 0 deletions src/bridge/extra/ERC20MigrationOutbox.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.4;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Bridge} from "../IERC20Bridge.sol";
import {IERC20MigrationOutbox} from "./IERC20MigrationOutbox.sol";

/**
* @title An outbox for migrating nativeToken of a rollup from the native bridge to a new address
* @notice For some custom fee token orbit chains, they might want to have their native token being managed by an external bridge. This
* contract allow permissionless migration of the native bridge collateral, without requiring any change in the vanilla outbox.
* @dev This contract should be allowed as an outbox in conjunction with the vanilla outbox contract. Nonzero value withdrawal via the
* native bridge (ArbSys) must be disabled on the child chain or funds and messages will be stuck.
*/
contract ERC20MigrationOutbox is IERC20MigrationOutbox {
IERC20Bridge public immutable bridge;
address public immutable nativeToken;
address public immutable destination;

constructor(IERC20Bridge _bridge, address _destination) {
if (_destination == address(0)) {
revert InvalidDestination();
}
bridge = _bridge;
destination = _destination;
nativeToken = bridge.nativeToken();
}

/// @inheritdoc IERC20MigrationOutbox
function migrate() external {
uint256 bal = IERC20(nativeToken).balanceOf(address(bridge));
if (bal == 0) {
revert NoBalanceToMigrate();
}
(bool success, bytes memory returndata) = bridge.executeCall(destination, bal, "");
if (!success) {
revert MigrationFailed(returndata);
}
emit CollateralMigrated(destination, bal);
}
}
38 changes: 38 additions & 0 deletions src/bridge/extra/IERC20MigrationOutbox.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.4;

import {IERC20Bridge} from "../IERC20Bridge.sol";

interface IERC20MigrationOutbox {
/// @notice Thrown when there is no balance to migrate.
error NoBalanceToMigrate();

/// @notice Thrown when the migration process fails.
/// @param returndata The return data from the failed migration call.
error MigrationFailed(bytes returndata);

/// @notice Thrown when the destination address is invalid.
error InvalidDestination();

/// @notice Emitted when a migration is completed.
event CollateralMigrated(address indexed destination, uint256 amount);

/// @notice Returns the address of the bridge contract.
/// @return The IERC20Bridge contract address.
function bridge() external view returns (IERC20Bridge);

/// @notice Returns the address of the native token to be migrated.
/// @return The address of the native token.
function nativeToken() external view returns (address);

/// @notice Returns the destination address for the migration.
/// @return The address where the native token will be migrated to.
function destination() external view returns (address);

/// @notice Migrate the native token of the rollup to the destination address.
/// @dev Can be called by anyone. Reverts if there is no balance to migrate or if the migration fails.
function migrate() external;
}
59 changes: 59 additions & 0 deletions test/foundry/ERC20MigrationOutbox.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

import "forge-std/Test.sol";
import "./util/TestUtil.sol";
import "../../src/bridge/IERC20Bridge.sol";
import "../../src/bridge/ERC20Bridge.sol";
import "../../src/bridge/extra/ERC20MigrationOutbox.sol";
import {NoZeroTransferToken} from "./util/NoZeroTransferToken.sol";

contract ERC20MigrationOutboxTest is Test {
IERC20Bridge public bridge;

IERC20MigrationOutbox public erc20MigrationOutbox;
IERC20Bridge public erc20Bridge;
IERC20 public nativeToken;

address public user = address(100);
address public rollup = address(1000);
address public seqInbox = address(1001);
address public constant dst = address(1337);

function setUp() public {
// deploy token, bridge and erc20MigrationOutbox
nativeToken = new NoZeroTransferToken("Appchain Token", "App", 1_000_000, address(this));
bridge = IERC20Bridge(TestUtil.deployProxy(address(new ERC20Bridge())));
erc20Bridge = IERC20Bridge(address(bridge));

// init bridge
erc20Bridge.initialize(IOwnable(rollup), address(nativeToken));

// deploy erc20MigrationOutbox
erc20MigrationOutbox = new ERC20MigrationOutbox(bridge, dst);

// set outbox
vm.prank(rollup);
bridge.setOutbox(address(erc20MigrationOutbox), true);
}

function test_invalid_destination() public {
vm.expectRevert(IERC20MigrationOutbox.InvalidDestination.selector);
new ERC20MigrationOutbox(bridge, address(0));
}

function test_migrate() public {
nativeToken.transfer(address(bridge), 1000);

vm.prank(user);
erc20MigrationOutbox.migrate();

assertEq(nativeToken.balanceOf(dst), 1000);
}

function test_migrate_no_balance() public {
vm.expectRevert(IERC20MigrationOutbox.NoBalanceToMigrate.selector);
vm.prank(user);
erc20MigrationOutbox.migrate();
}
}
13 changes: 13 additions & 0 deletions test/signatures/ERC20MigrationOutbox
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

╭---------------+------------╮
| Method | Identifier |
+============================+
| bridge() | e78cea92 |
|---------------+------------|
| destination() | b269681d |
|---------------+------------|
| migrate() | 8fd3ab80 |
|---------------+------------|
| nativeToken() | e1758bd8 |
╰---------------+------------╯

2 changes: 1 addition & 1 deletion test/signatures/test-sigs.bash
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash
output_dir="./test/signatures"
for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox EdgeChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox BridgeCreator DeployHelper RollupCreator OneStepProofEntry CacheManager
for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox EdgeChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox BridgeCreator DeployHelper RollupCreator OneStepProofEntry CacheManager ERC20MigrationOutbox
do
echo "Checking for signature changes in $CONTRACTNAME"
[ -f "$output_dir/$CONTRACTNAME" ] && mv "$output_dir/$CONTRACTNAME" "$output_dir/$CONTRACTNAME-old"
Expand Down
6 changes: 6 additions & 0 deletions test/storage/ERC20MigrationOutbox
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

╭------+------+------+--------+-------+----------╮
| Name | Type | Slot | Offset | Bytes | Contract |
+================================================+
╰------+------+------+--------+-------+----------╯

2 changes: 1 addition & 1 deletion test/storage/test.bash
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash
output_dir="./test/storage"
for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox EdgeChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox OneStepProofEntry CacheManager
for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox EdgeChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox OneStepProofEntry CacheManager ERC20MigrationOutbox
do
echo "Checking storage change of $CONTRACTNAME"
[ -f "$output_dir/$CONTRACTNAME" ] && mv "$output_dir/$CONTRACTNAME" "$output_dir/$CONTRACTNAME-old"
Expand Down
Loading