Commit 49d33b08 authored by smartcontracts's avatar smartcontracts Committed by GitHub

fix(ctb): universal contract comments/errs/events (#2940)

Standardizes the comments, errors, and events for all contracts within
the /universal folder.
parent 297af083
---
'@eth-optimism/contracts-bedrock': patch
---
Standardizes comments, errors, and events for contracts in the /universal package
...@@ -103,18 +103,18 @@ OVM_ETH_Test:test_transfer() (gas: 10726) ...@@ -103,18 +103,18 @@ OVM_ETH_Test:test_transfer() (gas: 10726)
OVM_ETH_Test:test_transferFrom() (gas: 13008) OVM_ETH_Test:test_transferFrom() (gas: 13008)
OptimismMintableERC20_Test:test_bridge() (gas: 9785) OptimismMintableERC20_Test:test_bridge() (gas: 9785)
OptimismMintableERC20_Test:test_burn() (gas: 52791) OptimismMintableERC20_Test:test_burn() (gas: 52791)
OptimismMintableERC20_Test:test_burnRevertsFromNotBridge() (gas: 13211) OptimismMintableERC20_Test:test_burnRevertsFromNotBridge() (gas: 13241)
OptimismMintableERC20_Test:test_erc165_supportsInterface() (gas: 7828) OptimismMintableERC20_Test:test_erc165_supportsInterface() (gas: 7828)
OptimismMintableERC20_Test:test_l1Token() (gas: 9779) OptimismMintableERC20_Test:test_l1Token() (gas: 9779)
OptimismMintableERC20_Test:test_l2Bridge() (gas: 9790) OptimismMintableERC20_Test:test_l2Bridge() (gas: 9790)
OptimismMintableERC20_Test:test_mint() (gas: 65687) OptimismMintableERC20_Test:test_mint() (gas: 65687)
OptimismMintableERC20_Test:test_mintRevertsFromNotBridge() (gas: 13235) OptimismMintableERC20_Test:test_mintRevertsFromNotBridge() (gas: 13265)
OptimismMintableERC20_Test:test_remoteToken() (gas: 9784) OptimismMintableERC20_Test:test_remoteToken() (gas: 9784)
OptimismMintableTokenFactory_Test:test_bridge() (gas: 9707) OptimismMintableTokenFactory_Test:test_bridge() (gas: 9707)
OptimismMintableTokenFactory_Test:test_createStandardL2Token() (gas: 1100125) OptimismMintableTokenFactory_Test:test_createStandardL2Token() (gas: 1115359)
OptimismMintableTokenFactory_Test:test_createStandardL2TokenSameTwice() (gas: 2181161) OptimismMintableTokenFactory_Test:test_createStandardL2TokenSameTwice() (gas: 2211629)
OptimismMintableTokenFactory_Test:test_createStandardL2TokenShouldRevertIfRemoteIsZero() (gas: 9374) OptimismMintableTokenFactory_Test:test_createStandardL2TokenShouldRevertIfRemoteIsZero() (gas: 9407)
OptimismMintableTokenFactory_Test:test_initializeShouldRevert() (gas: 12696) OptimismMintableTokenFactory_Test:test_initializeShouldRevert() (gas: 12732)
OptimismPortalUpgradeable_Test:test_cannotInitImpl() (gas: 10936) OptimismPortalUpgradeable_Test:test_cannotInitImpl() (gas: 10936)
OptimismPortalUpgradeable_Test:test_cannotInitProxy() (gas: 15934) OptimismPortalUpgradeable_Test:test_cannotInitProxy() (gas: 15934)
OptimismPortalUpgradeable_Test:test_initValuesOnProxy() (gas: 16034) OptimismPortalUpgradeable_Test:test_initValuesOnProxy() (gas: 16034)
...@@ -137,12 +137,12 @@ OptimismPortal_Test:test_simple_isOutputFinalized() (gas: 23877) ...@@ -137,12 +137,12 @@ OptimismPortal_Test:test_simple_isOutputFinalized() (gas: 23877)
Proxy_Test:test_clashingFunctionSignatures() (gas: 101427) Proxy_Test:test_clashingFunctionSignatures() (gas: 101427)
Proxy_Test:test_implementationKey() (gas: 20942) Proxy_Test:test_implementationKey() (gas: 20942)
Proxy_Test:test_implementationProxyCallIfNotAdmin() (gas: 30021) Proxy_Test:test_implementationProxyCallIfNotAdmin() (gas: 30021)
Proxy_Test:test_implementationZeroAddress() (gas: 48006) Proxy_Test:test_implementationZeroAddress() (gas: 48007)
Proxy_Test:test_itDelegatesToTheImplementation() (gas: 45173) Proxy_Test:test_itDelegatesToTheImplementation() (gas: 45173)
Proxy_Test:test_ownerKey() (gas: 19113) Proxy_Test:test_ownerKey() (gas: 19113)
Proxy_Test:test_ownerProxyCallIfNotAdmin() (gas: 34733) Proxy_Test:test_ownerProxyCallIfNotAdmin() (gas: 34733)
Proxy_Test:test_payableUpgradeToAndCall() (gas: 53887) Proxy_Test:test_payableUpgradeToAndCall() (gas: 53887)
Proxy_Test:test_revertUpgradeToAndCall() (gas: 104501) Proxy_Test:test_revertUpgradeToAndCall() (gas: 104654)
Proxy_Test:test_upgradeToAndCall() (gas: 125238) Proxy_Test:test_upgradeToAndCall() (gas: 125238)
Proxy_Test:test_zeroAddressCaller() (gas: 14758) Proxy_Test:test_zeroAddressCaller() (gas: 14758)
ProxyAdmin_Test:test_chugsplashChangeProxyAdmin() (gas: 35647) ProxyAdmin_Test:test_chugsplashChangeProxyAdmin() (gas: 35647)
...@@ -155,7 +155,7 @@ ProxyAdmin_Test:test_delegateResolvedGetProxyAdmin() (gas: 17685) ...@@ -155,7 +155,7 @@ ProxyAdmin_Test:test_delegateResolvedGetProxyAdmin() (gas: 17685)
ProxyAdmin_Test:test_delegateResolvedGetProxyImplementation() (gas: 62016) ProxyAdmin_Test:test_delegateResolvedGetProxyImplementation() (gas: 62016)
ProxyAdmin_Test:test_delegateResolvedUpgrade() (gas: 58422) ProxyAdmin_Test:test_delegateResolvedUpgrade() (gas: 58422)
ProxyAdmin_Test:test_delegateResolvedUpgradeAndCall() (gas: 97948) ProxyAdmin_Test:test_delegateResolvedUpgradeAndCall() (gas: 97948)
ProxyAdmin_Test:test_erc1967ChangeProxyAdmin() (gas: 33862) ProxyAdmin_Test:test_erc1967ChangeProxyAdmin() (gas: 33863)
ProxyAdmin_Test:test_erc1967GetProxyAdmin() (gas: 15672) ProxyAdmin_Test:test_erc1967GetProxyAdmin() (gas: 15672)
ProxyAdmin_Test:test_erc1967GetProxyImplementation() (gas: 52124) ProxyAdmin_Test:test_erc1967GetProxyImplementation() (gas: 52124)
ProxyAdmin_Test:test_erc1967Upgrade() (gas: 50036) ProxyAdmin_Test:test_erc1967Upgrade() (gas: 50036)
......
...@@ -41,7 +41,7 @@ contract OptimismMintableERC20_Test is Bridge_Initializer { ...@@ -41,7 +41,7 @@ contract OptimismMintableERC20_Test is Bridge_Initializer {
function test_mintRevertsFromNotBridge() external { function test_mintRevertsFromNotBridge() external {
// NOT the bridge // NOT the bridge
vm.expectRevert("Only L2 Bridge can mint and burn"); vm.expectRevert("OptimismMintableERC20: only bridge can mint and burn");
vm.prank(address(alice)); vm.prank(address(alice));
L2Token.mint(alice, 100); L2Token.mint(alice, 100);
} }
...@@ -61,7 +61,7 @@ contract OptimismMintableERC20_Test is Bridge_Initializer { ...@@ -61,7 +61,7 @@ contract OptimismMintableERC20_Test is Bridge_Initializer {
function test_burnRevertsFromNotBridge() external { function test_burnRevertsFromNotBridge() external {
// NOT the bridge // NOT the bridge
vm.expectRevert("Only L2 Bridge can mint and burn"); vm.expectRevert("OptimismMintableERC20: only bridge can mint and burn");
vm.prank(address(alice)); vm.prank(address(alice));
L2Token.burn(alice, 100); L2Token.burn(alice, 100);
} }
......
...@@ -17,7 +17,7 @@ contract OptimismMintableTokenFactory_Test is Bridge_Initializer { ...@@ -17,7 +17,7 @@ contract OptimismMintableTokenFactory_Test is Bridge_Initializer {
} }
function test_initializeShouldRevert() external { function test_initializeShouldRevert() external {
vm.expectRevert("Already initialized."); vm.expectRevert("OptimismMintableTokenFactory: already initialized");
L2TokenFactory.initialize(address(L1Bridge)); L2TokenFactory.initialize(address(L1Bridge));
} }
...@@ -73,7 +73,7 @@ contract OptimismMintableTokenFactory_Test is Bridge_Initializer { ...@@ -73,7 +73,7 @@ contract OptimismMintableTokenFactory_Test is Bridge_Initializer {
function test_createStandardL2TokenShouldRevertIfRemoteIsZero() external { function test_createStandardL2TokenShouldRevertIfRemoteIsZero() external {
address remote = address(0); address remote = address(0);
vm.expectRevert("Must provide L1 token address"); vm.expectRevert("OptimismMintableTokenFactory: must provide remote token address");
L2TokenFactory.createStandardL2Token(remote, "Beep", "BOOP"); L2TokenFactory.createStandardL2Token(remote, "Beep", "BOOP");
} }
} }
...@@ -184,7 +184,7 @@ contract Proxy_Test is Test { ...@@ -184,7 +184,7 @@ contract Proxy_Test is Test {
// Set the new SimpleStorage as the implementation // Set the new SimpleStorage as the implementation
// and call. This reverts because the calldata doesn't // and call. This reverts because the calldata doesn't
// match a function on the implementation. // match a function on the implementation.
vm.expectRevert(); vm.expectRevert("Proxy: delegatecall to new implementation contract failed");
vm.prank(alice); vm.prank(alice);
proxy.upgradeToAndCall( proxy.upgradeToAndCall(
address(simpleStorage), address(simpleStorage),
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.9; pragma solidity ^0.8.9;
// solhint-disable max-line-length
/* Library Imports */
import { Lib_DefaultValues } from "../libraries/Lib_DefaultValues.sol";
import { CrossDomainHashing } from "../libraries/Lib_CrossDomainHashing.sol";
/* External Imports */
import { import {
OwnableUpgradeable OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
...@@ -18,22 +11,31 @@ import { ...@@ -18,22 +11,31 @@ import {
ReentrancyGuardUpgradeable ReentrancyGuardUpgradeable
} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { ExcessivelySafeCall } from "excessively-safe-call/src/ExcessivelySafeCall.sol"; import { ExcessivelySafeCall } from "excessively-safe-call/src/ExcessivelySafeCall.sol";
import { Lib_DefaultValues } from "../libraries/Lib_DefaultValues.sol";
// solhint-enable max-line-length import { CrossDomainHashing } from "../libraries/Lib_CrossDomainHashing.sol";
/** /**
* @title CrossDomainMessenger * @title CrossDomainMessenger
* @dev The CrossDomainMessenger contract delivers messages between two layers. * @notice CrossDomainMessenger is a base contract that provides the core logic for the L1 and L2
* cross-chain messenger contracts. It's designed to be a universal interface that only
* needs to be extended slightly to provide low-level message passing functionality on each
* chain it's deployed on. Currently only designed for message passing between two paired
* chains and does not support one-to-many interactions.
*/ */
abstract contract CrossDomainMessenger is abstract contract CrossDomainMessenger is
OwnableUpgradeable, OwnableUpgradeable,
PausableUpgradeable, PausableUpgradeable,
ReentrancyGuardUpgradeable ReentrancyGuardUpgradeable
{ {
/********** /**
* Events * * @notice Emitted whenever a message is sent to the other chain.
**********/ *
* @param target Address of the recipient of the message.
* @param sender Address of the sender of the message.
* @param message Message to trigger the recipient address with.
* @param messageNonce Unique nonce attached to the message.
* @param gasLimit Minimum gas limit that the message can be executed with.
*/
event SentMessage( event SentMessage(
address indexed target, address indexed target,
address sender, address sender,
...@@ -42,73 +44,112 @@ abstract contract CrossDomainMessenger is ...@@ -42,73 +44,112 @@ abstract contract CrossDomainMessenger is
uint256 gasLimit uint256 gasLimit
); );
/**
* @notice Emitted whenever a message is successfully relayed on this chain.
*
* @param msgHash Hash of the message that was relayed.
*/
event RelayedMessage(bytes32 indexed msgHash); event RelayedMessage(bytes32 indexed msgHash);
/**
* @notice Emitted whenever a message fails to be relayed on this chain.
*
* @param msgHash Hash of the message that failed to be relayed.
*/
event FailedRelayedMessage(bytes32 indexed msgHash); event FailedRelayedMessage(bytes32 indexed msgHash);
/************* /**
* Constants * * @notice Current message version identifier.
*************/ */
uint16 public constant MESSAGE_VERSION = 1; uint16 public constant MESSAGE_VERSION = 1;
/**
* @notice Dynamic overhead applied to the base gas for a message.
*/
uint32 public constant MIN_GAS_DYNAMIC_OVERHEAD = 1; uint32 public constant MIN_GAS_DYNAMIC_OVERHEAD = 1;
/**
* @notice Constant overhead added to the base gas for a message.
*/
uint32 public constant MIN_GAS_CONSTANT_OVERHEAD = 100_000; uint32 public constant MIN_GAS_CONSTANT_OVERHEAD = 100_000;
/// @notice Minimum amount of gas required prior to relaying a message. /**
* @notice Minimum amount of gas required to relay a message.
*/
uint256 internal constant RELAY_GAS_REQUIRED = 45_000; uint256 internal constant RELAY_GAS_REQUIRED = 45_000;
/// @notice Amount of gas held in reserve for accounting after relaying a message. /**
* @notice Amount of gas held in reserve to guarantee that relay execution completes.
*/
uint256 internal constant RELAY_GAS_BUFFER = RELAY_GAS_REQUIRED - 5000; uint256 internal constant RELAY_GAS_BUFFER = RELAY_GAS_REQUIRED - 5000;
/************* /**
* Variables * * @notice Mapping of message hashes to boolean receipt values. Note that a message will only
*************/ * be present in this mapping if it failed to be relayed on this chain at least once.
* If a message is successfully relayed on the first attempt, then it will only be
/// @notice Mapping of message hash to boolean success value. * present within the successfulMessages mapping.
*/
mapping(bytes32 => bool) public successfulMessages; mapping(bytes32 => bool) public successfulMessages;
/// @notice Current x-domain message sender. /**
* @notice Address of the sender of the currently executing message on the other chain. If the
* value of this variable is the default value (0x00000000...dead) then no message is
* currently being executed. Use the xDomainMessageSender getter which will throw an
* error if this is the case.
*/
address internal xDomainMsgSender; address internal xDomainMsgSender;
/// @notice Nonce for the next message to be sent. /**
* @notice Nonce for the next message to be sent, without the message version applied. Use the
* messageNonce getter which will insert the message version into the nonce to give you
* the actual nonce to be used for the message.
*/
uint256 internal msgNonce; uint256 internal msgNonce;
/// @notice Address of the CrossDomainMessenger on the other chain. /**
* @notice Address of the paired CrossDomainMessenger contract on the other chain.
*/
address public otherMessenger; address public otherMessenger;
/// @notice Mapping of message hash to boolean receipt value. /**
* @notice Mapping of message hashes to boolean receipt values. Note that a message will only
* be present in this mapping if it failed to be relayed on this chain at least once.
* If a message is successfully relayed on the first attempt, then it will only be
* present within the successfulMessages mapping.
*/
mapping(bytes32 => bool) public receivedMessages; mapping(bytes32 => bool) public receivedMessages;
/// @notice Blocked system addresses that cannot be called (for security reasons). /**
* @notice Mapping of blocked system addresses. Note that this is NOT a mapping of blocked user
* addresses and cannot be used to prevent users from sending or receiving messages.
* This is ONLY used to prevent the execution of messages to specific system addresses
* that could cause security issues, e.g., having the CrossDomainMessenger send
* messages to itself.
*/
mapping(address => bool) public blockedSystemAddresses; mapping(address => bool) public blockedSystemAddresses;
/********************
* Public Functions *
********************/
/** /**
* Pause relaying. * @notice Allows the owner of this contract to temporarily pause message relaying. Backup
* security mechanism just in case. Owner should be the same as the upgrade wallet to
* maintain the security model of the system as a whole.
*/ */
function pause() external onlyOwner { function pause() external onlyOwner {
_pause(); _pause();
} }
/** /**
* Unpause relaying. * @notice Allows the owner of this contract to resume message relaying once paused.
*/ */
function unpause() external onlyOwner { function unpause() external onlyOwner {
_unpause(); _unpause();
} }
/** /**
* Retrieves the address of the x-domain message sender. Will throw an error * @notice Retrieves the address of the contract or wallet that initiated the currently
* if the sender is not currently set (equal to the default sender). * executing message on the other chain. Will throw an error if there is no message
* This function is meant to be called on the remote side of a cross domain * currently being executed. Allows the recipient of a call to see who triggered it.
* message so that the account that initiated the call can be known.
* *
* @return Address of the x-domain message sender. * @return Address of the sender of the currently executing message on the other chain.
*/ */
function xDomainMessageSender() external view returns (address) { function xDomainMessageSender() external view returns (address) {
require( require(
...@@ -120,22 +161,25 @@ abstract contract CrossDomainMessenger is ...@@ -120,22 +161,25 @@ abstract contract CrossDomainMessenger is
} }
/** /**
* Retrieves the next message nonce. Adds the hash version to the nonce. * @notice Retrieves the next message nonce. Message version will be added to the upper two
* bytes of the message nonce. Message version allows us to treat messages as having
* different structures.
* *
* @return Next message nonce with added hash version. * @return Nonce of the next message to be sent, with added message version.
*/ */
function messageNonce() public view returns (uint256) { function messageNonce() public view returns (uint256) {
return CrossDomainHashing.addVersionToNonce(msgNonce, MESSAGE_VERSION); return CrossDomainHashing.addVersionToNonce(msgNonce, MESSAGE_VERSION);
} }
/** /**
* Base amount of gas required to make sure that the message will be received without * @notice Computes the amount of gas required to guarantee that a given message will be
* running out of gas. Amount of gas provided to the L2 call will be the gas requested by * received on the other chain without running out of gas. Guaranteeing that a message
* the user PLUS this gas value so that if the message is not successful, it can always be * will not run out of gas is important because this ensures that a message can always
* replayed on the other end. * be replayed on the other chain if it fails to execute completely.
* *
* @param _message Message to compute base gas for. * @param _message Message to compute the amount of required gas for.
* @return Base gas required for message. *
* @return Amount of gas required to guarantee message receipt.
*/ */
function baseGas(bytes memory _message) public pure returns (uint32) { function baseGas(bytes memory _message) public pure returns (uint32) {
// TODO: Values here are meant to be good enough to get a devnet running. We need to do // TODO: Values here are meant to be good enough to get a devnet running. We need to do
...@@ -145,9 +189,11 @@ abstract contract CrossDomainMessenger is ...@@ -145,9 +189,11 @@ abstract contract CrossDomainMessenger is
} }
/** /**
* @param _target Target contract address. * @notice Sends a message to some target address on the other chain.
* @param _message Message to send to the target. *
* @param _minGasLimit Gas limit for the provided message. * @param _target Target contract or wallet address.
* @param _message Message to trigger the target address with.
* @param _minGasLimit Minimum gas limit that the message can be executed with.
*/ */
function sendMessage( function sendMessage(
address _target, address _target,
...@@ -180,6 +226,18 @@ abstract contract CrossDomainMessenger is ...@@ -180,6 +226,18 @@ abstract contract CrossDomainMessenger is
} }
} }
/**
* @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only
* be executed via cross-chain call from the other messenger OR if the message was
* already received once and is currently being replayed.
*
* @param _nonce Nonce of the message being relayed.
* @param _sender Address of the user who sent the message.
* @param _target Address that the message is targeted at.
* @param _value ETH value to send with the message.
* @param _minGasLimit Minimum amount of gas that the message can be executed with.
* @param _message Message to send to the target.
*/
function relayMessage( function relayMessage(
uint256 _nonce, uint256 _nonce,
address _sender, address _sender,
...@@ -240,22 +298,16 @@ abstract contract CrossDomainMessenger is ...@@ -240,22 +298,16 @@ abstract contract CrossDomainMessenger is
} }
} }
/**********************
* Internal Functions *
**********************/
function _isSystemMessageSender() internal view virtual returns (bool);
function _sendMessage(
address _to,
uint64 _gasLimit,
uint256 _value,
bytes memory _data
) internal virtual;
/** /**
* @notice Initializes the contract. The parent contract MUST handle * @notice Intializer.
* preventing this from being called twice. *
* @param _otherMessenger Address of the CrossDomainMessenger on the paired chain.
* @param _blockedSystemAddresses List of system addresses that need to be blocked to prevent
* certain security issues. Exact list depends on the network
* where this contract is deployed. See note attached to the
* blockedSystemAddresses variable in this contract for more
* detailed information about what this block list can and
* cannot be used for.
*/ */
function _initialize(address _otherMessenger, address[] memory _blockedSystemAddresses) function _initialize(address _otherMessenger, address[] memory _blockedSystemAddresses)
internal internal
...@@ -274,4 +326,27 @@ abstract contract CrossDomainMessenger is ...@@ -274,4 +326,27 @@ abstract contract CrossDomainMessenger is
__Pausable_init_unchained(); __Pausable_init_unchained();
__ReentrancyGuard_init_unchained(); __ReentrancyGuard_init_unchained();
} }
/**
* @notice Checks whether the message is coming from the other messenger. Implemented by child
* contracts because the logic for this depends on the network where the messenger is
* being deployed.
*/
function _isSystemMessageSender() internal view virtual returns (bool) {
revert("CrossDomainMessenger: child contract must implement");
}
/**
* @notice Sends a low-level message to the other messenger. Needs to be implemented by child
* contracts because the logic for this depends on the network where the messenger is
* being deployed.
*/
function _sendMessage(
address _to,
uint64 _gasLimit,
uint256 _value,
bytes memory _data
) internal virtual {
revert("CrossDomainMessenger: child contract must implement");
}
} }
...@@ -6,32 +6,44 @@ import "./SupportedInterfaces.sol"; ...@@ -6,32 +6,44 @@ import "./SupportedInterfaces.sol";
/** /**
* @title OptimismMintableERC20 * @title OptimismMintableERC20
* This contract represents the remote representation * @notice OptimismMintableERC20 is a standard extension of the base ERC20 token contract designed
* of an ERC20 token. It is linked to the address of * to allow the StandardBridge contracts to mint and burn tokens. This makes it possible to
* a token in another domain and tokens can be locked * use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa.
* in the StandardBridge which will mint tokens in the * Designed to be backwards compatible with the older StandardL2ERC20 token which was only
* other domain. * meant for use on L2.
*/ */
contract OptimismMintableERC20 is ERC20 { contract OptimismMintableERC20 is ERC20 {
event Mint(address indexed _account, uint256 _amount); /**
event Burn(address indexed _account, uint256 _amount); * @notice Emitted whenever tokens are minted for an account.
*
* @param account Address of the account tokens are being minted for.
* @param amount Amount of tokens minted.
*/
event Mint(address indexed account, uint256 amount);
/** /**
* @notice The address of the token in the remote domain * @notice Emitted whenever tokens are burned from an account.
*
* @param account Address of the account tokens are being burned from.
* @param amount Amount of tokens burned.
*/
event Burn(address indexed account, uint256 amount);
/**
* @notice Address of the corresponding version of this token on the remote chain.
*/ */
address public remoteToken; address public remoteToken;
/** /**
* @notice The address of the bridge responsible for * @notice Address of the StandardBridge on this network.
* minting. It is in the same domain.
*/ */
address public bridge; address public bridge;
/** /**
* @param _bridge Address of the L2 standard bridge. * @param _bridge Address of the L2 standard bridge.
* @param _remoteToken Address of the corresponding L1 token. * @param _remoteToken Address of the corresponding L1 token.
* @param _name ERC20 name. * @param _name ERC20 name.
* @param _symbol ERC20 symbol. * @param _symbol ERC20 symbol.
*/ */
constructor( constructor(
address _bridge, address _bridge,
...@@ -44,17 +56,16 @@ contract OptimismMintableERC20 is ERC20 { ...@@ -44,17 +56,16 @@ contract OptimismMintableERC20 is ERC20 {
} }
/** /**
* @notice Returns the corresponding L1 token address. * @custom:legacy
* This is a legacy function and wraps the remoteToken value. * @notice Legacy getter for the remote token. Use remoteToken going forward.
*/ */
function l1Token() public view returns (address) { function l1Token() public view returns (address) {
return remoteToken; return remoteToken;
} }
/** /**
* @notice The address of the bridge contract * @custom:legacy
* responsible for minting tokens. This is a legacy * @notice Legacy getter for the bridge. Use bridge going forward.
* getter function
*/ */
function l2Bridge() public view returns (address) { function l2Bridge() public view returns (address) {
return bridge; return bridge;
...@@ -64,15 +75,18 @@ contract OptimismMintableERC20 is ERC20 { ...@@ -64,15 +75,18 @@ contract OptimismMintableERC20 is ERC20 {
* @notice A modifier that only allows the bridge to call * @notice A modifier that only allows the bridge to call
*/ */
modifier onlyBridge() { modifier onlyBridge() {
require(msg.sender == bridge, "Only L2 Bridge can mint and burn"); require(msg.sender == bridge, "OptimismMintableERC20: only bridge can mint and burn");
_; _;
} }
/** /**
* @notice ERC165 * @notice ERC165 interface check function.
*
* @param _interfaceId Interface ID to check.
*
* @return Whether or not the interface is supported by this contract.
*/ */
// slither-disable-next-line external-function function supportsInterface(bytes4 _interfaceId) external pure returns (bool) {
function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {
bytes4 iface1 = type(IERC165).interfaceId; bytes4 iface1 = type(IERC165).interfaceId;
bytes4 iface2 = type(IL1Token).interfaceId; bytes4 iface2 = type(IL1Token).interfaceId;
bytes4 iface3 = type(IRemoteToken).interfaceId; bytes4 iface3 = type(IRemoteToken).interfaceId;
...@@ -80,22 +94,24 @@ contract OptimismMintableERC20 is ERC20 { ...@@ -80,22 +94,24 @@ contract OptimismMintableERC20 is ERC20 {
} }
/** /**
* @notice The bridge can mint tokens * @notice Allows the StandardBridge on this network to mint tokens.
*
* @param _to Address to mint tokens to.
* @param _amount Amount of tokens to mint.
*/ */
// slither-disable-next-line external-function function mint(address _to, uint256 _amount) external virtual onlyBridge {
function mint(address _to, uint256 _amount) public virtual onlyBridge {
_mint(_to, _amount); _mint(_to, _amount);
emit Mint(_to, _amount); emit Mint(_to, _amount);
} }
/** /**
* @notice The bridge can burn tokens * @notice Allows the StandardBridge on this network to burn tokens.
*
* @param _from Address to burn tokens from.
* @param _amount Amount of tokens to burn.
*/ */
// slither-disable-next-line external-function function burn(address _from, uint256 _amount) external virtual onlyBridge {
function burn(address _from, uint256 _amount) public virtual onlyBridge {
_burn(_from, _amount); _burn(_from, _amount);
emit Burn(_from, _amount); emit Burn(_from, _amount);
} }
} }
...@@ -6,50 +6,77 @@ import { OptimismMintableERC20 } from "../universal/OptimismMintableERC20.sol"; ...@@ -6,50 +6,77 @@ import { OptimismMintableERC20 } from "../universal/OptimismMintableERC20.sol";
import { Lib_PredeployAddresses } from "../libraries/Lib_PredeployAddresses.sol"; import { Lib_PredeployAddresses } from "../libraries/Lib_PredeployAddresses.sol";
/** /**
* @custom:proxied
* @custom:predeployed 0x4200000000000000000000000000000000000012
* @title OptimismMintableTokenFactory * @title OptimismMintableTokenFactory
* @dev Factory contract for creating standard remote token representations of * @notice OptimismMintableTokenFactory is a factory contract that generates OptimismMintableERC20
* local ERC20s. This can be used to bridge native L1 ERC20s to L2 or native L2 * contracts on the network it's deployed to. Simplifies the deployment process for users
* ERC20s to L1. The tokens created through this factory are meant to operate * who may be less familiar with deploying smart contracts. Designed to be backwards
* with the StandardBridge contract for deposits/withdrawals. * compatible with the older StandardL2ERC20Factory contract.
* This contract is a predeploy on L2 at 0x4200000000000000000000000000000000000012
* TODO: deploy to a deterministic address on L1 networks?
* TODO: should this be extended for L1/L2 with hardcoded values in
* the base contract's initialize?
*/ */
contract OptimismMintableTokenFactory { contract OptimismMintableTokenFactory {
event StandardL2TokenCreated(address indexed _remoteToken, address indexed _localToken); /**
* @custom:legacy
* @notice Emitted whenever a new OptimismMintableERC20 is created. Legacy version of the newer
* OptimismMintableERC20Created event. We recommend relying on that event instead.
*
* @param remoteToken Address of the token on the remote chain.
* @param localToken Address of the created token on the local chain.
*/
event StandardL2TokenCreated(address indexed remoteToken, address indexed localToken);
/**
* @notice Emitted whenever a new OptimismMintableERC20 is created.
*
* @param localToken Address of the created token on the local chain.
* @param remoteToken Address of the corresponding token on the remote chain.
* @param deployer Address of the account that deployed the token.
*/
event OptimismMintableTokenCreated( event OptimismMintableTokenCreated(
address indexed _localToken, address indexed localToken,
address indexed _remoteToken, address indexed remoteToken,
address _deployer address deployer
); );
/**
* @notice Address of the StandardBridge on this chain.
*/
address public bridge; address public bridge;
/** /**
* @dev Initialize the factory * @notice Initializer.
* On L2 _bridge should be Lib_PredeployAddresses.L2_STANDARD_BRIDGE, *
* On L1 _bridge should be the L1StandardBridge * @param _bridge Address of the StandardBridge on this chain.
*/ */
function initialize(address _bridge) public { function initialize(address _bridge) public {
require(bridge == address(0), "Already initialized."); require(bridge == address(0), "OptimismMintableTokenFactory: already initialized");
bridge = _bridge; bridge = _bridge;
} }
/** /**
* @dev Creates an instance of the standard ERC20 token on L2. * @notice Creates an instance of the OptimismMintableERC20 contract. Legacy version of the
* @param _remoteToken Address of the corresponding L1 token. * newer createOptimismMintableERC20 function, which has a more intuitive name.
* @param _name ERC20 name. *
* @param _symbol ERC20 symbol. * @param _remoteToken Address of the token on the remote chain.
* @return Address of the new token. * @param _name ERC20 name.
* @param _symbol ERC20 symbol.
*
* @return Address of the newly created token.
*/ */
function createStandardL2Token( function createStandardL2Token(
address _remoteToken, address _remoteToken,
string memory _name, string memory _name,
string memory _symbol string memory _symbol
) external returns (address) { ) external returns (address) {
require(_remoteToken != address(0), "Must provide L1 token address"); require(
require(bridge != address(0), "Must initialize first"); _remoteToken != address(0),
"OptimismMintableTokenFactory: must provide remote token address"
);
require(
bridge != address(0),
"OptimismMintableTokenFactory: must initialize contract first"
);
OptimismMintableERC20 localToken = new OptimismMintableERC20( OptimismMintableERC20 localToken = new OptimismMintableERC20(
bridge, bridge,
...@@ -58,7 +85,7 @@ contract OptimismMintableTokenFactory { ...@@ -58,7 +85,7 @@ contract OptimismMintableTokenFactory {
_symbol _symbol
); );
// Legacy Purposes // Emit the old event too for legacy support.
emit StandardL2TokenCreated(_remoteToken, address(localToken)); emit StandardL2TokenCreated(_remoteToken, address(localToken));
emit OptimismMintableTokenCreated(_remoteToken, address(localToken), msg.sender); emit OptimismMintableTokenCreated(_remoteToken, address(localToken), msg.sender);
......
...@@ -3,22 +3,22 @@ pragma solidity ^0.8.9; ...@@ -3,22 +3,22 @@ pragma solidity ^0.8.9;
/** /**
* @title Proxy * @title Proxy
* @notice Proxy is a transparent proxy that passes through the call * @notice Proxy is a transparent proxy that passes through the call if the caller is the owner or
* if the caller is the owner or if the caller is `address(0)`, * if the caller is address(0), meaning that the call originated from an off-chain
* meaning that the call originated from an offchain simulation. * simulation.
*/ */
contract Proxy { contract Proxy {
/** /**
* @notice An event that is emitted each time the implementation is changed. * @notice An event that is emitted each time the implementation is changed. This event is part
* This event is part of the EIP 1967 spec. * of the EIP-1967 specification.
* *
* @param implementation The address of the implementation contract * @param implementation The address of the implementation contract
*/ */
event Upgraded(address indexed implementation); event Upgraded(address indexed implementation);
/** /**
* @notice An event that is emitted each time the owner is upgraded. * @notice An event that is emitted each time the owner is upgraded. This event is part of the
* This event is part of the EIP 1967 spec. * EIP-1967 specification.
* *
* @param previousAdmin The previous owner of the contract * @param previousAdmin The previous owner of the contract
* @param newAdmin The new owner of the contract * @param newAdmin The new owner of the contract
...@@ -40,12 +40,12 @@ contract Proxy { ...@@ -40,12 +40,12 @@ contract Proxy {
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/** /**
* @notice set the initial owner during contract deployment. The * @notice Sets the initial admin during contract deployment. Admin address is stored at the
* owner is stored at the eip1967 owner storage slot so that * EIP-1967 admin storage slot so that accidental storage collision with the
* storage collision with the implementation is not possible. * implementation is not possible.
* *
* @param _admin Address of the initial contract owner. The owner has * @param _admin Address of the initial contract admin. Admin as the ability to access the
* the ability to access the transparent proxy interface. * transparent proxy interface.
*/ */
constructor(address _admin) { constructor(address _admin) {
_changeAdmin(_admin); _changeAdmin(_admin);
...@@ -58,11 +58,10 @@ contract Proxy { ...@@ -58,11 +58,10 @@ contract Proxy {
} }
/** /**
* @notice A modifier that reverts if not called by the owner * @notice A modifier that reverts if not called by the owner or by address(0) to allow
* or by `address(0)` to allow `eth_call` to interact * eth_call to interact with this proxy without needing to use low-level storage
* with the proxy without needing to use low level storage * inspection. We assume that nobody is able to trigger calls from address(0) during
* inspection. It is assumed that nobody controls the private * normal EVM execution.
* key for `address(0)`.
*/ */
modifier proxyCallIfNotAdmin() { modifier proxyCallIfNotAdmin() {
if (msg.sender == _getAdmin() || msg.sender == address(0)) { if (msg.sender == _getAdmin() || msg.sender == address(0)) {
...@@ -74,23 +73,21 @@ contract Proxy { ...@@ -74,23 +73,21 @@ contract Proxy {
} }
/** /**
* @notice Set the implementation contract address. The code at this * @notice Set the implementation contract address. The code at the given address will execute
* address will execute when this contract is called. * when this contract is called.
* *
* @param _implementation The address of the implementation contract * @param _implementation Address of the implementation contract.
*/ */
function upgradeTo(address _implementation) external proxyCallIfNotAdmin { function upgradeTo(address _implementation) external proxyCallIfNotAdmin {
_setImplementation(_implementation); _setImplementation(_implementation);
} }
/** /**
* @notice Set the implementation and call a function in a single * @notice Set the implementation and call a function in a single transaction. Useful to ensure
* transaction. This is useful to ensure atomic `initialize()` * atomic execution of initialization-based upgrades.
* based upgrades.
* *
* @param _implementation The address of the implementation contract * @param _implementation Address of the implementation contract.
* @param _data The calldata to delegatecall the new * @param _data Calldata to delegatecall the new implementation with.
* implementation with
*/ */
function upgradeToAndCall(address _implementation, bytes calldata _data) function upgradeToAndCall(address _implementation, bytes calldata _data)
external external
...@@ -100,7 +97,7 @@ contract Proxy { ...@@ -100,7 +97,7 @@ contract Proxy {
{ {
_setImplementation(_implementation); _setImplementation(_implementation);
(bool success, bytes memory returndata) = _implementation.delegatecall(_data); (bool success, bytes memory returndata) = _implementation.delegatecall(_data);
require(success); require(success, "Proxy: delegatecall to new implementation contract failed");
return returndata; return returndata;
} }
...@@ -146,7 +143,7 @@ contract Proxy { ...@@ -146,7 +143,7 @@ contract Proxy {
/** /**
* @notice Queries the implementation address. * @notice Queries the implementation address.
* *
* @return implementation address. * @return Implementation address.
*/ */
function _getImplementation() internal view returns (address) { function _getImplementation() internal view returns (address) {
address implementation; address implementation;
...@@ -172,7 +169,7 @@ contract Proxy { ...@@ -172,7 +169,7 @@ contract Proxy {
/** /**
* @notice Queries the owner of the proxy contract. * @notice Queries the owner of the proxy contract.
* *
* @return owner address. * @return Owner address.
*/ */
function _getAdmin() internal view returns (address) { function _getAdmin() internal view returns (address) {
address owner; address owner;
...@@ -187,7 +184,6 @@ contract Proxy { ...@@ -187,7 +184,6 @@ contract Proxy {
*/ */
function _doProxyCall() internal { function _doProxyCall() internal {
address implementation = _getImplementation(); address implementation = _getImplementation();
require(implementation != address(0), "Proxy: implementation not initialized"); require(implementation != address(0), "Proxy: implementation not initialized");
assembly { assembly {
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.9; pragma solidity ^0.8.9;
import { Proxy } from "./Proxy.sol";
import { Owned } from "@rari-capital/solmate/src/auth/Owned.sol"; import { Owned } from "@rari-capital/solmate/src/auth/Owned.sol";
import { Proxy } from "./Proxy.sol";
import { Lib_AddressManager } from "../legacy/Lib_AddressManager.sol"; import { Lib_AddressManager } from "../legacy/Lib_AddressManager.sol";
import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol"; import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
// Define static interfaces of these proxies so that we can easily /**
// use staticcall on the getters we need. * @title IStaticERC1967Proxy
interface IStatic_ERC1967Proxy { * @notice IStaticERC1967Proxy is a static version of the ERC1967 proxy interface.
*/
interface IStaticERC1967Proxy {
function implementation() external view returns (address); function implementation() external view returns (address);
function admin() external view returns (address); function admin() external view returns (address);
} }
interface IStatic_L1ChugSplashProxy { /**
* @title IStaticL1ChugSplashProxy
* @notice IStaticL1ChugSplashProxy is a static version of the ChugSplash proxy interface.
*/
interface IStaticL1ChugSplashProxy {
function getImplementation() external view returns (address); function getImplementation() external view returns (address);
function getOwner() external view returns (address); function getOwner() external view returns (address);
...@@ -22,20 +28,17 @@ interface IStatic_L1ChugSplashProxy { ...@@ -22,20 +28,17 @@ interface IStatic_L1ChugSplashProxy {
/** /**
* @title ProxyAdmin * @title ProxyAdmin
* @dev This is an auxiliary contract meant to be assigned as the admin of an ERC1967 Proxy, * @notice This is an auxiliary contract meant to be assigned as the admin of an ERC1967 Proxy,
* based on the OpenZeppelin implementation. It has backwards compatibility logic to work with * based on the OpenZeppelin implementation. It has backwards compatibility logic to work
* the various types of proxies that have been deployed by Optimism. * with the various types of proxies that have been deployed by Optimism in the past.
*/ */
contract ProxyAdmin is Owned { contract ProxyAdmin is Owned {
/** /**
* @notice The proxy types that the ProxyAdmin can manage. * @notice The proxy types that the ProxyAdmin can manage.
* *
* @custom:value ERC1967 Represents an ERC1967 compliant transparent proxy * @custom:value ERC1967 Represents an ERC1967 compliant transparent proxy interface.
* interface, this is the default. * @custom:value Chugsplash Represents the Chugsplash proxy interface (legacy).
* @custom:value Chugsplash Represents the Chugsplash proxy interface, * @custom:value ResolvedDelegate Represents the ResolvedDelegate proxy (legacy).
* this is legacy.
* @custom:value ResolvedDelegate Represents the ResolvedDelegate proxy
* interface, this is legacy.
*/ */
enum ProxyType { enum ProxyType {
ERC1967, ERC1967,
...@@ -45,7 +48,7 @@ contract ProxyAdmin is Owned { ...@@ -45,7 +48,7 @@ contract ProxyAdmin is Owned {
/** /**
* @custom:legacy * @custom:legacy
* @notice A mapping of proxy types, used for backwards compatibility. * @notice A mapping of proxy types, used for backwards compatibility.
*/ */
mapping(address => ProxyType) public proxyType; mapping(address => ProxyType) public proxyType;
...@@ -53,14 +56,14 @@ contract ProxyAdmin is Owned { ...@@ -53,14 +56,14 @@ contract ProxyAdmin is Owned {
* @custom:legacy * @custom:legacy
* @notice A reverse mapping of addresses to names held in the AddressManager. This must be * @notice A reverse mapping of addresses to names held in the AddressManager. This must be
* manually kept up to date with changes in the AddressManager for this contract * manually kept up to date with changes in the AddressManager for this contract
* to be able to work as an admin for the Lib_ResolvedDelegateProxy type. * to be able to work as an admin for the ResolvedDelegateProxy type.
*/ */
mapping(address => string) public implementationName; mapping(address => string) public implementationName;
/** /**
* @custom:legacy * @custom:legacy
* @notice The address of the address manager, this is required to manage the * @notice The address of the address manager, this is required to manage the
* Lib_ResolvedDelegateProxy type. * ResolvedDelegateProxy type.
*/ */
Lib_AddressManager public addressManager; Lib_AddressManager public addressManager;
...@@ -71,36 +74,37 @@ contract ProxyAdmin is Owned { ...@@ -71,36 +74,37 @@ contract ProxyAdmin is Owned {
bool internal upgrading = false; bool internal upgrading = false;
/** /**
* @notice Set the owner of the ProxyAdmin via constructor argument. * @param _owner Address of the initial owner of this contract.
*/ */
constructor(address owner) Owned(owner) {} constructor(address _owner) Owned(_owner) {}
/** /**
* @notice * @notice Sets the proxy type for a given address. Only required for non-standard (legacy)
* proxy types.
* *
* @param _address The address of the proxy. * @param _address Address of the proxy.
* @param _type The type of the proxy. * @param _type Type of the proxy.
*/ */
function setProxyType(address _address, ProxyType _type) external onlyOwner { function setProxyType(address _address, ProxyType _type) external onlyOwner {
proxyType[_address] = _type; proxyType[_address] = _type;
} }
/** /**
* @notice Set the proxy type in the mapping. This needs to be kept up to date by the owner of * @notice Sets the implementation name for a given address. Only required for
* the contract. * ResolvedDelegateProxy type proxies that have an implementation name.
* *
* @param _address The address to be named. * @param _address Address of the ResolvedDelegateProxy.
* @param _name The name of the address. * @param _name Name of the implementation for the proxy.
*/ */
function setImplementationName(address _address, string memory _name) external onlyOwner { function setImplementationName(address _address, string memory _name) external onlyOwner {
implementationName[_address] = _name; implementationName[_address] = _name;
} }
/** /**
* @notice Set the address of the address manager. This is required to manage the legacy * @notice Set the address of the AddressManager. This is required to manage legacy
* `Lib_ResolvedDelegateProxy`. * ResolvedDelegateProxy type proxy contracts.
* *
* @param _address The address of the address manager. * @param _address Address of the AddressManager.
*/ */
function setAddressManager(Lib_AddressManager _address) external onlyOwner { function setAddressManager(Lib_AddressManager _address) external onlyOwner {
addressManager = _address; addressManager = _address;
...@@ -108,11 +112,12 @@ contract ProxyAdmin is Owned { ...@@ -108,11 +112,12 @@ contract ProxyAdmin is Owned {
/** /**
* @custom:legacy * @custom:legacy
* @notice Set an address in the address manager. This is required because only the owner of * @notice Set an address in the address manager. Since only the owner of the AddressManager
* the AddressManager can set the addresses in it. * can directly modify addresses and the ProxyAdmin will own the AddressManager, this
* gives the owner of the ProxyAdmin the ability to modify addresses directly.
* *
* @param _name The name of the address to set in the address manager. * @param _name Name to set within the AddressManager.
* @param _address The address to set in the address manager. * @param _address Address to attach to the given name.
*/ */
function setAddress(string memory _name, address _address) external onlyOwner { function setAddress(string memory _name, address _address) external onlyOwner {
addressManager.setAddress(_name, _address); addressManager.setAddress(_name, _address);
...@@ -120,10 +125,11 @@ contract ProxyAdmin is Owned { ...@@ -120,10 +125,11 @@ contract ProxyAdmin is Owned {
/** /**
* @custom:legacy * @custom:legacy
* @notice Legacy function used by the old Chugsplash proxy to determine if an upgrade is * @notice Legacy function used to tell ChugSplashProxy contracts if an upgrade is happening.
* happening.
* *
* @return Whether or not there is an upgrade going on * @return Whether or not there is an upgrade going on. May not actually tell you whether an
* upgrade is going on, since we don't currently plan to use this variable for anything
* other than a legacy indicator to fix a UX bug in the ChugSplash proxy.
*/ */
function isUpgrading() external view returns (bool) { function isUpgrading() external view returns (bool) {
return upgrading; return upgrading;
...@@ -140,40 +146,40 @@ contract ProxyAdmin is Owned { ...@@ -140,40 +146,40 @@ contract ProxyAdmin is Owned {
} }
/** /**
* @dev Returns the current implementation of `proxy`. * @notice Returns the implementation of the given proxy address.
* This contract must be the admin of `proxy`.
* *
* @param proxy The Proxy to return the implementation of. * @param _proxy Address of the proxy to get the implementation of.
* @return The address of the implementation. *
* @return Address of the implementation of the proxy.
*/ */
function getProxyImplementation(Proxy proxy) external view returns (address) { function getProxyImplementation(Proxy _proxy) external view returns (address) {
ProxyType proxyType = proxyType[address(proxy)]; ProxyType proxyType = proxyType[address(_proxy)];
if (proxyType == ProxyType.ERC1967) { if (proxyType == ProxyType.ERC1967) {
return IStatic_ERC1967Proxy(address(proxy)).implementation(); return IStaticERC1967Proxy(address(_proxy)).implementation();
} else if (proxyType == ProxyType.Chugsplash) { } else if (proxyType == ProxyType.Chugsplash) {
return IStatic_L1ChugSplashProxy(address(proxy)).getImplementation(); return IStaticL1ChugSplashProxy(address(_proxy)).getImplementation();
} else if (proxyType == ProxyType.ResolvedDelegate) { } else if (proxyType == ProxyType.ResolvedDelegate) {
return addressManager.getAddress(implementationName[address(proxy)]); return addressManager.getAddress(implementationName[address(_proxy)]);
} else { } else {
revert("ProxyAdmin: unknown proxy type"); revert("ProxyAdmin: unknown proxy type");
} }
} }
/** /**
* @dev Returns the current admin of `proxy`. * @notice Returns the admin of the given proxy address.
* This contract must be the admin of `proxy`. *
* @param _proxy Address of the proxy to get the admin of.
* *
* @param proxy The Proxy to return the admin of. * @return Address of the admin of the proxy.
* @return The address of the admin.
*/ */
function getProxyAdmin(Proxy proxy) external view returns (address) { function getProxyAdmin(Proxy _proxy) external view returns (address) {
ProxyType proxyType = proxyType[address(proxy)]; ProxyType proxyType = proxyType[address(_proxy)];
if (proxyType == ProxyType.ERC1967) { if (proxyType == ProxyType.ERC1967) {
return IStatic_ERC1967Proxy(address(proxy)).admin(); return IStaticERC1967Proxy(address(_proxy)).admin();
} else if (proxyType == ProxyType.Chugsplash) { } else if (proxyType == ProxyType.Chugsplash) {
return IStatic_L1ChugSplashProxy(address(proxy)).getOwner(); return IStaticL1ChugSplashProxy(address(_proxy)).getOwner();
} else if (proxyType == ProxyType.ResolvedDelegate) { } else if (proxyType == ProxyType.ResolvedDelegate) {
return addressManager.owner(); return addressManager.owner();
} else { } else {
...@@ -182,71 +188,70 @@ contract ProxyAdmin is Owned { ...@@ -182,71 +188,70 @@ contract ProxyAdmin is Owned {
} }
/** /**
* @dev Changes the admin of `proxy` to `newAdmin`. This contract must be the current admin * @notice Updates the admin of the given proxy address.
* of `proxy`.
* *
* @param proxy The proxy that will have its admin updated. * @param _proxy Address of the proxy to update.
* @param newAdmin The address of the admin to update to. * @param _newAdmin Address of the new proxy admin.
*/ */
function changeProxyAdmin(Proxy proxy, address newAdmin) external onlyOwner { function changeProxyAdmin(Proxy _proxy, address _newAdmin) external onlyOwner {
ProxyType proxyType = proxyType[address(proxy)]; ProxyType proxyType = proxyType[address(_proxy)];
if (proxyType == ProxyType.ERC1967) { if (proxyType == ProxyType.ERC1967) {
proxy.changeAdmin(newAdmin); _proxy.changeAdmin(_newAdmin);
} else if (proxyType == ProxyType.Chugsplash) { } else if (proxyType == ProxyType.Chugsplash) {
L1ChugSplashProxy(payable(proxy)).setOwner(newAdmin); L1ChugSplashProxy(payable(_proxy)).setOwner(_newAdmin);
} else if (proxyType == ProxyType.ResolvedDelegate) { } else if (proxyType == ProxyType.ResolvedDelegate) {
addressManager.transferOwnership(newAdmin); addressManager.transferOwnership(_newAdmin);
} else { } else {
revert("ProxyAdmin: unknown proxy type"); revert("ProxyAdmin: unknown proxy type");
} }
} }
/** /**
* @dev Upgrades `proxy` to `implementation`. This contract must be the admin of `proxy`. * @notice Changes a proxy's implementation contract.
* *
* @param proxy The address of the proxy. * @param _proxy Address of the proxy to upgrade.
* @param implementation The address of the implementation. * @param _implementation Address of the new implementation address.
*/ */
function upgrade(Proxy proxy, address implementation) public onlyOwner { function upgrade(Proxy _proxy, address _implementation) public onlyOwner {
ProxyType proxyType = proxyType[address(proxy)]; ProxyType proxyType = proxyType[address(_proxy)];
if (proxyType == ProxyType.ERC1967) { if (proxyType == ProxyType.ERC1967) {
proxy.upgradeTo(implementation); _proxy.upgradeTo(_implementation);
} else if (proxyType == ProxyType.Chugsplash) { } else if (proxyType == ProxyType.Chugsplash) {
L1ChugSplashProxy(payable(proxy)).setStorage( L1ChugSplashProxy(payable(_proxy)).setStorage(
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc,
bytes32(uint256(uint160(implementation))) bytes32(uint256(uint160(_implementation)))
); );
} else if (proxyType == ProxyType.ResolvedDelegate) { } else if (proxyType == ProxyType.ResolvedDelegate) {
string memory name = implementationName[address(proxy)]; string memory name = implementationName[address(_proxy)];
addressManager.setAddress(name, implementation); addressManager.setAddress(name, _implementation);
} else { } else {
revert("ProxyAdmin: unknown proxy type"); revert("ProxyAdmin: unknown proxy type");
} }
} }
/** /**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. * @notice Changes a proxy's implementation contract and delegatecalls the new implementation
* This contract must be the admin of `proxy`. * with some given data. Useful for atomic upgrade-and-initialize calls.
* *
* @param proxy The proxy to call. * @param _proxy Address of the proxy to upgrade.
* @param implementation The implementation to upgrade the proxy to. * @param _implementation Address of the new implementation address.
* @param data The calldata to pass to the implementation. * @param _data Data to trigger the new implementation with.
*/ */
function upgradeAndCall( function upgradeAndCall(
Proxy proxy, Proxy _proxy,
address implementation, address _implementation,
bytes memory data bytes memory _data
) external payable onlyOwner { ) external payable onlyOwner {
ProxyType proxyType = proxyType[address(proxy)]; ProxyType proxyType = proxyType[address(_proxy)];
if (proxyType == ProxyType.ERC1967) { if (proxyType == ProxyType.ERC1967) {
proxy.upgradeToAndCall{ value: msg.value }(implementation, data); _proxy.upgradeToAndCall{ value: msg.value }(_implementation, _data);
} else { } else {
// reverts if proxy type is unknown // reverts if proxy type is unknown
upgrade(proxy, implementation); upgrade(_proxy, _implementation);
(bool success, ) = address(proxy).call{ value: msg.value }(data); (bool success, ) = address(_proxy).call{ value: msg.value }(_data);
require(success); require(success);
} }
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.9; pragma solidity ^0.8.9;
/* Interface Imports */
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./SupportedInterfaces.sol";
/* Library Imports */
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IRemoteToken, IL1Token } from "./SupportedInterfaces.sol";
import { CrossDomainMessenger } from "./CrossDomainMessenger.sol"; import { CrossDomainMessenger } from "./CrossDomainMessenger.sol";
import { OptimismMintableERC20 } from "./OptimismMintableERC20.sol"; import { OptimismMintableERC20 } from "./OptimismMintableERC20.sol";
/** /**
* @title StandardBridge * @title StandardBridge
* This contract can manage a 1:1 bridge between two domains for both * @notice StandardBridge is a base contract for the L1 and L2 standard ERC20 bridges.
* ETH (native asset) and ERC20s.
* This contract should be deployed behind a proxy.
* TODO: do we want a donateERC20 function as well?
*/ */
abstract contract StandardBridge { abstract contract StandardBridge {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
/********** /**
* Events * * @notice Emitted when an ETH bridge is initiated to the other chain.
**********/ *
* @param from Address of the sender.
* @param to Address of the receiver.
* @param amount Amount of ETH sent.
* @param extraData Extra data sent with the transaction.
*/
event ETHBridgeInitiated( event ETHBridgeInitiated(
address indexed _from, address indexed from,
address indexed _to, address indexed to,
uint256 _amount, uint256 amount,
bytes _extraData bytes extraData
); );
/**
* @notice Emitted when an ETH bridge is finalized on this chain.
*
* @param from Address of the sender.
* @param to Address of the receiver.
* @param amount Amount of ETH sent.
* @param extraData Extra data sent with the transaction.
*/
event ETHBridgeFinalized( event ETHBridgeFinalized(
address indexed _from, address indexed from,
address indexed _to, address indexed to,
uint256 _amount, uint256 amount,
bytes _extraData bytes extraData
); );
/**
* @notice Emitted when an ERC20 bridge is initiated to the other chain.
*
* @param localToken Address of the ERC20 on this chain.
* @param remoteToken Address of the ERC20 on the remote chain.
* @param from Address of the sender.
* @param to Address of the receiver.
* @param amount Amount of ETH sent.
* @param extraData Extra data sent with the transaction.
*/
event ERC20BridgeInitiated( event ERC20BridgeInitiated(
address indexed _localToken, address indexed localToken,
address indexed _remoteToken, address indexed remoteToken,
address indexed _from, address indexed from,
address _to, address to,
uint256 _amount, uint256 amount,
bytes _extraData bytes extraData
); );
/**
* @notice Emitted when an ERC20 bridge is finalized on this chain.
*
* @param localToken Address of the ERC20 on this chain.
* @param remoteToken Address of the ERC20 on the remote chain.
* @param from Address of the sender.
* @param to Address of the receiver.
* @param amount Amount of ETH sent.
* @param extraData Extra data sent with the transaction.
*/
event ERC20BridgeFinalized( event ERC20BridgeFinalized(
address indexed _localToken, address indexed localToken,
address indexed _remoteToken, address indexed remoteToken,
address indexed _from, address indexed from,
address _to, address to,
uint256 _amount, uint256 amount,
bytes _extraData bytes extraData
); );
/**
* @notice Emitted when an ERC20 bridge to this chain fails.
*
* @param localToken Address of the ERC20 on this chain.
* @param remoteToken Address of the ERC20 on the remote chain.
* @param from Address of the sender.
* @param to Address of the receiver.
* @param amount Amount of ETH sent.
* @param extraData Extra data sent with the transaction.
*/
event ERC20BridgeFailed( event ERC20BridgeFailed(
address indexed _localToken, address indexed localToken,
address indexed _remoteToken, address indexed remoteToken,
address indexed _from, address indexed from,
address _to, address to,
uint256 _amount, uint256 amount,
bytes _extraData bytes extraData
); );
/*************
* Constants *
*************/
/** /**
* @notice The L2 gas limit set when eth is depoisited using the receive() function. * @notice The L2 gas limit set when eth is depoisited using the receive() function.
*/ */
uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000; uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000;
/*************
* Variables *
*************/
/** /**
* @notice The messenger contract on the same domain * @notice Messenger contract on this domain.
*/ */
CrossDomainMessenger public messenger; CrossDomainMessenger public messenger;
/** /**
* @notice The corresponding bridge on the other domain * @notice Corresponding bridge on the other domain.
*/ */
StandardBridge public otherBridge; StandardBridge public otherBridge;
/**
* @notice Mapping that stores deposits for a given pair of local and remote tokens.
*/
mapping(address => mapping(address => uint256)) public deposits; mapping(address => mapping(address => uint256)) public deposits;
/*************
* Modifiers *
*************/
/** /**
* @notice Only allow EOAs to call the functions. Note that this * @notice Only allow EOAs to call the functions. Note that this is not safe against contracts
* is not safe against contracts calling code during their constructor * calling code within their constructors, but also doesn't really matter since we're
* just trying to prevent users accidentally depositing with smart contract wallets.
*/ */
modifier onlyEOA() { modifier onlyEOA() {
require(!Address.isContract(msg.sender), "Account not EOA"); require(!Address.isContract(msg.sender), "Account not EOA");
...@@ -107,8 +134,7 @@ abstract contract StandardBridge { ...@@ -107,8 +134,7 @@ abstract contract StandardBridge {
} }
/** /**
* @notice Ensures that the caller is the messenger, and that * @notice Ensures that the caller is a cross-chain message from the other bridge.
* it has the l2Sender value set to the address of the remote Token Bridge.
*/ */
modifier onlyOtherBridge() { modifier onlyOtherBridge() {
require( require(
...@@ -119,33 +145,45 @@ abstract contract StandardBridge { ...@@ -119,33 +145,45 @@ abstract contract StandardBridge {
_; _;
} }
/**
* @notice Ensures that the caller is this contract.
*/
modifier onlySelf() { modifier onlySelf() {
require(msg.sender == address(this), "Function can only be called by self."); require(msg.sender == address(this), "Function can only be called by self.");
_; _;
} }
/********************
* Public Functions *
********************/
/** /**
* @notice EOAs can simply send ETH to this contract to have it be deposited * @notice Allows EOAs to deposit ETH by sending directly to the bridge.
* to L2 through the standard bridge.
*/ */
receive() external payable onlyEOA { receive() external payable onlyEOA {
_initiateBridgeETH(msg.sender, msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, bytes("")); _initiateBridgeETH(msg.sender, msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, bytes(""));
} }
/** /**
* @notice Send ETH to the message sender on the remote domain * @notice Sends ETH to the sender's address on the other chain.
*
* @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function bridgeETH(uint32 _minGasLimit, bytes calldata _extraData) public payable onlyEOA { function bridgeETH(uint32 _minGasLimit, bytes calldata _extraData) public payable onlyEOA {
_initiateBridgeETH(msg.sender, msg.sender, msg.value, _minGasLimit, _extraData); _initiateBridgeETH(msg.sender, msg.sender, msg.value, _minGasLimit, _extraData);
} }
/** /**
* @notice Send ETH to a specified account on the remote domain. Note that if ETH is sent to a * @notice Sends ETH to a receiver's address on the other chain. Note that if ETH is sent to a
* contract and the call fails, then that ETH will be locked in the other bridge. * smart contract and the call fails, the ETH will be temporarily locked in the
* StandardBridge on the other chain until the call is replayed. If the call cannot be
* replayed with any amount of gas (call always reverts), then the ETH will be
* permanently locked in the StandardBridge on the other chain.
*
* @param _to Address of the receiver.
* @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function bridgeETHTo( function bridgeETHTo(
address _to, address _to,
...@@ -156,7 +194,18 @@ abstract contract StandardBridge { ...@@ -156,7 +194,18 @@ abstract contract StandardBridge {
} }
/** /**
* @notice Send an ERC20 to the message sender on the remote domain * @notice Sends ERC20 tokens to the sender's address on the other chain. Note that if the
* ERC20 token on the other chain does not recognize the local token as the correct
* pair token, the ERC20 bridge will fail and the tokens will be returned to sender on
* this chain.
*
* @param _localToken Address of the ERC20 on this chain.
* @param _remoteToken Address of the corresponding token on the remote chain.
* @param _amount Amount of local tokens to deposit.
* @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function bridgeERC20( function bridgeERC20(
address _localToken, address _localToken,
...@@ -177,7 +226,19 @@ abstract contract StandardBridge { ...@@ -177,7 +226,19 @@ abstract contract StandardBridge {
} }
/** /**
* @notice Send an ERC20 to a specified account on the remote domain * @notice Sends ERC20 tokens to a receiver's address on the other chain. Note that if the
* ERC20 token on the other chain does not recognize the local token as the correct
* pair token, the ERC20 bridge will fail and the tokens will be returned to sender on
* this chain.
*
* @param _localToken Address of the ERC20 on this chain.
* @param _remoteToken Address of the corresponding token on the remote chain.
* @param _to Address of the receiver.
* @param _amount Amount of local tokens to deposit.
* @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function bridgeERC20To( function bridgeERC20To(
address _localToken, address _localToken,
...@@ -199,7 +260,15 @@ abstract contract StandardBridge { ...@@ -199,7 +260,15 @@ abstract contract StandardBridge {
} }
/** /**
* @notice Finalize an ETH sending transaction sent from a remote domain * @notice Finalizes an ETH bridge on this chain. Can only be triggered by the other
* StandardBridge contract on the remote chain.
*
* @param _from Address of the sender.
* @param _to Address of the receiver.
* @param _amount Amount of ETH being bridged.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function finalizeBridgeETH( function finalizeBridgeETH(
address _from, address _from,
...@@ -216,7 +285,18 @@ abstract contract StandardBridge { ...@@ -216,7 +285,18 @@ abstract contract StandardBridge {
} }
/** /**
* @notice Finalize an ERC20 sending transaction sent from a remote domain * @notice Finalizes an ERC20 bridge on this chain. Can only be triggered by the other
* StandardBridge contract on the remote chain.
*
*
* @param _localToken Address of the ERC20 on this chain.
* @param _remoteToken Address of the corresponding token on the remote chain.
* @param _from Address of the sender.
* @param _to Address of the receiver.
* @param _amount Amount of ETH being bridged.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function finalizeBridgeERC20( function finalizeBridgeERC20(
address _localToken, address _localToken,
...@@ -246,6 +326,18 @@ abstract contract StandardBridge { ...@@ -246,6 +326,18 @@ abstract contract StandardBridge {
} }
} }
/**
* @notice Completes an outbound token transfer. Public function, but can only be called by
* this contract. It's security critical that there be absolutely no way for anyone to
* trigger this function, except by explicit trigger within this contract. Used as a
* simple way to be able to try/catch any type of revert that could occur during an
* ERC20 mint/transfer.
*
* @param _localToken Address of the ERC20 on this chain.
* @param _remoteToken Address of the corresponding token on the remote chain.
* @param _to Address of the receiver.
* @param _amount Amount of ETH being bridged.
*/
function completeOutboundTransfer( function completeOutboundTransfer(
address _localToken, address _localToken,
address _remoteToken, address _remoteToken,
...@@ -269,14 +361,11 @@ abstract contract StandardBridge { ...@@ -269,14 +361,11 @@ abstract contract StandardBridge {
} }
} }
/**********************
* Internal Functions *
**********************/
/** /**
* @notice Initialize the StandardBridge contract with the address of * @notice Initializer.
* the messenger on the same domain as well as the address of the bridge *
* on the remote domain * @param _messenger Address of CrossDomainMessenger on this network.
* @param _otherBridge Address of the other StandardBridge contract.
*/ */
function _initialize(address payable _messenger, address payable _otherBridge) internal { function _initialize(address payable _messenger, address payable _otherBridge) internal {
require(address(messenger) == address(0), "Contract has already been initialized."); require(address(messenger) == address(0), "Contract has already been initialized.");
...@@ -286,7 +375,15 @@ abstract contract StandardBridge { ...@@ -286,7 +375,15 @@ abstract contract StandardBridge {
} }
/** /**
* @notice Bridge ETH to the remote chain through the messenger * @notice Initiates a bridge of ETH through the CrossDomainMessenger.
*
* @param _from Address of the sender.
* @param _to Address of the receiver.
* @param _amount Amount of ETH being bridged.
* @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function _initiateBridgeETH( function _initiateBridgeETH(
address _from, address _from,
...@@ -311,7 +408,16 @@ abstract contract StandardBridge { ...@@ -311,7 +408,16 @@ abstract contract StandardBridge {
} }
/** /**
* @notice Bridge an ERC20 to the remote chain through the messengers * @notice Sends ERC20 tokens to a receiver's address on the other chain.
*
* @param _localToken Address of the ERC20 on this chain.
* @param _remoteToken Address of the corresponding token on the remote chain.
* @param _to Address of the receiver.
* @param _amount Amount of local tokens to deposit.
* @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function _initiateBridgeERC20( function _initiateBridgeERC20(
address _localToken, address _localToken,
...@@ -351,7 +457,17 @@ abstract contract StandardBridge { ...@@ -351,7 +457,17 @@ abstract contract StandardBridge {
} }
/** /**
* @notice Bridge an ERC20 to the remote chain through the messengers * @notice Sends ERC20 tokens to a receiver's address on the other chain WITHOUT doing any
* validation. Be EXTREMELY careful when using this function.
*
* @param _localToken Address of the ERC20 on this chain.
* @param _remoteToken Address of the corresponding token on the remote chain.
* @param _to Address of the receiver.
* @param _amount Amount of local tokens to deposit.
* @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
* @param _extraData Extra data to be sent with the transaction. Note that the recipient will
* not be triggered with this data, but it will be emitted and can be used
* to identify the transaction.
*/ */
function _initiateBridgeERC20Unchecked( function _initiateBridgeERC20Unchecked(
address _localToken, address _localToken,
...@@ -383,10 +499,11 @@ abstract contract StandardBridge { ...@@ -383,10 +499,11 @@ abstract contract StandardBridge {
} }
/** /**
* Checks if a given address is an OptimismMintableERC20. Not perfect, but good enough. * @notice Checks if a given address is an OptimismMintableERC20. Not perfect, but good enough.
* Just the way we like it. * Just the way we like it.
* *
* @param _token Address of the token to check. * @param _token Address of the token to check.
*
* @return True if the token is an OptimismMintableERC20. * @return True if the token is an OptimismMintableERC20.
*/ */
function _isOptimismMintableERC20(address _token) internal view returns (bool) { function _isOptimismMintableERC20(address _token) internal view returns (bool) {
...@@ -394,10 +511,11 @@ abstract contract StandardBridge { ...@@ -394,10 +511,11 @@ abstract contract StandardBridge {
} }
/** /**
* Checks if the "other token" is the correct pair token for the OptimismMintableERC20. * @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20.
* *
* @param _mintableToken OptimismMintableERC20 to check against. * @param _mintableToken OptimismMintableERC20 to check against.
* @param _otherToken Pair token to check. * @param _otherToken Pair token to check.
*
* @return True if the other token is the correct pair token for the OptimismMintableERC20. * @return True if the other token is the correct pair token for the OptimismMintableERC20.
*/ */
function _isCorrectTokenPair(address _mintableToken, address _otherToken) function _isCorrectTokenPair(address _mintableToken, address _otherToken)
......
...@@ -5,17 +5,17 @@ pragma solidity ^0.8.9; ...@@ -5,17 +5,17 @@ pragma solidity ^0.8.9;
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IRemoteToken { interface IRemoteToken {
function mint(address _to, uint256 _amount) external virtual; function mint(address _to, uint256 _amount) external;
function burn(address _from, uint256 _amount) external virtual; function burn(address _from, uint256 _amount) external;
function remoteToken() external virtual; function remoteToken() external;
} }
interface IL1Token { interface IL1Token {
function mint(address _to, uint256 _amount) external virtual; function mint(address _to, uint256 _amount) external;
function burn(address _from, uint256 _amount) external virtual; function burn(address _from, uint256 _amount) external;
function l1Token() external virtual; function l1Token() external;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment