Skip to content
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

test: add superchain erc20 bridge tests #65

Merged
merged 6 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@
"sourceCodeHash": "0x5e58b7c867fafa49fe39d68d83875425e9cf94f05f2835bdcdaa08fc8bc6b68e"
},
"src/L2/OptimismSuperchainERC20Factory.sol": {
"initCodeHash": "0x98011045722178751e4a1112892f7d9a11bc1f5e42ac18205b6d30a1f1476d24",
"sourceCodeHash": "0x9e72b2a77d82fcf3963734232ba9faff9d63962594a032041c2561f0a9f1b0b5"
"initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053",
"sourceCodeHash": "0x3dc742f350bf100f92fd32769c99668cddf1f9cf6782dcff0bb1243b7c7ed186"
},
"src/L2/SequencerFeeVault.sol": {
"initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74",
Expand All @@ -136,8 +136,8 @@
"sourceCodeHash": "0xb11ce94fd6165d8ca86eebafc7235e0875380d1a5d4e8b267ff0c6477083b21c"
},
"src/L2/SuperchainWETH.sol": {
"initCodeHash": "0x702ff6dc90e7e02085e95e3510590cce9bf44a7ea06bfbb8f7a47e203a8809b2",
"sourceCodeHash": "0x823ded4da0dc1f44bc87b5e46d0a1c90c76f76e0f36c294c5410c4755886c925"
"initCodeHash": "0xe87f7012ac3050250d6cc6ca371ec09ec888b991603e531e3a97485c751d586b",
"sourceCodeHash": "0x720f4a6f4157558214c9943c49dc28702b7695b91ab1413a4f1c0ca216a97877"
},
"src/L2/WETH.sol": {
"initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol";
import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
Expand All @@ -13,7 +13,7 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol";
/// @title OptimismSuperchainERC20Factory
/// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies
/// using CREATE3.
contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver {
contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISemver {
/// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address.
/// This is used to keep track of the token deployments.
mapping(address superchainToken => address remoteToken) public deployments;
Expand All @@ -27,8 +27,8 @@ contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver {
);

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1
string public constant version = "1.0.0-beta.1";
/// @custom:semver 1.0.0-beta.3
string public constant version = "1.0.0-beta.3";
0xDiscotech marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3.
/// @param _remoteToken Address of the remote token.
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/src/L2/SuperchainWETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
/// do not use a custom gas token.
contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver {
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.5
string public constant version = "1.0.0-beta.5";
/// @custom:semver 1.0.0-beta.6
string public constant version = "1.0.0-beta.6";

/// @inheritdoc WETH98
function deposit() public payable override {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

/// @title IOptimismSuperchainERC20Factory
/// @notice Interface for OptimismSuperchainERC20Factory.
interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory {
/// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3.
/// @param _remoteToken Address of the remote token.
/// @param _name Name of the OptimismSuperchainERC20.
/// @param _symbol Symbol of the OptimismSuperchainERC20.
/// @param _decimals Decimals of the OptimismSuperchainERC20.
/// @return _superchainERC20 Address of the OptimismSuperchainERC20 deployment.
function deploy(
address _remoteToken,
string memory _name,
string memory _symbol,
uint8 _decimals
)
external
returns (address _superchainERC20);
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
pragma solidity 0.8.15;

// Testing utilities
import { Test } from "forge-std/Test.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";

// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol";
import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol";

// Target contract
import { OptimismSuperchainERC20Factory, OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20Factory.sol";
import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol";
import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol";

/// @title OptimismSuperchainERC20FactoryTest
/// @notice Contract for testing the OptimismSuperchainERC20Factory contract.
contract OptimismSuperchainERC20FactoryTest is Test {
contract OptimismSuperchainERC20FactoryTest is Bridge_Initializer {
using Bytes32AddressLib for bytes32;

OptimismSuperchainERC20 public superchainERC20Impl;
OptimismSuperchainERC20Factory public superchainERC20Factory;
event OptimismSuperchainERC20Created(
address indexed superchainToken, address indexed remoteToken, address deployer
);

/// @notice Sets up the test suite.
function setUp() public {
superchainERC20Impl = new OptimismSuperchainERC20();

// Deploy the OptimismSuperchainERC20Beacon contract
_deployBeacon();

superchainERC20Factory = new OptimismSuperchainERC20Factory();
}

/// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract
function _deployBeacon() internal {
// Deploy the OptimismSuperchainERC20Beacon implementation
address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON;
address _impl = Predeploys.predeployToCodeNamespace(_addr);
vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon"));

// Deploy the ERC1967Proxy contract at the Predeploy
bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy");
vm.etch(_addr, code);
EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN);
EIP1967Helper.setImplementation(_addr, _impl);

// Mock implementation address
vm.mockCall(
_impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl))
);
function setUp() public override {
super.enableInterop();
super.setUp();
}

/// @notice Test that calling `deploy` with valid parameters succeeds.
Expand All @@ -62,22 +39,22 @@ contract OptimismSuperchainERC20FactoryTest is Test {
{
// Arrange
bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals));
address deployment = _calculateTokenAddress(salt, address(superchainERC20Factory));
address deployment = _calculateTokenAddress(salt, address(l2OptimismSuperchainERC20Factory));

vm.expectEmit(address(superchainERC20Factory));
emit OptimismSuperchainERC20Factory.OptimismSuperchainERC20Created(deployment, _remoteToken, _caller);
vm.expectEmit(address(l2OptimismSuperchainERC20Factory));
emit OptimismSuperchainERC20Created(deployment, _remoteToken, _caller);

// Act
vm.prank(_caller);
address addr = superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
address addr = l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);

// Assert
assertTrue(addr == deployment);
assertTrue(OptimismSuperchainERC20(deployment).decimals() == _decimals);
assertTrue(OptimismSuperchainERC20(deployment).remoteToken() == _remoteToken);
assertEq(OptimismSuperchainERC20(deployment).name(), _name);
assertEq(OptimismSuperchainERC20(deployment).symbol(), _symbol);
assertEq(superchainERC20Factory.deployments(deployment), _remoteToken);
assertTrue(IOptimismSuperchainERC20(deployment).decimals() == _decimals);
assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken);
assertEq(IOptimismSuperchainERC20(deployment).name(), _name);
assertEq(IOptimismSuperchainERC20(deployment).symbol(), _symbol);
assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken);
}

/// @notice Test that calling `deploy` with the same parameters twice reverts.
Expand All @@ -92,13 +69,13 @@ contract OptimismSuperchainERC20FactoryTest is Test {
{
// Arrange
vm.prank(_caller);
superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);

vm.expectRevert(bytes("DEPLOYMENT_FAILED"));

// Act
vm.prank(_caller);
superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
}

/// @notice Precalculates the address of the token contract using CREATE3.
Expand Down
179 changes: 179 additions & 0 deletions packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

// Testing utilities
import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";

// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";

// Target contract
import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol";
import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol";
import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol";

/// @title SuperchainERC20BridgeTest
/// @notice Contract for testing the SuperchainERC20Bridge contract.
contract SuperchainERC20BridgeTest is Bridge_Initializer {
address internal constant ZERO_ADDRESS = address(0);
string internal constant NAME = "SuperchainERC20";
string internal constant SYMBOL = "SCE";
address internal constant REMOTE_TOKEN = address(0x123);

event Transfer(address indexed from, address indexed to, uint256 value);

event SendERC20(
address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination
);

event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source);

IOptimismSuperchainERC20 public superchainERC20;

/// @notice Sets up the test suite.
function setUp() public override {
super.enableInterop();
super.setUp();

superchainERC20 = IOptimismSuperchainERC20(
IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deploy(
REMOTE_TOKEN, NAME, SYMBOL, 18
)
);
}

/// @notice Helper function to setup a mock and expect a call to it.
function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal {
vm.mockCall(_receiver, _calldata, _returned);
vm.expectCall(_receiver, _calldata);
}

/// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20`
/// event.
function testFuzz_sendERC20_succeeds(address _sender, address _to, uint256 _amount, uint256 _chainId) external {
// Ensure `_sender` is not the zero address
vm.assume(_sender != ZERO_ADDRESS);

// Mint some tokens to the sender so then they can be sent
vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE);
superchainERC20.mint(_sender, _amount);

// Get the total supply and balance of `_sender` before the send to compare later on the assertions
uint256 _totalSupplyBefore = superchainERC20.totalSupply();
uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender);

// Look for the emit of the `Transfer` event
vm.expectEmit(address(superchainERC20));
emit Transfer(_sender, ZERO_ADDRESS, _amount);

// Look for the emit of the `SendERC20` event
vm.expectEmit(address(superchainERC20Bridge));
emit SendERC20(address(superchainERC20), _sender, _to, _amount, _chainId);

// Mock the call over the `sendMessage` function and expect it to be called properly
bytes memory _message =
abi.encodeCall(superchainERC20Bridge.relayERC20, (address(superchainERC20), _sender, _to, _amount));
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeWithSelector(
IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20Bridge), _message
),
abi.encode("")
);

