Skip to content

Commit

Permalink
test: add superchain erc20 bridge tests (#65)
Browse files Browse the repository at this point in the history
* test: add superchain erc20 bridge tests

* test: add optimism superchain erc20 beacon tests

* test: remove unnecessary test

* test: tests fixes

* test: tests fixes
  • Loading branch information
agusduha authored Sep 27, 2024
1 parent 2a22161 commit 6aaf905
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 63 deletions.
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";

/// @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

0 comments on commit 6aaf905

Please sign in to comment.