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

feat: introduce SuperchainERC20 redesign + ICrosschainERC20 #12321

Open
wants to merge 27 commits into
base: develop
Choose a base branch
from

Conversation

agusduha
Copy link
Contributor

@agusduha agusduha commented Oct 4, 2024

Description

The main idea is migrating the sendERC20 and relayERC20 functions from the SuperchainERC20 standard to a new SuperchainERC20Bridge. Also introducing ICrosschainERC20 interface for mint/burn functions to be cross-chain neutral and extensible to other L2s.

Specs:
ethereum-optimism/specs#384
ethereum-optimism/specs#413

Tests

  • Add SuperchainERC20Bridge tests
  • Refactor the old superchain standard tests

Additional context

The SuperchainERC20Bridge is a new predeploy.

Metadata

Closes #12322

agusduha and others added 15 commits September 25, 2024 15:20
* feat: add superchain erc20 bridge

* fix: interfaces and versions
* refactor: use oz upgradeable erc20 as dependency

* chore: update interfaces

* fix: tests based on changes

* refactor: remove op as dependency

* feat: add check for supererc20 bridge on modifier

* chore: update tests and interfaces

* chore: update stack vars name on test

* chore: remove empty gitmodules file

* chore: update superchain weth errors
* test: add superchain erc20 bridge tests

* test: add optimism superchain erc20 beacon tests

* test: remove unnecessary test

* test: tests fixes

* test: tests fixes
* chore: update missing bridge on natspec

* fix: natspecs

---------

Co-authored-by: agusduha <[email protected]>
* refactor: rename mint and burn functions on superchain erc20

* chore: rename optimism superchain erc20 to superchain erc20

* feat: create optimism superchain erc20 contract

* chore: update natspec and errors

* fix: superchain erc20 tests

* refactor: make superchain erc20 abstract

* refactor: move storage and erc20 metadata functions to implementation

* chore: update interfaces

* chore: update superchain erc20 events

* fix: tests

* fix: natspecs

* fix: add semmver lock and snapshots

* fix: remove unused imports

* fix: natspecs

---------

Co-authored-by: 0xDiscotech <[email protected]>
* fix: semver natspec check failure

* fix: ignore mock contracts in semver natspec script

* fix: error message
* feat: add crosschain erc20 interface

* fix: refactor interfaces
Co-authored-by: 0xng <[email protected]>
Co-authored-by: 0xParticle <[email protected]>
Co-authored-by: gotzenx <[email protected]>
@agusduha agusduha requested a review from a team as a code owner October 4, 2024 14:11
* fix: stop inheriting superchain interfaces

* fix: move events and erros into the implementation

* fix: make superchainERC20 inherits from crosschainERC20
@tynes
Copy link
Contributor

tynes commented Oct 4, 2024

What is the forge-std update from?

@@ -129,9 +129,14 @@ func run() error {
return
}

// Skip mock contracts
if strings.Contains(contractName, "_MockContract") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a strong opinion but could just be open to making it mock, case insensitive. Tradeoff in explicit vs implicit

