Commit 1d5074df authored by ben-chain's avatar ben-chain Committed by GitHub

Standardized ETH and ERC20 Gateways (#222)

* initial

* get .calls test working

* add assertions to withdrawal tests

* add missing finalizeDeposit tests

* clean up tests

* clean up crosschain library

* further clean up tests

* clean up L1 Gateway, fix bug

* further clean up contracts

* quick save

* quick save

* Add unsupported to fix compiler bug, need to remove this

* fix constructor inputs

* build fixes

* improve test case names

* l1 gateway testing WIP

* add comment on init

* Add withdraw tests

* temp only

* All tests implemented, 2 still failing, needs polish

* All tests implemented, 2 still failing, needs polish

* add failing test for finalizeWithdrawAndCall

* Add univ2erc20, broken build tho

* get contracts building

* clean up tests and interface

* updates

* move to uniswap ERC20

* more updates, fix gateway tests with uniswap

* finish deploy config?

* Add ETH deposit gateway (OVM_L1ETHGateway) (#225)

* OVM_L1ETHGateway almost working

* OVM_L1ETHGateway tests all passing

* clean up unused stuff

* lower gaslimit

* lower gas further

* typo

* break out helper

* resolve messenger

* update deploy config

* renaming

* lint

* update tests to use AM

* restructure fs layout

* clean unused envar

* remove interface version bound

* remove todo

* various minor cleanups

* add safemath to contract account

* update naming conventions

* fix test config and test name

* cleanup for consistency

* fix eth send test

* remove .only

* clean up, add deposit gas limit

* lint

* add gas config and tests

* fix indent
Co-authored-by: default avatarben <ben@bens-MacBook-Pro.local>
Co-authored-by: default avatarMaurelian <maurelian@protonmail.ch>
Co-authored-by: default avatarMatt Masurka <m.masurka@gmail.com>
parent 13e8a698
...@@ -95,9 +95,6 @@ const RELAYER_PRIVATE_KEY = env.RELAYER_PRIVATE_KEY; ...@@ -95,9 +95,6 @@ const RELAYER_PRIVATE_KEY = env.RELAYER_PRIVATE_KEY;
owner: WHITELIST_OWNER, owner: WHITELIST_OWNER,
allowArbitraryContractDeployment: WHITELIST_ALLOW_ARBITRARY_CONTRACT_DEPLOYMENT allowArbitraryContractDeployment: WHITELIST_ALLOW_ARBITRARY_CONTRACT_DEPLOYMENT
}, },
ethConfig: {
initialAmount: 0,
},
deployOverrides: { deployOverrides: {
gasLimit: DEPLOY_TX_GAS_LIMIT gasLimit: DEPLOY_TX_GAS_LIMIT
}, },
......
...@@ -93,7 +93,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -93,7 +93,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// Transfer fee to relayer. // Transfer fee to relayer.
address relayer = Lib_SafeExecutionManagerWrapper.safeCALLER(); address relayer = Lib_SafeExecutionManagerWrapper.safeCALLER();
uint256 fee = decodedTx.gasLimit * decodedTx.gasPrice; uint256 fee = Lib_SafeMathWrapper.mul(decodedTx.gasLimit, decodedTx.gasPrice);
(bool success, ) = Lib_SafeExecutionManagerWrapper.safeCALL( (bool success, ) = Lib_SafeExecutionManagerWrapper.safeCALL(
gasleft(), gasleft(),
ETH_ERC20_ADDRESS, ETH_ERC20_ADDRESS,
......
...@@ -3,10 +3,10 @@ pragma solidity >0.5.0 <0.8.0; ...@@ -3,10 +3,10 @@ pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
/* Interface Imports */ /* Interface Imports */
import { iAbs_BaseCrossDomainMessenger } from "../../iOVM/bridge/iAbs_BaseCrossDomainMessenger.sol"; import { iAbs_BaseCrossDomainMessenger } from "../../../iOVM/bridge/messenging/iAbs_BaseCrossDomainMessenger.sol";
/* Library Imports */ /* Library Imports */
import { Lib_ReentrancyGuard } from "../../libraries/utils/Lib_ReentrancyGuard.sol"; import { Lib_ReentrancyGuard } from "../../../libraries/utils/Lib_ReentrancyGuard.sol";
/** /**
* @title Abs_BaseCrossDomainMessenger * @title Abs_BaseCrossDomainMessenger
......
...@@ -3,16 +3,16 @@ pragma solidity >0.5.0 <0.8.0; ...@@ -3,16 +3,16 @@ pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
/* Library Imports */ /* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol"; import { Lib_OVMCodec } from "../../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol"; import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_AddressManager } from "../../libraries/resolver/Lib_AddressManager.sol"; import { Lib_AddressManager } from "../../../libraries/resolver/Lib_AddressManager.sol";
import { Lib_SecureMerkleTrie } from "../../libraries/trie/Lib_SecureMerkleTrie.sol"; import { Lib_SecureMerkleTrie } from "../../../libraries/trie/Lib_SecureMerkleTrie.sol";
import { Lib_ReentrancyGuard } from "../../libraries/utils/Lib_ReentrancyGuard.sol"; import { Lib_ReentrancyGuard } from "../../../libraries/utils/Lib_ReentrancyGuard.sol";
/* Interface Imports */ /* Interface Imports */
import { iOVM_L1CrossDomainMessenger } from "../../iOVM/bridge/iOVM_L1CrossDomainMessenger.sol"; import { iOVM_L1CrossDomainMessenger } from "../../../iOVM/bridge/messenging/iOVM_L1CrossDomainMessenger.sol";
import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalTransactionChain.sol"; import { iOVM_CanonicalTransactionChain } from "../../../iOVM/chain/iOVM_CanonicalTransactionChain.sol";
import { iOVM_StateCommitmentChain } from "../../iOVM/chain/iOVM_StateCommitmentChain.sol"; import { iOVM_StateCommitmentChain } from "../../../iOVM/chain/iOVM_StateCommitmentChain.sol";
/* Contract Imports */ /* Contract Imports */
import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol"; import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol";
......
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
pragma solidity >0.5.0 <0.8.0; pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
/* Interface Imports */ /* Interface Imports */
import { iOVM_L1CrossDomainMessenger } from "../../iOVM/bridge/iOVM_L1CrossDomainMessenger.sol"; import { iOVM_L1CrossDomainMessenger } from "../../../iOVM/bridge/messenging/iOVM_L1CrossDomainMessenger.sol";
import { iOVM_L1MultiMessageRelayer } from "../../iOVM/bridge/iOVM_L1MultiMessageRelayer.sol"; import { iOVM_L1MultiMessageRelayer } from "../../../iOVM/bridge/messenging/iOVM_L1MultiMessageRelayer.sol";
/* Contract Imports */ /* Contract Imports */
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol"; import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol";
/** /**
......
...@@ -3,13 +3,13 @@ pragma solidity >0.5.0 <0.8.0; ...@@ -3,13 +3,13 @@ pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
/* Library Imports */ /* Library Imports */
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol"; import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_ReentrancyGuard } from "../../libraries/utils/Lib_ReentrancyGuard.sol"; import { Lib_ReentrancyGuard } from "../../../libraries/utils/Lib_ReentrancyGuard.sol";
/* Interface Imports */ /* Interface Imports */
import { iOVM_L2CrossDomainMessenger } from "../../iOVM/bridge/iOVM_L2CrossDomainMessenger.sol"; import { iOVM_L2CrossDomainMessenger } from "../../../iOVM/bridge/messenging/iOVM_L2CrossDomainMessenger.sol";
import { iOVM_L1MessageSender } from "../../iOVM/precompiles/iOVM_L1MessageSender.sol"; import { iOVM_L1MessageSender } from "../../../iOVM/precompiles/iOVM_L1MessageSender.sol";
import { iOVM_L2ToL1MessagePasser } from "../../iOVM/precompiles/iOVM_L2ToL1MessagePasser.sol"; import { iOVM_L2ToL1MessagePasser } from "../../../iOVM/precompiles/iOVM_L2ToL1MessagePasser.sol";
/* Contract Imports */ /* Contract Imports */
import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol"; import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol";
......
// SPDX-License-Identifier: MIT
// @unsupported: ovm
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_L1ERC20Gateway } from "../../../iOVM/bridge/tokens/iOVM_L1ERC20Gateway.sol";
import { iOVM_L2DepositedERC20 } from "../../../iOVM/bridge/tokens/iOVM_L2DepositedERC20.sol";
import { iOVM_ERC20 } from "../../../iOVM/precompiles/iOVM_ERC20.sol";
/* Library Imports */
import { OVM_CrossDomainEnabled } from "../../../libraries/bridge/OVM_CrossDomainEnabled.sol";
/**
* @title OVM_L1ERC20Gateway
* @dev The L1 ERC20 Gateway is a contract which stores deposited L1 funds that are in use on L2.
* It synchronizes a corresponding L2 ERC20 Gateway, informing it of deposits, and listening to it
* for newly finalized withdrawals.
*
* Compiler used: solc
* Runtime target: EVM
*/
contract OVM_L1ERC20Gateway is iOVM_L1ERC20Gateway, OVM_CrossDomainEnabled {
/********************************
* External Contract References *
********************************/
iOVM_ERC20 public l1ERC20;
address public l2ERC20Gateway;
/***************
* Constructor *
***************/
/**
* @param _l1ERC20 L1 ERC20 address this contract stores deposits for
* @param _l2ERC20Gateway L2 Gateway address on the chain being deposited into
* @param _l1messenger L1 Messenger address being used for cross-chain communications.
*/
constructor(
iOVM_ERC20 _l1ERC20,
address _l2ERC20Gateway,
address _l1messenger
)
OVM_CrossDomainEnabled(_l1messenger)
{
l1ERC20 = _l1ERC20;
l2ERC20Gateway = _l2ERC20Gateway;
}
/**************
* Depositing *
**************/
/**
* @dev deposit an amount of the ERC20 to the caller's balance on L2
* @param _amount Amount of the ERC20 to deposit
*/
function deposit(
uint _amount
)
external
override
{
_initiateDeposit(msg.sender, msg.sender, _amount);
}
/**
* @dev deposit an amount of ERC20 to a recipients's balance on L2
* @param _to L2 address to credit the withdrawal to
* @param _amount Amount of the ERC20 to deposit
*/
function depositTo(
address _to,
uint _amount
)
external
override
{
_initiateDeposit(msg.sender, _to, _amount);
}
/**
* @dev Performs the logic for deposits by storing the ERC20 and informing the L2 ERC20 Gateway of the deposit.
*
* @param _from Account to pull the deposit from on L1
* @param _to Account to give the deposit to on L2
* @param _amount Amount of the ERC20 to deposit.
*/
function _initiateDeposit(
address _from,
address _to,
uint _amount
)
internal
{
// Hold on to the newly deposited funds
l1ERC20.transferFrom(
_from,
address(this),
_amount
);
// Construct calldata for l2ERC20Gateway.finalizeDeposit(_to, _amount)
bytes memory data = abi.encodeWithSelector(
iOVM_L2DepositedERC20.finalizeDeposit.selector,
_to,
_amount
);
// Send calldata into L2
sendCrossDomainMessage(
l2ERC20Gateway,
data,
DEFAULT_FINALIZE_DEPOSIT_L2_GAS
);
emit DepositInitiated(_from, _to, _amount);
}
/*************************************
* Cross-chain Function: Withdrawing *
*************************************/
/**
* @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the
* L1 ERC20 token.
* This call will fail if the initialized withdrawal from L2 has not been finalized.
*
* @param _to L1 address to credit the withdrawal to
* @param _amount Amount of the ERC20 to withdraw
*/
function finalizeWithdrawal(
address _to,
uint _amount
)
external
override
onlyFromCrossDomainAccount(l2ERC20Gateway)
{
l1ERC20.transfer(_to, _amount);
emit WithdrawalFinalized(_to, _amount);
}
}
// SPDX-License-Identifier: MIT
// @unsupported: ovm
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_L1ETHGateway } from "../../../iOVM/bridge/tokens/iOVM_L1ETHGateway.sol";
import { iOVM_L2DepositedERC20 } from "../../../iOVM/bridge/tokens/iOVM_L2DepositedERC20.sol";
import { iOVM_ERC20 } from "../../../iOVM/precompiles/iOVM_ERC20.sol";
/* Library Imports */
import { OVM_CrossDomainEnabled } from "../../../libraries/bridge/OVM_CrossDomainEnabled.sol";
import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol";
/**
* @title OVM_L1ETHGateway
* @dev The L1 ETH Gateway is a contract which stores deposited ETH that is in use on L2.
*
* Compiler used: solc
* Runtime target: EVM
*/
contract OVM_L1ETHGateway is iOVM_L1ETHGateway, OVM_CrossDomainEnabled, Lib_AddressResolver {
/********************************
* External Contract References *
********************************/
address public l2ERC20Gateway;
/***************
* Constructor *
***************/
/**
* @param _libAddressManager Address manager for this OE deployment
*/
constructor(
address _libAddressManager,
address _l2ERC20Gateway
)
OVM_CrossDomainEnabled(address(0)) // overridden in constructor code
Lib_AddressResolver(_libAddressManager)
{
l2ERC20Gateway = _l2ERC20Gateway;
messenger = resolve("Proxy__OVM_L1CrossDomainMessenger"); // overrides OVM_CrossDomainEnabled constructor setting because resolve() is not yet accessible
}
/**************
* Depositing *
**************/
/**
* @dev deposit an amount of the ERC20 to the caller's balance on L2
*/
function deposit()
external
override
payable
{
_initiateDeposit(msg.sender, msg.sender);
}
/**
* @dev deposit an amount of ERC20 to a recipients's balance on L2
* @param _to L2 address to credit the withdrawal to
*/
function depositTo(
address _to
)
external
override
payable
{
_initiateDeposit(msg.sender, _to);
}
/**
* @dev Performs the logic for deposits by storing the ERC20 and informing the L2 ERC20 Gateway of the deposit.
*
* @param _from Account to pull the deposit from on L1
* @param _to Account to give the deposit to on L2
*/
function _initiateDeposit(
address _from,
address _to
)
internal
{
// Construct calldata for l2ERC20Gateway.finalizeDeposit(_to, _amount)
bytes memory data =
abi.encodeWithSelector(
iOVM_L2DepositedERC20.finalizeDeposit.selector,
_to,
msg.value
);
// Send calldata into L2
sendCrossDomainMessage(
l2ERC20Gateway,
data,
DEFAULT_FINALIZE_DEPOSIT_L2_GAS
);
emit DepositInitiated(_from, _to, msg.value);
}
/*************************
* Cross-chain Functions *
*************************/
/**
* @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the
* L1 ERC20 token.
* This call will fail if the initialized withdrawal from L2 has not been finalized.
*
* @param _to L1 address to credit the withdrawal to
* @param _amount Amount of the ERC20 to withdraw
*/
function finalizeWithdrawal(
address _to,
uint256 _amount
)
external
override
onlyFromCrossDomainAccount(l2ERC20Gateway)
{
_safeTransferETH(_to, _amount);
emit WithdrawalFinalized(_to, _amount);
}
/**********************************
* Internal Functions: Accounting *
**********************************/
/**
* @dev Internal accounting function for moving around L1 ETH.
*
* @param _to L1 address to transfer ETH to
* @param _value Amount of ETH to send to
*/
function _safeTransferETH(
address _to,
uint256 _value
)
internal
{
(bool success, ) = _to.call{value: _value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
/**
* @dev Prevent users from sending ETH directly to this contract without calling deposit
*/
receive()
external
payable
{
revert("Deposits must be initiated via deposit() or depositTo()");
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_L2DepositedERC20 } from "../../../iOVM/bridge/tokens/iOVM_L2DepositedERC20.sol";
import { iOVM_L1ERC20Gateway } from "../../../iOVM/bridge/tokens/iOVM_L1ERC20Gateway.sol";
/* Contract Imports */
import { UniswapV2ERC20 } from "../../../libraries/standards/UniswapV2ERC20.sol";
/* Library Imports */
import { OVM_CrossDomainEnabled } from "../../../libraries/bridge/OVM_CrossDomainEnabled.sol";
/**
* @title OVM_L2DepositedERC20
* @dev The L2 Deposited ERC20 is an ERC20 implementation which represents L1 assets deposited into L2.
* This contract mints new tokens when it hears about deposits into the L1 ERC20 gateway.
* This contract also burns the tokens intended for withdrawal, informing the L1 gateway to release L1 funds.
*
* Compiler used: optimistic-solc
* Runtime target: OVM
*/
contract OVM_L2DepositedERC20 is iOVM_L2DepositedERC20, UniswapV2ERC20, OVM_CrossDomainEnabled {
/*******************
* Contract Events *
*******************/
event Initialized(iOVM_L1ERC20Gateway _l1ERC20Gateway);
/********************************
* External Contract References *
********************************/
iOVM_L1ERC20Gateway l1ERC20Gateway;
/********************************
* Constructor & Initialization *
********************************/
/**
* @param _l2CrossDomainMessenger L1 Messenger address being used for cross-chain communications.
* @param _decimals L2 ERC20 decimals
* @param _name L2 ERC20 name
* @param _symbol L2 ERC20 symbol
*/
constructor(
address _l2CrossDomainMessenger,
uint8 _decimals,
string memory _name,
string memory _symbol
)
public
OVM_CrossDomainEnabled(_l2CrossDomainMessenger)
UniswapV2ERC20(_decimals, _name, _symbol)
{}
/**
* @dev Initialize this gateway with the L1 gateway address
* The assumed flow is that this contract is deployed on L2, then the L1
* gateway is dpeloyed, and its address passed here to init.
*
* @param _l1ERC20Gateway Address of the corresponding L1 gateway deployed to the main chain
*/
function init(
iOVM_L1ERC20Gateway _l1ERC20Gateway
)
public
{
require(address(l1ERC20Gateway) == address(0), "Contract has already been initialized");
l1ERC20Gateway = _l1ERC20Gateway;
emit Initialized(l1ERC20Gateway);
}
/**********************
* Function Modifiers *
**********************/
modifier onlyInitialized() {
require(address(l1ERC20Gateway) != address(0), "Contract has not yet been initialized");
_;
}
/***************
* Withdrawing *
***************/
/**
* @dev initiate a withdraw of some ERC20 to the caller's account on L1
* @param _amount Amount of the ERC20 to withdraw
*/
function withdraw(
uint _amount
)
external
override
onlyInitialized()
{
_initiateWithdrawal(msg.sender, _amount);
}
/**
* @dev initiate a withdraw of some ERC20 to a recipient's account on L1
* @param _to L1 adress to credit the withdrawal to
* @param _amount Amount of the ERC20 to withdraw
*/
function withdrawTo(address _to, uint _amount) external override onlyInitialized() {
_initiateWithdrawal(_to, _amount);
}
/**
* @dev Performs the logic for deposits by storing the ERC20 and informing the L2 ERC20 Gateway of the deposit.
*
* @param _to Account to give the withdrawal to on L1
* @param _amount Amount of the ERC20 to withdraw
*/
function _initiateWithdrawal(address _to, uint _amount) internal {
// burn L2 funds so they can't be used more on L2
_burn(msg.sender, _amount);
// Construct calldata for l1ERC20Gateway.finalizeWithdrawal(_to, _amount)
bytes memory data = abi.encodeWithSelector(
iOVM_L1ERC20Gateway.finalizeWithdrawal.selector,
_to,
_amount
);
// Send message up to L1 gateway
sendCrossDomainMessage(
address(l1ERC20Gateway),
data,
DEFAULT_FINALIZE_WITHDRAWAL_L1_GAS
);
emit WithdrawalInitiated(msg.sender, _to, _amount);
}
/************************************
* Cross-chain Function: Depositing *
************************************/
/**
* @dev Complete a deposit from L1 to L2, and credits funds to the recipient's balance of this
* L2 ERC20 token.
* This call will fail if it did not originate from a corresponding deposit in OVM_L1ERC20Gateway.
*
* @param _to Address to receive the withdrawal at
* @param _amount Amount of the ERC20 to withdraw
*/
function finalizeDeposit(address _to, uint _amount) external override onlyInitialized()
onlyFromCrossDomainAccount(address(l1ERC20Gateway))
{
_mint(_to, _amount);
emit DepositFinalized(_to, _amount);
}
}
\ No newline at end of file
...@@ -5,7 +5,10 @@ pragma solidity >0.5.0 <0.8.0; ...@@ -5,7 +5,10 @@ pragma solidity >0.5.0 <0.8.0;
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol"; import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
/* Interface Imports */ /* Interface Imports */
import { iOVM_ERC20 } from "../../iOVM/precompiles/iOVM_ERC20.sol"; import { iOVM_L1ERC20Gateway } from "../../iOVM/bridge/tokens/iOVM_L1ERC20Gateway.sol";
/* Contract Imports */
import { OVM_L2DepositedERC20 } from "../bridge/tokens/OVM_L2DepositedERC20.sol";
/** /**
* @title OVM_ETH * @title OVM_ETH
...@@ -15,96 +18,19 @@ import { iOVM_ERC20 } from "../../iOVM/precompiles/iOVM_ERC20.sol"; ...@@ -15,96 +18,19 @@ import { iOVM_ERC20 } from "../../iOVM/precompiles/iOVM_ERC20.sol";
* Compiler used: optimistic-solc * Compiler used: optimistic-solc
* Runtime target: OVM * Runtime target: OVM
*/ */
contract OVM_ETH is iOVM_ERC20, Lib_AddressResolver { contract OVM_ETH is OVM_L2DepositedERC20 {
uint256 constant private MAX_UINT256 = 2**256 - 1;
mapping (address => uint256) public balances;
mapping (address => mapping (address => uint256)) public allowed;
/*
NOTE:
The following variables are OPTIONAL vanities. One does not have to include them.
They allow one to customise the token contract & in no way influences the core functionality.
Some wallets/interfaces might not even bother to look at this information.
*/
string public name; //fancy name: eg OVM Coin
uint8 public decimals; //How many decimals to show.
string public symbol; //An identifier: eg OVM
uint256 public override totalSupply;
constructor( constructor(
address _libAddressManager, address _l2CrossDomainMessenger,
uint256 _initialAmount, address _l1ETHGateway
string memory _tokenName, )
uint8 _decimalUnits, OVM_L2DepositedERC20(
string memory _tokenSymbol _l2CrossDomainMessenger,
) 18, // WETH decimals
public "ovmWETH",
Lib_AddressResolver(_libAddressManager) "oWETH"
)
public
{ {
balances[msg.sender] = _initialAmount; // Give the creator all initial tokens init(iOVM_L1ERC20Gateway(_l1ETHGateway));
totalSupply = _initialAmount; // Update total supply
name = _tokenName; // Set the name for display purposes
decimals = _decimalUnits; // Amount of decimals for display purposes
symbol = _tokenSymbol; // Set the symbol for display purposes
}
modifier onlyOVMETHBridge() {
address bridgeOnL2 = resolve("OVM_L2ETHBridge");
require(bridgeOnL2 != address(0), "OVM_L2ETHBridge is not yet initialized.");
require(msg.sender == bridgeOnL2, "Only callable by OVM ETH Deposit/Withdrawal contract");
_;
}
function transfer(address _to, uint256 _value) external override returns (bool success) {
require(balances[msg.sender] >= _value);
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) external override returns (bool success) {
uint256 allowance = allowed[_from][msg.sender];
require(balances[_from] >= _value && allowance >= _value);
balances[_to] += _value;
balances[_from] -= _value;
if (allowance < MAX_UINT256) {
allowed[_from][msg.sender] -= _value;
}
emit Transfer(_from, _to, _value);
return true;
}
function balanceOf(address _owner) external view override returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) external override returns (bool success) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) external view override returns (uint256 remaining) {
return allowed[_owner][_spender];
}
function mint(address _account, uint256 _amount) external onlyOVMETHBridge returns (bool success) {
uint256 newTotalSupply = totalSupply + _amount;
require(newTotalSupply >= totalSupply, "SafeMath: addition overflow");
totalSupply = newTotalSupply;
balances[_account] += _amount;
emit Mint(_account, _amount);
return true;
}
function burn(address _account, uint256 _amount) external onlyOVMETHBridge returns (bool success) {
require(balances[_account] >= _amount, "Unable to burn due to insufficient balance");
balances[_account] -= _amount;
totalSupply -= _amount;
emit Burn(_account, _amount);
return true;
} }
} }
...@@ -3,7 +3,7 @@ pragma solidity >0.5.0 <0.8.0; ...@@ -3,7 +3,7 @@ pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
/* Library Imports */ /* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol"; import { Lib_OVMCodec } from "../../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */ /* Interface Imports */
import { iAbs_BaseCrossDomainMessenger } from "./iAbs_BaseCrossDomainMessenger.sol"; import { iAbs_BaseCrossDomainMessenger } from "./iAbs_BaseCrossDomainMessenger.sol";
......
...@@ -3,7 +3,7 @@ pragma solidity >0.5.0 <0.8.0; ...@@ -3,7 +3,7 @@ pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
/* Interface Imports */ /* Interface Imports */
import { iOVM_L1CrossDomainMessenger } from "../../iOVM/bridge/iOVM_L1CrossDomainMessenger.sol"; import { iOVM_L1CrossDomainMessenger } from "../../../iOVM/bridge/messenging/iOVM_L1CrossDomainMessenger.sol";
interface iOVM_L1MultiMessageRelayer { interface iOVM_L1MultiMessageRelayer {
struct L2ToL1Message { struct L2ToL1Message {
......
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0;
pragma experimental ABIEncoderV2;
/**
* @title iOVM_L1ERC20Gateway
*/
interface iOVM_L1ERC20Gateway {
/**********
* Events *
**********/
event DepositInitiated(
address indexed _from,
address _to,
uint256 _amount
);
event WithdrawalFinalized(
address indexed _to,
uint256 _amount
);
/********************
* Public Functions *
********************/
function deposit(
uint _amount
)
external;
function depositTo(
address _to,
uint _amount
)
external;
/*************************
* Cross-chain Functions *
*************************/
function finalizeWithdrawal(
address _to,
uint _amount
)
external;
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0;
pragma experimental ABIEncoderV2;
/**
* @title iOVM_L1ETHGateway
*/
interface iOVM_L1ETHGateway {
/**********
* Events *
**********/
event DepositInitiated(
address indexed _from,
address _to,
uint256 _amount
);
event WithdrawalFinalized(
address indexed _to,
uint256 _amount
);
/********************
* Public Functions *
********************/
function deposit()
external
payable;
function depositTo(
address _to
)
external
payable;
/*************************
* Cross-chain Functions *
*************************/
function finalizeWithdrawal(
address _to,
uint _amount
)
external;
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { IUniswapV2ERC20 } from "../../../libraries/standards/IUniswapV2ERC20.sol";
/**
* @title iOVM_L2DepositedERC20
*/
interface iOVM_L2DepositedERC20 is IUniswapV2ERC20 {
/**********
* Events *
**********/
event WithdrawalInitiated(
address indexed _from,
address _to,
uint256 _amount
);
event DepositFinalized(
address indexed _to,
uint256 _amount
);
/********************
* Public Functions *
********************/
function withdraw(
uint _amount
)
external;
function withdrawTo(
address _to,
uint _amount
)
external;
/*************************
* Cross-chain Functions *
*************************/
function finalizeDeposit(
address _to,
uint _amount
)
external;
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/* Interface Imports */
import { iAbs_BaseCrossDomainMessenger } from "../../iOVM/bridge/messenging/iAbs_BaseCrossDomainMessenger.sol";
/**
* @title OVM_CrossDomainEnabled
* @dev Helper contract for contracts performing cross-domain communications
*
* Compiler used: defined by inheriting contract
* Runtime target: defined by inheriting contract
*/
contract OVM_CrossDomainEnabled {
// Messenger contract used to send and recieve messages from the other domain.
address public messenger;
uint32 public constant DEFAULT_FINALIZE_DEPOSIT_L2_GAS = 700000;
uint32 public constant DEFAULT_FINALIZE_WITHDRAWAL_L1_GAS = 100000;
/***************
* Constructor *
***************/
constructor(
address _messenger
) {
messenger = _messenger;
}
/**********************
* Function Modifiers *
**********************/
/**
* @notice Enforces that the modified function is only callable by a specific cross-domain account.
* @param _sourceDomainAccount The only account on the originating domain which is authenticated to call this function.
*/
modifier onlyFromCrossDomainAccount(
address _sourceDomainAccount
) {
require(
msg.sender == address(getCrossDomainMessenger()),
"OVM_XCHAIN: messenger contract unauthenticated"
);
require(
getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,
"OVM_XCHAIN: wrong sender of cross-domain message"
);
_;
}
/**********************
* Internal Functions *
**********************/
/**
* @notice Gets the messenger, usually from storage. This function is exposed in case a child contract needs to override.
* @return The address of the cross-domain messenger contract which should be used.
*/
function getCrossDomainMessenger()
internal
virtual
returns(
iAbs_BaseCrossDomainMessenger
)
{
return iAbs_BaseCrossDomainMessenger(messenger);
}
/**
* @notice Sends a message to an account on another domain
* @param _crossDomainTarget The intended recipient on the destination domain
* @param _data The data to send to the target (usually calldata to a function with `onlyFromCrossDomainAccount()`)
* @param _gasLimit The gasLimit for the receipt of the message on the target domain.
*/
function sendCrossDomainMessage(
address _crossDomainTarget,
bytes memory _data,
uint32 _gasLimit
) internal {
getCrossDomainMessenger().sendMessage(_crossDomainTarget, _data, _gasLimit);
}
}
pragma solidity >=0.5.16 <0.8.0;
interface IUniswapV2ERC20 {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
}
pragma solidity >=0.5.16 <0.8.0;
// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
library UniSafeMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x, 'ds-math-add-overflow');
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x, 'ds-math-sub-underflow');
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
}
}
pragma solidity >=0.5.16 <0.8.0;
import './IUniswapV2ERC20.sol';
import './UniSafeMath.sol';
contract UniswapV2ERC20 is IUniswapV2ERC20 {
using UniSafeMath for uint;
string public override name;
string public override symbol;
uint8 public override immutable decimals;
uint public override totalSupply;
mapping(address => uint) public override balanceOf;
mapping(address => mapping(address => uint)) public override allowance;
bytes32 public override DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant override PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public override nonces;
constructor(
uint8 _decimals,
string memory _name,
string memory _symbol
) public {
decimals = _decimals;
name = _name;
symbol = _symbol;
uint chainId;
assembly {
chainId := chainid()
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}
function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}
function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(from, address(0), value);
}
function _approve(address owner, address spender, uint value) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}
function approve(address spender, uint value) external override returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transfer(address to, uint value) external override returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint value) external override returns (bool) {
if (allowance[from][msg.sender] != uint(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}
_transfer(from, to, value);
return true;
}
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external override {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
}
...@@ -3,7 +3,7 @@ pragma solidity >0.5.0 <0.8.0; ...@@ -3,7 +3,7 @@ pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
/* Contract Imports */ /* Contract Imports */
import { iAbs_BaseCrossDomainMessenger } from "../../iOVM/bridge/iAbs_BaseCrossDomainMessenger.sol"; import { iAbs_BaseCrossDomainMessenger } from "../../iOVM/bridge/messenging/iAbs_BaseCrossDomainMessenger.sol";
/** /**
* @title mockOVM_CrossDomainMessenger * @title mockOVM_CrossDomainMessenger
......
...@@ -30,9 +30,6 @@ export interface RollupDeployConfig { ...@@ -30,9 +30,6 @@ export interface RollupDeployConfig {
l1CrossDomainMessengerConfig: { l1CrossDomainMessengerConfig: {
relayerAddress?: string | Signer relayerAddress?: string | Signer
} }
ethConfig: {
initialAmount: number
}
whitelistConfig: { whitelistConfig: {
owner: string | Signer owner: string | Signer
allowArbitraryContractDeployment: boolean allowArbitraryContractDeployment: boolean
...@@ -110,6 +107,13 @@ export const makeContractDeployConfig = async ( ...@@ -110,6 +107,13 @@ export const makeContractDeployConfig = async (
) )
}, },
}, },
OVM_L1ETHGateway: {
factory: getContractFactory('OVM_L1ETHGateway'),
params: [
AddressManager.address,
'0x4200000000000000000000000000000000000006',
],
},
OVM_L1MultiMessageRelayer: { OVM_L1MultiMessageRelayer: {
factory: getContractFactory('OVM_L1MultiMessageRelayer'), factory: getContractFactory('OVM_L1MultiMessageRelayer'),
params: [AddressManager.address], params: [AddressManager.address],
...@@ -215,11 +219,8 @@ export const makeContractDeployConfig = async ( ...@@ -215,11 +219,8 @@ export const makeContractDeployConfig = async (
OVM_ETH: { OVM_ETH: {
factory: getContractFactory('OVM_ETH'), factory: getContractFactory('OVM_ETH'),
params: [ params: [
AddressManager.address, '0x4200000000000000000000000000000000000007',
config.ethConfig.initialAmount, '0x0000000000000000000000000000000000000000', // will be overridden by geth when state dump is ingested. Storage key: 0x0000000000000000000000000000000000000000000000000000000000000008
'Ether',
18,
'ETH',
], ],
}, },
'OVM_ChainStorageContainer:CTC:batches': { 'OVM_ChainStorageContainer:CTC:batches': {
......
...@@ -137,9 +137,6 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => { ...@@ -137,9 +137,6 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
allowArbitraryContractDeployment: true, allowArbitraryContractDeployment: true,
}, },
l1CrossDomainMessengerConfig: {}, l1CrossDomainMessengerConfig: {},
ethConfig: {
initialAmount: 0,
},
dependencies: [ dependencies: [
'Lib_AddressManager', 'Lib_AddressManager',
'OVM_DeployerWhitelist', 'OVM_DeployerWhitelist',
......
import { expect } from '../../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import {
smockit,
MockContract,
smoddit,
ModifiableContract,
} from '@eth-optimism/smock'
/* Internal Imports */
import { NON_ZERO_ADDRESS, ZERO_ADDRESS } from '../../../../helpers'
const INITIAL_TOTAL_L1_SUPPLY = 3000
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
const ERR_INVALID_X_DOMAIN_MSG_SENDER =
'OVM_XCHAIN: wrong sender of cross-domain message'
describe('OVM_L1ERC20Gateway', () => {
// init signers
let alice: Signer
let bob: Signer
// we can just make up this string since it's on the "other" Layer
let Mock__OVM_L2DepositedERC20: MockContract
let Factory__L1ERC20: ContractFactory
let L1ERC20: Contract
const initialSupply = 1_000
before(async () => {
;[alice, bob] = await ethers.getSigners()
Mock__OVM_L2DepositedERC20 = await smockit(
await ethers.getContractFactory('OVM_L2DepositedERC20')
)
// deploy an ERC20 contract on L1
Factory__L1ERC20 = await smoddit('UniswapV2ERC20')
L1ERC20 = await Factory__L1ERC20.deploy(18, 'L1ERC20', 'ERC')
const aliceAddress = await alice.getAddress()
L1ERC20.smodify.put({
totalSupply: INITIAL_TOTAL_L1_SUPPLY,
balanceOf: {
[aliceAddress]: INITIAL_TOTAL_L1_SUPPLY,
},
})
})
let OVM_L1ERC20Gateway: Contract
let Mock__OVM_L1CrossDomainMessenger: MockContract
let finalizeDepositGasLimit: number
beforeEach(async () => {
// Create a special signer which will enable us to send messages from the L1Messenger contract
let l1MessengerImpersonator: Signer
;[l1MessengerImpersonator, alice, bob] = await ethers.getSigners()
// Get a new mock L1 messenger
Mock__OVM_L1CrossDomainMessenger = await smockit(
await ethers.getContractFactory('OVM_L1CrossDomainMessenger'),
{ address: await l1MessengerImpersonator.getAddress() } // This allows us to use an ethers override {from: Mock__OVM_L2CrossDomainMessenger.address} to mock calls
)
// Deploy the contract under test
OVM_L1ERC20Gateway = await (
await ethers.getContractFactory('OVM_L1ERC20Gateway')
).deploy(
L1ERC20.address,
Mock__OVM_L2DepositedERC20.address,
Mock__OVM_L1CrossDomainMessenger.address
)
finalizeDepositGasLimit = await OVM_L1ERC20Gateway.DEFAULT_FINALIZE_DEPOSIT_L2_GAS()
})
describe('finalizeWithdrawal', () => {
it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L1 account', async () => {
// Deploy new gateway, initialize with random messenger
OVM_L1ERC20Gateway = await (
await ethers.getContractFactory('OVM_L1ERC20Gateway')
).deploy(
L1ERC20.address,
Mock__OVM_L2DepositedERC20.address,
NON_ZERO_ADDRESS
)
await expect(
OVM_L1ERC20Gateway.finalizeWithdrawal(ZERO_ADDRESS, 1)
).to.be.revertedWith(ERR_INVALID_MESSENGER)
})
it('onlyFromCrossDomainAccount: should revert on calls from the right crossDomainMessenger, but wrong xDomainMessageSender (ie. not the L2ERC20Gateway)', async () => {
Mock__OVM_L1CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with(
() => NON_ZERO_ADDRESS
)
await expect(
OVM_L1ERC20Gateway.finalizeWithdrawal(ZERO_ADDRESS, 1, {
from: Mock__OVM_L1CrossDomainMessenger.address,
})
).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MSG_SENDER)
})
it('should credit funds to the withdrawer and not use too much gas', async () => {
// make sure no balance at start of test
await expect(await L1ERC20.balanceOf(NON_ZERO_ADDRESS)).to.be.equal(0)
const withdrawalAmount = 100
Mock__OVM_L1CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with(
() => Mock__OVM_L2DepositedERC20.address
)
await L1ERC20.transfer(OVM_L1ERC20Gateway.address, withdrawalAmount)
const res = await OVM_L1ERC20Gateway.finalizeWithdrawal(
NON_ZERO_ADDRESS,
withdrawalAmount,
{ from: Mock__OVM_L1CrossDomainMessenger.address }
)
await expect(await L1ERC20.balanceOf(NON_ZERO_ADDRESS)).to.be.equal(
withdrawalAmount
)
const gasUsed = (
await OVM_L1ERC20Gateway.provider.getTransactionReceipt(res.hash)
).gasUsed
await expect(
gasUsed.gt(
((await OVM_L1ERC20Gateway.DEFAULT_FINALIZE_WITHDRAWAL_L1_GAS()) *
11) /
10
)
)
})
it.skip('finalizeWithdrawalAndCall(): should should credit funds to the withdrawer, and forward from and data', async () => {
// TODO: implement this functionality in a future update
expect.fail()
})
})
describe('deposits', () => {
const INITIAL_DEPOSITER_BALANCE = 100_000
let depositer: string
const depositAmount = 1_000
let L1ERC20: Contract
beforeEach(async () => {
// Deploy the L1 ERC20 token, Alice will receive the full initialSupply
L1ERC20 = await Factory__L1ERC20.deploy(18, 'L1ERC20', 'ERC')
// get a new mock L1 messenger
Mock__OVM_L1CrossDomainMessenger = await smockit(
await ethers.getContractFactory('OVM_L1CrossDomainMessenger')
)
// Deploy the contract under test:
OVM_L1ERC20Gateway = await (
await ethers.getContractFactory('OVM_L1ERC20Gateway')
).deploy(
L1ERC20.address,
Mock__OVM_L2DepositedERC20.address,
Mock__OVM_L1CrossDomainMessenger.address
)
// the Signer sets approve for the L1 Gateway
await L1ERC20.approve(OVM_L1ERC20Gateway.address, depositAmount)
depositer = await L1ERC20.signer.getAddress()
await L1ERC20.smodify.put({
balanceOf: {
[depositer]: INITIAL_DEPOSITER_BALANCE,
},
})
})
it('deposit() escrows the deposit amount and sends the correct deposit message', async () => {
// alice calls deposit on the gateway and the L1 gateway calls transferFrom on the token
await OVM_L1ERC20Gateway.deposit(depositAmount)
const depositCallToMessenger =
Mock__OVM_L1CrossDomainMessenger.smocked.sendMessage.calls[0]
const depositerBalance = await L1ERC20.balanceOf(depositer)
expect(depositerBalance).to.equal(
INITIAL_DEPOSITER_BALANCE - depositAmount
)
// gateway's balance is increased
const gatewayBalance = await L1ERC20.balanceOf(OVM_L1ERC20Gateway.address)
expect(gatewayBalance).to.equal(depositAmount)
// Check the correct cross-chain call was sent:
// Message should be sent to the L2ERC20Gateway on L2
expect(depositCallToMessenger._target).to.equal(
Mock__OVM_L2DepositedERC20.address
)
// Message data should be a call telling the L2ERC20Gateway to finalize the deposit
// the L1 gateway sends the correct message to the L1 messenger
expect(depositCallToMessenger._message).to.equal(
await Mock__OVM_L2DepositedERC20.interface.encodeFunctionData(
'finalizeDeposit',
[depositer, depositAmount]
)
)
expect(depositCallToMessenger._gasLimit).to.equal(finalizeDepositGasLimit)
})
it('depositTo() escrows the deposit amount and sends the correct deposit message', async () => {
// depositor calls deposit on the gateway and the L1 gateway calls transferFrom on the token
const bobsAddress = await bob.getAddress()
await OVM_L1ERC20Gateway.depositTo(bobsAddress, depositAmount)
const depositCallToMessenger =
Mock__OVM_L1CrossDomainMessenger.smocked.sendMessage.calls[0]
const depositerBalance = await L1ERC20.balanceOf(depositer)
expect(depositerBalance).to.equal(
INITIAL_DEPOSITER_BALANCE - depositAmount
)
// gateway's balance is increased
const gatewayBalance = await L1ERC20.balanceOf(OVM_L1ERC20Gateway.address)
expect(gatewayBalance).to.equal(depositAmount)
// Check the correct cross-chain call was sent:
// Message should be sent to the L2ERC20Gateway on L2
expect(depositCallToMessenger._target).to.equal(
Mock__OVM_L2DepositedERC20.address
)
// Message data should be a call telling the L2ERC20Gateway to finalize the deposit
// the L1 gateway sends the correct message to the L1 messenger
expect(depositCallToMessenger._message).to.equal(
await Mock__OVM_L2DepositedERC20.interface.encodeFunctionData(
'finalizeDeposit',
[bobsAddress, depositAmount]
)
)
expect(depositCallToMessenger._gasLimit).to.equal(finalizeDepositGasLimit)
})
})
})
import { expect } from '../../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, BigNumber, providers } from 'ethers'
import {
smockit,
MockContract,
smoddit,
ModifiableContract,
} from '@eth-optimism/smock'
/* Internal Imports */
import {
NON_ZERO_ADDRESS,
ZERO_ADDRESS,
makeAddressManager,
} from '../../../../helpers'
const L1_ETH_GATEWAY_NAME = 'Proxy__OVM_L1CrossDomainMessenger'
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
const ERR_INVALID_X_DOMAIN_MSG_SENDER =
'OVM_XCHAIN: wrong sender of cross-domain message'
describe('OVM_L1ETHGateway', () => {
// init signers
let l1MessengerImpersonator: Signer
let alice: Signer
let bob: Signer
let AddressManager: Contract
before(async () => {
AddressManager = await makeAddressManager()
})
// we can just make up this string since it's on the "other" Layer
let Mock__OVM_L2DepositedERC20: MockContract
before(async () => {
;[l1MessengerImpersonator, alice, bob] = await ethers.getSigners()
Mock__OVM_L2DepositedERC20 = await smockit(
await ethers.getContractFactory('OVM_L2DepositedERC20')
)
})
let OVM_L1ETHGateway: Contract
let Mock__OVM_L1CrossDomainMessenger: MockContract
let finalizeDepositGasLimit: number
beforeEach(async () => {
// Get a new mock L1 messenger
Mock__OVM_L1CrossDomainMessenger = await smockit(
await ethers.getContractFactory('OVM_L1CrossDomainMessenger'),
{ address: await l1MessengerImpersonator.getAddress() } // This allows us to use an ethers override {from: Mock__OVM_L2CrossDomainMessenger.address} to mock calls
)
// Deploy the contract under test
OVM_L1ETHGateway = await (
await ethers.getContractFactory('OVM_L1ETHGateway')
).deploy(AddressManager.address, Mock__OVM_L2DepositedERC20.address)
finalizeDepositGasLimit = await OVM_L1ETHGateway.DEFAULT_FINALIZE_DEPOSIT_L2_GAS()
})
describe('finalizeWithdrawal', () => {
it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L1 account', async () => {
// Deploy new gateway, initialize with random messenger
await expect(
OVM_L1ETHGateway.connect(alice).finalizeWithdrawal(ZERO_ADDRESS, 1)
).to.be.revertedWith(ERR_INVALID_MESSENGER)
})
it('onlyFromCrossDomainAccount: should revert on calls from the right crossDomainMessenger, but wrong xDomainMessageSender (ie. not the L2ETHGateway)', async () => {
await AddressManager.setAddress(
L1_ETH_GATEWAY_NAME,
Mock__OVM_L1CrossDomainMessenger.address
)
OVM_L1ETHGateway = await (
await ethers.getContractFactory('OVM_L1ETHGateway')
).deploy(AddressManager.address, Mock__OVM_L2DepositedERC20.address)
Mock__OVM_L1CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with(
NON_ZERO_ADDRESS
)
await expect(
OVM_L1ETHGateway.finalizeWithdrawal(ZERO_ADDRESS, 1)
).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MSG_SENDER)
})
it('should credit funds to the withdrawer and not use too much gas', async () => {
// make sure no balance at start of test
await expect(
await ethers.provider.getBalance(NON_ZERO_ADDRESS)
).to.be.equal(0)
const withdrawalAmount = 100
Mock__OVM_L1CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with(
() => Mock__OVM_L2DepositedERC20.address
)
// thanks Alice
await OVM_L1ETHGateway.connect(alice).deposit({
value: ethers.utils.parseEther('1.0'),
gasPrice: 0,
})
const res = await OVM_L1ETHGateway.finalizeWithdrawal(
NON_ZERO_ADDRESS,
withdrawalAmount,
{ from: Mock__OVM_L1CrossDomainMessenger.address }
)
await expect(
await ethers.provider.getBalance(NON_ZERO_ADDRESS)
).to.be.equal(withdrawalAmount)
const gasUsed = (
await OVM_L1ETHGateway.provider.getTransactionReceipt(res.hash)
).gasUsed
await expect(
gasUsed.gt(
((await OVM_L1ETHGateway.DEFAULT_FINALIZE_WITHDRAWAL_L1_GAS()) * 11) /
10
)
)
})
it.skip('finalizeWithdrawalAndCall(): should should credit funds to the withdrawer, and forward from and data', async () => {
// TODO: implement this functionality in a future update
expect.fail()
})
})
describe('deposits', () => {
const depositAmount = 1_000
beforeEach(async () => {
// Deploy the L1 ETH token, Alice will receive the full initialSupply
// get a new mock L1 messenger and set in AM
Mock__OVM_L1CrossDomainMessenger = await smockit(
await ethers.getContractFactory('OVM_L1CrossDomainMessenger')
)
await AddressManager.setAddress(
L1_ETH_GATEWAY_NAME,
Mock__OVM_L1CrossDomainMessenger.address
)
// Deploy the contract under test:
OVM_L1ETHGateway = await (
await ethers.getContractFactory('OVM_L1ETHGateway')
).deploy(AddressManager.address, Mock__OVM_L2DepositedERC20.address)
})
it('deposit() escrows the deposit amount and sends the correct deposit message', async () => {
const depositer = await alice.getAddress()
const initialBalance = await ethers.provider.getBalance(depositer)
// alice calls deposit on the gateway and the L1 gateway calls transferFrom on the token
await OVM_L1ETHGateway.connect(alice).deposit({
value: depositAmount,
gasPrice: 0,
})
const depositCallToMessenger =
Mock__OVM_L1CrossDomainMessenger.smocked.sendMessage.calls[0]
const depositerBalance = await ethers.provider.getBalance(depositer)
expect(depositerBalance).to.equal(initialBalance.sub(depositAmount))
// gateway's balance is increased
const gatewayBalance = await ethers.provider.getBalance(
OVM_L1ETHGateway.address
)
expect(gatewayBalance).to.equal(depositAmount)
// Check the correct cross-chain call was sent:
// Message should be sent to the L2ETHGateway on L2
expect(depositCallToMessenger._target).to.equal(
Mock__OVM_L2DepositedERC20.address
)
// Message data should be a call telling the L2ETHGateway to finalize the deposit
// the L1 gateway sends the correct message to the L1 messenger
expect(depositCallToMessenger._message).to.equal(
await Mock__OVM_L2DepositedERC20.interface.encodeFunctionData(
'finalizeDeposit',
[depositer, depositAmount]
)
)
expect(depositCallToMessenger._gasLimit).to.equal(finalizeDepositGasLimit)
})
it('depositTo() escrows the deposit amount and sends the correct deposit message', async () => {
// depositor calls deposit on the gateway and the L1 gateway calls transferFrom on the token
const bobsAddress = await bob.getAddress()
const aliceAddress = await alice.getAddress()
const initialBalance = await ethers.provider.getBalance(aliceAddress)
await OVM_L1ETHGateway.connect(alice).depositTo(bobsAddress, {
value: depositAmount,
gasPrice: 0,
})
const depositCallToMessenger =
Mock__OVM_L1CrossDomainMessenger.smocked.sendMessage.calls[0]
const depositerBalance = await ethers.provider.getBalance(aliceAddress)
expect(depositerBalance).to.equal(initialBalance.sub(depositAmount))
// gateway's balance is increased
const gatewayBalance = await ethers.provider.getBalance(
OVM_L1ETHGateway.address
)
expect(gatewayBalance).to.equal(depositAmount)
// Check the correct cross-chain call was sent:
// Message should be sent to the L2ETHGateway on L2
expect(depositCallToMessenger._target).to.equal(
Mock__OVM_L2DepositedERC20.address
)
// Message data should be a call telling the L2ETHGateway to finalize the deposit
// the L1 gateway sends the correct message to the L1 messenger
expect(depositCallToMessenger._message).to.equal(
await Mock__OVM_L2DepositedERC20.interface.encodeFunctionData(
'finalizeDeposit',
[bobsAddress, depositAmount]
)
)
expect(depositCallToMessenger._gasLimit).to.equal(finalizeDepositGasLimit)
})
})
})
import { expect } from '../../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import {
smockit,
MockContract,
smoddit,
ModifiableContract,
} from '@eth-optimism/smock'
/* Internal Imports */
import { NON_ZERO_ADDRESS, ZERO_ADDRESS } from '../../../../helpers'
const decimals = 1
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
const ERR_INVALID_X_DOMAIN_MSG_SENDER =
'OVM_XCHAIN: wrong sender of cross-domain message'
const MOCK_L1GATEWAY_ADDRESS: string =
'0x1234123412341234123412341234123412341234'
describe('OVM_L2DepositedERC20', () => {
let alice: Signer
let bob: Signer
let Factory__OVM_L1ERC20Gateway: ContractFactory
before(async () => {
;[alice, bob] = await ethers.getSigners()
Factory__OVM_L1ERC20Gateway = await ethers.getContractFactory(
'OVM_L1ERC20Gateway'
)
})
let OVM_L2DepositedERC20: Contract
let Mock__OVM_L2CrossDomainMessenger: MockContract
let finalizeWithdrawalGasLimit: number
beforeEach(async () => {
// Create a special signer which will enable us to send messages from the L2Messenger contract
let l2MessengerImpersonator: Signer
;[l2MessengerImpersonator] = await ethers.getSigners()
// Get a new mock L2 messenger
Mock__OVM_L2CrossDomainMessenger = await smockit(
await ethers.getContractFactory('OVM_L2CrossDomainMessenger'),
// This allows us to use an ethers override {from: Mock__OVM_L2CrossDomainMessenger.address} to mock calls
{ address: await l2MessengerImpersonator.getAddress() }
)
// Deploy the contract under test
OVM_L2DepositedERC20 = await (
await ethers.getContractFactory('OVM_L2DepositedERC20')
).deploy(
Mock__OVM_L2CrossDomainMessenger.address,
decimals,
'ovmWETH',
'oWETH'
)
// initialize the L2 Gateway with the L1G ateway addrss
await OVM_L2DepositedERC20.init(MOCK_L1GATEWAY_ADDRESS)
finalizeWithdrawalGasLimit = await OVM_L2DepositedERC20.DEFAULT_FINALIZE_WITHDRAWAL_L1_GAS()
})
// test the transfer flow of moving a token from L2 to L1
describe('finalizeDeposit', () => {
it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L2 account', async () => {
// Deploy new gateway, initialize with random messenger
OVM_L2DepositedERC20 = await (
await ethers.getContractFactory('OVM_L2DepositedERC20')
).deploy(NON_ZERO_ADDRESS, decimals, 'ovmWETH', 'oWETH')
await OVM_L2DepositedERC20.init(NON_ZERO_ADDRESS)
await expect(
OVM_L2DepositedERC20.finalizeDeposit(ZERO_ADDRESS, 0)
).to.be.revertedWith(ERR_INVALID_MESSENGER)
})
it('onlyFromCrossDomainAccount: should revert on calls from the right crossDomainMessenger, but wrong xDomainMessageSender (ie. not the L1ERC20Gateway)', async () => {
Mock__OVM_L2CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with(
NON_ZERO_ADDRESS
)
await expect(
OVM_L2DepositedERC20.finalizeDeposit(ZERO_ADDRESS, 0, {
from: Mock__OVM_L2CrossDomainMessenger.address,
})
).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MSG_SENDER)
})
it('should credit funds to the depositor', async () => {
const depositAmount = 100
Mock__OVM_L2CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with(
() => MOCK_L1GATEWAY_ADDRESS
)
await OVM_L2DepositedERC20.finalizeDeposit(
await alice.getAddress(),
depositAmount,
{ from: Mock__OVM_L2CrossDomainMessenger.address }
)
const aliceBalance = await OVM_L2DepositedERC20.balanceOf(
await alice.getAddress()
)
aliceBalance.should.equal(depositAmount)
})
})
describe('withdrawals', () => {
const INITIAL_TOTAL_SUPPLY = 100_000
const ALICE_INITIAL_BALANCE = 50_000
const withdrawAmount = 1_000
let SmoddedL2Gateway: ModifiableContract
beforeEach(async () => {
// Deploy a smodded gateway so we can give some balances to withdraw
SmoddedL2Gateway = await (
await smoddit('OVM_L2DepositedERC20', alice)
).deploy(
Mock__OVM_L2CrossDomainMessenger.address,
decimals,
'ovmWETH',
'oWETH'
)
await SmoddedL2Gateway.init(MOCK_L1GATEWAY_ADDRESS)
// Populate the initial state with a total supply and some money in alice's balance
const aliceAddress = await alice.getAddress()
SmoddedL2Gateway.smodify.put({
totalSupply: INITIAL_TOTAL_SUPPLY,
balanceOf: {
[aliceAddress]: ALICE_INITIAL_BALANCE,
},
})
})
it('withdraw() burns and sends the correct withdrawal message', async () => {
await SmoddedL2Gateway.withdraw(withdrawAmount)
const withdrawalCallToMessenger =
Mock__OVM_L2CrossDomainMessenger.smocked.sendMessage.calls[0]
// Assert Alice's balance went down
const aliceBalance = await SmoddedL2Gateway.balanceOf(
await alice.getAddress()
)
expect(aliceBalance).to.deep.equal(
ethers.BigNumber.from(ALICE_INITIAL_BALANCE - withdrawAmount)
)
// Assert totalSupply went down
const newTotalSupply = await SmoddedL2Gateway.totalSupply()
expect(newTotalSupply).to.deep.equal(
ethers.BigNumber.from(INITIAL_TOTAL_SUPPLY - withdrawAmount)
)
// Assert the correct cross-chain call was sent:
// Message should be sent to the L1ERC20Gateway on L1
expect(withdrawalCallToMessenger._target).to.equal(MOCK_L1GATEWAY_ADDRESS)
// Message data should be a call telling the L1ERC20Gateway to finalize the withdrawal
expect(withdrawalCallToMessenger._message).to.equal(
await Factory__OVM_L1ERC20Gateway.interface.encodeFunctionData(
'finalizeWithdrawal',
[await alice.getAddress(), withdrawAmount]
)
)
// Hardcoded gaslimit should be correct
expect(withdrawalCallToMessenger._gasLimit).to.equal(
finalizeWithdrawalGasLimit
)
})
it('withdrawTo() burns and sends the correct withdrawal message', async () => {
await SmoddedL2Gateway.withdrawTo(await bob.getAddress(), withdrawAmount)
const withdrawalCallToMessenger =
Mock__OVM_L2CrossDomainMessenger.smocked.sendMessage.calls[0]
// Assert Alice's balance went down
const aliceBalance = await SmoddedL2Gateway.balanceOf(
await alice.getAddress()
)
expect(aliceBalance).to.deep.equal(
ethers.BigNumber.from(ALICE_INITIAL_BALANCE - withdrawAmount)
)
// Assert totalSupply went down
const newTotalSupply = await SmoddedL2Gateway.totalSupply()
expect(newTotalSupply).to.deep.equal(
ethers.BigNumber.from(INITIAL_TOTAL_SUPPLY - withdrawAmount)
)
// Assert the correct cross-chain call was sent.
// Message should be sent to the L1ERC20Gateway on L1
expect(withdrawalCallToMessenger._target).to.equal(MOCK_L1GATEWAY_ADDRESS)
// The message data should be a call telling the L1ERC20Gateway to finalize the withdrawal
expect(withdrawalCallToMessenger._message).to.equal(
await Factory__OVM_L1ERC20Gateway.interface.encodeFunctionData(
'finalizeWithdrawal',
[await bob.getAddress(), withdrawAmount]
)
)
// Hardcoded gaslimit should be correct
expect(withdrawalCallToMessenger._gasLimit).to.equal(
finalizeWithdrawalGasLimit
)
})
})
// low priority todos: see question in contract
describe.skip('Initialization logic', () => {
it('should not allow calls to onlyInitialized functions', async () => {
// TODO
})
it('should only allow initialization once and emits initialized event', async () => {
// TODO
})
})
})
import { expect } from '../../../setup' import { expect } from '../../../../setup'
/* External Imports */ /* External Imports */
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
...@@ -19,21 +19,10 @@ import { ...@@ -19,21 +19,10 @@ import {
toHexString, toHexString,
getNextBlockNumber, getNextBlockNumber,
remove0x, remove0x,
} from '../../../helpers' getXDomainCalldata,
import { getContractInterface } from '../../../../src' } from '../../../../helpers'
import { keccak256 } from 'ethers/lib/utils' import { keccak256 } from 'ethers/lib/utils'
const getXDomainCalldata = (
sender: string,
target: string,
message: string,
messageNonce: number
): string => {
return getContractInterface(
'OVM_L2CrossDomainMessenger'
).encodeFunctionData('relayMessage', [target, sender, message, messageNonce])
}
const deployProxyXDomainMessenger = async ( const deployProxyXDomainMessenger = async (
addressManager: Contract, addressManager: Contract,
l1XDomainMessenger: Contract l1XDomainMessenger: Contract
......
import { expect } from '../../../setup' import { expect } from '../../../../setup'
/* External Imports */ /* External Imports */
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
DUMMY_BATCH_HEADERS, DUMMY_BATCH_HEADERS,
DUMMY_BATCH_PROOFS, DUMMY_BATCH_PROOFS,
toHexString, toHexString,
} from '../../../helpers' } from '../../../../helpers'
import { sign } from 'crypto' import { sign } from 'crypto'
describe('OVM_L1MultiMessageRelayer', () => { describe('OVM_L1MultiMessageRelayer', () => {
......
import { expect } from '../../../setup' import { expect } from '../../../../setup'
/* External Imports */ /* External Imports */
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
...@@ -12,19 +12,8 @@ import { ...@@ -12,19 +12,8 @@ import {
NON_NULL_BYTES32, NON_NULL_BYTES32,
ZERO_ADDRESS, ZERO_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
} from '../../../helpers' getXDomainCalldata,
import { getContractInterface } from '../../../../src' } from '../../../../helpers'
const getXDomainCalldata = (
sender: string,
target: string,
message: string,
messageNonce: number
): string => {
return getContractInterface(
'OVM_L2CrossDomainMessenger'
).encodeFunctionData('relayMessage', [target, sender, message, messageNonce])
}
describe('OVM_L2CrossDomainMessenger', () => { describe('OVM_L2CrossDomainMessenger', () => {
let signer: Signer let signer: Signer
......
import { getContractInterface } from '../../../src/contract-defs'
export const getXDomainCalldata = (
sender: string,
target: string,
message: string,
messageNonce: number
): string => {
return getContractInterface(
'OVM_L2CrossDomainMessenger'
).encodeFunctionData('relayMessage', [target, sender, message, messageNonce])
}
export * from './revert-flags' export * from './revert-flags'
export * from './encoding' export * from './encoding'
export * from './bridge'
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