Skip to content

Commit 0da1336

Browse files
gzeonethTucksonDev
andauthored
feat: ERC20MigrationOutbox (#339)
* feat: ERC20MigrationOutbox * test: ERC20MigrationOutbox * test: storage and 4bytes * chore: add natspec * test: new test * chore: update slither db --------- Co-authored-by: José FP <[email protected]>
1 parent be1b577 commit 0da1336

File tree

8 files changed

+163
-3
lines changed

8 files changed

+163
-3
lines changed

slither.db.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2021-2022, Offchain Labs, Inc.
2+
// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE
3+
// SPDX-License-Identifier: BUSL-1.1
4+
5+
pragma solidity ^0.8.4;
6+
7+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8+
import {IERC20Bridge} from "../IERC20Bridge.sol";
9+
import {IERC20MigrationOutbox} from "./IERC20MigrationOutbox.sol";
10+
11+
/**
12+
* @title An outbox for migrating nativeToken of a rollup from the native bridge to a new address
13+
* @notice For some custom fee token orbit chains, they might want to have their native token being managed by an external bridge. This
14+
* contract allow permissionless migration of the native bridge collateral, without requiring any change in the vanilla outbox.
15+
* @dev This contract should be allowed as an outbox in conjunction with the vanilla outbox contract. Nonzero value withdrawal via the
16+
* native bridge (ArbSys) must be disabled on the child chain or funds and messages will be stuck.
17+
*/
18+
contract ERC20MigrationOutbox is IERC20MigrationOutbox {
19+
IERC20Bridge public immutable bridge;
20+
address public immutable nativeToken;
21+
address public immutable destination;
22+
23+
constructor(IERC20Bridge _bridge, address _destination) {
24+
if (_destination == address(0)) {
25+
revert InvalidDestination();
26+
}
27+
bridge = _bridge;
28+
destination = _destination;
29+
nativeToken = bridge.nativeToken();
30+
}
31+
32+
/// @inheritdoc IERC20MigrationOutbox
33+
function migrate() external {
34+
uint256 bal = IERC20(nativeToken).balanceOf(address(bridge));
35+
if (bal == 0) {
36+
revert NoBalanceToMigrate();
37+
}
38+
(bool success, bytes memory returndata) = bridge.executeCall(destination, bal, "");
39+
if (!success) {
40+
revert MigrationFailed(returndata);
41+
}
42+
emit CollateralMigrated(destination, bal);
43+
}
44+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2021-2022, Offchain Labs, Inc.
2+
// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE
3+
// SPDX-License-Identifier: BUSL-1.1
4+
5+
pragma solidity ^0.8.4;
6+
7+
import {IERC20Bridge} from "../IERC20Bridge.sol";
8+
9+
interface IERC20MigrationOutbox {
10+
/// @notice Thrown when there is no balance to migrate.
11+
error NoBalanceToMigrate();
12+
13+
/// @notice Thrown when the migration process fails.
14+
/// @param returndata The return data from the failed migration call.
15+
error MigrationFailed(bytes returndata);
16+
17+
/// @notice Thrown when the destination address is invalid.
18+
error InvalidDestination();
19+
20+
/// @notice Emitted when a migration is completed.
21+
event CollateralMigrated(address indexed destination, uint256 amount);
22+
23+
/// @notice Returns the address of the bridge contract.
24+
/// @return The IERC20Bridge contract address.
25+
function bridge() external view returns (IERC20Bridge);
26+
27+
/// @notice Returns the address of the native token to be migrated.
28+
/// @return The address of the native token.
29+
function nativeToken() external view returns (address);
30+
31+
/// @notice Returns the destination address for the migration.
32+
/// @return The address where the native token will be migrated to.
33+
function destination() external view returns (address);
34+
35+
/// @notice Migrate the native token of the rollup to the destination address.
36+
/// @dev Can be called by anyone. Reverts if there is no balance to migrate or if the migration fails.
37+
function migrate() external;
38+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.4;
3+
4+
import "forge-std/Test.sol";
5+
import "./util/TestUtil.sol";
6+
import "../../src/bridge/IERC20Bridge.sol";
7+
import "../../src/bridge/ERC20Bridge.sol";
8+
import "../../src/bridge/extra/ERC20MigrationOutbox.sol";
9+
import {NoZeroTransferToken} from "./util/NoZeroTransferToken.sol";
10+
11+
contract ERC20MigrationOutboxTest is Test {
12+
IERC20Bridge public bridge;
13+
14+
IERC20MigrationOutbox public erc20MigrationOutbox;
15+
IERC20Bridge public erc20Bridge;
16+
IERC20 public nativeToken;
17+
18+
address public user = address(100);
19+
address public rollup = address(1000);
20+
address public seqInbox = address(1001);
21+
address public constant dst = address(1337);
22+
23+
function setUp() public {
24+
// deploy token, bridge and erc20MigrationOutbox
25+
nativeToken = new NoZeroTransferToken("Appchain Token", "App", 1_000_000, address(this));
26+
bridge = IERC20Bridge(TestUtil.deployProxy(address(new ERC20Bridge())));
27+
erc20Bridge = IERC20Bridge(address(bridge));
28+
29+
// init bridge
30+
erc20Bridge.initialize(IOwnable(rollup), address(nativeToken));
31+
32+
// deploy erc20MigrationOutbox
33+
erc20MigrationOutbox = new ERC20MigrationOutbox(bridge, dst);
34+
35+
// set outbox
36+
vm.prank(rollup);
37+
bridge.setOutbox(address(erc20MigrationOutbox), true);
38+
}
39+
40+
function test_invalid_destination() public {
41+
vm.expectRevert(IERC20MigrationOutbox.InvalidDestination.selector);
42+
new ERC20MigrationOutbox(bridge, address(0));
43+
}
44+
45+
function test_migrate() public {
46+
nativeToken.transfer(address(bridge), 1000);
47+
48+
vm.prank(user);
49+
erc20MigrationOutbox.migrate();
50+
51+
assertEq(nativeToken.balanceOf(dst), 1000);
52+
}
53+
54+
function test_migrate_no_balance() public {
55+
vm.expectRevert(IERC20MigrationOutbox.NoBalanceToMigrate.selector);
56+
vm.prank(user);
57+
erc20MigrationOutbox.migrate();
58+
}
59+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
╭---------------+------------╮
3+
| Method | Identifier |
4+
+============================+
5+
| bridge() | e78cea92 |
6+
|---------------+------------|
7+
| destination() | b269681d |
8+
|---------------+------------|
9+
| migrate() | 8fd3ab80 |
10+
|---------------+------------|
11+
| nativeToken() | e1758bd8 |
12+
╰---------------+------------╯
13+

test/signatures/test-sigs.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
22
output_dir="./test/signatures"
3-
for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox EdgeChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox BridgeCreator DeployHelper RollupCreator OneStepProofEntry CacheManager
3+
for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox EdgeChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox BridgeCreator DeployHelper RollupCreator OneStepProofEntry CacheManager ERC20MigrationOutbox
44
do
55
echo "Checking for signature changes in $CONTRACTNAME"
66
[ -f "$output_dir/$CONTRACTNAME" ] && mv "$output_dir/$CONTRACTNAME" "$output_dir/$CONTRACTNAME-old"

test/storage/ERC20MigrationOutbox

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
╭------+------+------+--------+-------+----------╮
3+
| Name | Type | Slot | Offset | Bytes | Contract |
4+
+================================================+
5+
╰------+------+------+--------+-------+----------╯
6+

test/storage/test.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
22
output_dir="./test/storage"
3-
for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox EdgeChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox OneStepProofEntry CacheManager
3+
for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox EdgeChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox OneStepProofEntry CacheManager ERC20MigrationOutbox
44
do
55
echo "Checking storage change of $CONTRACTNAME"
66
[ -f "$output_dir/$CONTRACTNAME" ] && mv "$output_dir/$CONTRACTNAME" "$output_dir/$CONTRACTNAME-old"

0 commit comments

Comments
 (0)