"type": "error"
},
{
"inputs": [],
"name": "OnlyBridge",
"name": "OnlySuperchainERC20Bridge",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to move away from the superchain specific naming so that this can be an ethereum wide standard in theory

Copy link
Contributor Author

@agusduha agusduha Oct 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is being used in the SuperchainERC20 that is the OP implementation of ICrosschainERC20, the neutral namings are set on that interface so that it can be used in other ethereum chains

/// @notice The SuperchainERC20Bridge allows for the bridging of ERC20 tokens to make them fungible across the
/// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain
/// binding.
contract SuperchainERC20Bridge {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the naming nits, I know that I proposed this one, curious if you have any thoughts on the following issue: ethereum-optimism/specs#399

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We agree on changing the name, we notice that asset is often used

so to be more consistent, do u prefer SuperchainTokenBridge or SuperchainAssetBridge?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a slight preference for SuperchainTokenBridge unless you think that there are non token assets?

function relayERC20(address _token, address _from, address _to, uint256 _amount) external {
if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger();

if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) {
Copy link
Contributor

@tynes tynes Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you find any value in adding a L2ToL2CrossDomainMessenger.context() function that returns both the source + sender + other contextual information? I notice we do this double call thing everywhere, it could be a simplification

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a useful change, but we will be doing it in another PR to reduce the changes scope

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IOptimismERC20Factory } from "./IOptimismERC20Factory.sol";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit on import style, even tho eventually we should go back to relative paths so external projects can import this project without it breaking their remappings

Copy link

codecov bot commented Oct 8, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 64.04%. Comparing base (18732db) to head (71389b6).
Report is 1 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #12321      +/-   ##
===========================================
- Coverage    64.23%   64.04%   -0.19%     
===========================================
  Files           52       52              
  Lines         4353     4353              
===========================================
- Hits          2796     2788       -8     
- Misses        1381     1390       +9     
+ Partials       176      175       -1     
Flag Coverage Δ
cannon-go-tests 64.04% <ø> (-0.19%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

see 1 file with indirect coverage changes

@Dexaran
Copy link

Dexaran commented Oct 8, 2024

I would like to make a security disclosure.

ERC-20 standard contains a security flaw which resulted in $80,000,000 worth of tokens being lost due to lack of error handling implementation within the logic of transfer function.

Here is the full description: #12322 (comment)

As you are about to build your technology stack on top of the ERC-20 standard your users will be affected and are guaranteed to lose funds similar to how it happened on Ethereum due to the above described issue, unless you fix it.

@skeletor-spaceman
Copy link
Collaborator

Hi @Dexaran ! I'll echo what I replied on the other issue: #12322 (comment)
This is not a valid security disclosure, it does not affect the standard as is. The erc20 code will remain unchanged.

Copy link
Contributor

@mds1 mds1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a skim of the diff with a few small comments

Comment on lines 132 to 135
// Skip mock contracts
if strings.Contains(contractName, "_mock") {
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we already have MockL2ToL2CrossDomainMessenger and this PR adds SuperchainERC20Implementation_mock. Can we stick with the existing Mock* precedent?

/// @notice Allows the L2StandardBridge to mint tokens.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
interface IOptimismSuperchainERC20 is ISuperchainERC20 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the SuperchainERC20 interface compared to the CrosschainERC20 interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has specific errors of the abstract implementation SuperchainERC20 that extends ICrosschainERC20. We extend it here so that the OptimismSuperchainERC20 has the correct ABI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I thought I heard that SuperchainERC20 was being renamed to CrosschainERC20, and therefore I'd only expect to see CrosschainERC20 throughout this PR. I may be out of the loop there though, so just want to better understand the relationship between the two

/// @notice Mint tokens through a crosschain transfer.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
function __crosschainMint(address _to, uint256 _amount) external;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the double underscore convention mean? The only other place I see this in the codebase (aside from the __constructor__ dummy sigs) is internal initializer methods such as __ERC721Bridge_init, but these are not initializers

Copy link
Contributor Author

@agusduha agusduha Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is to make it clear that are not supposed to be called by anyone else

It was proposed in this thread https://discord.com/channels/1244729134312198194/1285980446857101423/1289362724135899209

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asked a follow up question to learn more in discord here

/// binding.
contract SuperchainTokenBridge {
/// @notice Thrown when attempting to perform an operation and the account is the zero address.
error ZeroAddress();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider an Errors.sol to avoid redeclaring errors like this in each file

Comment on lines 21 to 23
/// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not
/// L2ToL2CrossDomainMessenger.
error CallerNotL2ToL2CrossDomainMessenger();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks analagous to the Unauthorized eror we use elsewhere in the codebase. Consider using that to minimize the amount of new errors for a simpler ABI. Either the Unauthorized() or the Unauthorized(expected, actual) signatures that we use are ok with me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this with @skeletor-spaceman, @0xng, and @gotzenx, and we believe that having explicit error messages offers more advantages than disadvantages. However, we're open to changing them if it's absolutely necessary for the PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly, I just want to be careful about exploding the amount of custom errors vs. grouping related ones and using params to get specific. For example you could even do Unauthorized(string) to revert with Unauthorized("Caller is not L2ToL2CrossDomainMessenger")

Copy link
Contributor

@tynes tynes Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a string defeats the purpose of using a custom error no? At least with regards to the bytecode size

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely am a minimalist and prefer a minimal API but if you are very opinionated about very specific custom errors, its ok to ship

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a string defeats the purpose of using a custom error no? At least with regards to the bytecode size

Ultimately depends on what we want to optimize for. The tradeoff is roughly:

  • Bespoke errors for each case: Biggest ABI, but full rationale is part of the ABI for consumers
  • Unauthorized(address actual, address expected) types of errors: Small ABI, but ABI alone doesn't tell consumer what each address semantically represents from the ABI (i.e. CallerNotL2ToL2CrossDomainMessenger tells me I expected the L2ToL2CrossDomainMessenger, but I have to know that the expected: 0x... address is the L2ToL2CrossDomainMessenger address
  • Unauthorized(string): Same ABI size, more flexibility in communicating to users what went wrong, larger bytecode and gas due to string usage
  • Unauthorized(bytes32): Like the prior bullet, but limited string size, so smaller/cheaper

Personally I also like smaller interfaces (for functions, errors, and events) as it means smaller surface areas (less attack surface when talking about functions), and for me that makes it easier to fit full context in my head when reasoning + less room for mistakes when choosing which to use.

Ultimately whatever we decide should be upstreamed to the Style Guide to avoid this bikeshed in the future 😅

Comment on lines 25 to 26
/// @custom:semver 1.0.0-beta.7
string public constant version = "1.0.0-beta.7";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we skipped beta.6 and went straight to 7

Suggested change
/// @custom:semver 1.0.0-beta.7
string public constant version = "1.0.0-beta.7";
/// @custom:semver 1.0.0-beta.6
string public constant version = "1.0.0-beta.6";

* fix: refactor common errors

* fix: remove unused version
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SuperchainERC20: Redesign
6 participants