// Call the `sendERC20` function
vm.prank(_sender);
superchainERC20Bridge.sendERC20(address(superchainERC20), _to, _amount, _chainId);

// Check the total supply and balance of `_sender` after the send were updated correctly
assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount);
assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount);
}

/// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger.
function testFuzz_relayERC20_notMessenger_reverts(
address _token,
address _caller,
address _to,
uint256 _amount
)
public
{
// Ensure the caller is not the messenger
vm.assume(_caller != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);

// Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector
vm.expectRevert(ISuperchainERC20Bridge.CallerNotL2ToL2CrossDomainMessenger.selector);

// Call the `relayERC20` function with the non-messenger caller
vm.prank(_caller);
superchainERC20Bridge.relayERC20(_token, _caller, _to, _amount);
}

/// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not
/// the same SuperchainERC20Bridge.
function testFuzz_relayERC20_notCrossDomainSender_reverts(
address _token,
address _crossDomainMessageSender,
address _to,
uint256 _amount
)
public
{
vm.assume(_crossDomainMessageSender != address(superchainERC20Bridge));

// Mock the call over the `crossDomainMessageSender` function setting a wrong sender
vm.mockCall(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector),
abi.encode(_crossDomainMessageSender)
);

// Expect the revert with `InvalidCrossDomainSender` selector
vm.expectRevert(ISuperchainERC20Bridge.InvalidCrossDomainSender.selector);

// Call the `relayERC20` function with the sender caller
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainERC20Bridge.relayERC20(_token, _crossDomainMessageSender, _to, _amount);
}

/// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event.
function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public {
vm.assume(_to != ZERO_ADDRESS);

// Mock the call over the `crossDomainMessageSender` function setting the same address as value
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector),
abi.encode(address(superchainERC20Bridge))
);

// Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector),
abi.encode(_source)
);

// Get the total supply and balance of `_to` before the relay to compare later on the assertions
uint256 _totalSupplyBefore = superchainERC20.totalSupply();
uint256 _toBalanceBefore = superchainERC20.balanceOf(_to);

// Look for the emit of the `Transfer` event
vm.expectEmit(address(superchainERC20));
emit Transfer(ZERO_ADDRESS, _to, _amount);

// Look for the emit of the `RelayERC20` event
vm.expectEmit(address(superchainERC20Bridge));
emit RelayERC20(address(superchainERC20), _from, _to, _amount, _source);

// Call the `relayERC20` function with the messenger caller
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainERC20Bridge.relayERC20(address(superchainERC20), _from, _to, _amount);

// Check the total supply and balance of `_to` after the relay were updated correctly
assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount);
assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount);
}
}
Loading