Commit 6d72b070 authored by platocrat's avatar platocrat Committed by GitHub

docs: add l1-l2-deposit-withdrawal (#684)

* Merge conflict

* add: integration test

* chore: cleanup deps

* fix: nohoist examples so that paths get set correctly

* chore: rename package.json script

* chore: combine test statements into 1

previously the individually separate tests did not test anything meaningful

* fix: get tests to pass

* add yarn and fix typo

* remove call to yarn

* re-add call to yarn for first tests

* revert compilerVersion paths to og

* update ovm-solc path

* update test commands

* typo

* final bug fix

* cleaning

* i will prevail

* i will prevail

* timeout to 60sec
Co-authored-by: default avatarGeorgios Konstantopoulos <me@gakonst.com>

> LGTM, it may be worth splitting the examples testing in a separate github workflow to reduce CI times, as they've become long.
parent cc209be1
...@@ -45,40 +45,51 @@ jobs: ...@@ -45,40 +45,51 @@ jobs:
run: docker-compose run integration_tests run: docker-compose run integration_tests
# Examples Tests # Examples Tests
- name: Test & deploy hardhat-example on hardhat (regression) - name: Test & deploy hardhat-example on Ethereum (regression)
working-directory: ./examples/hardhat working-directory: ./examples/hardhat
run: | run: |
yarn
yarn deploy yarn deploy
yarn test:integration yarn test:integration
- name: Test & deploy hardhat-example on Optimism - name: Test & deploy hardhat-example on Optimistic Ethereum
working-directory: ./examples/hardhat working-directory: ./examples/hardhat
run: | run: |
yarn deploy:ovm yarn deploy:ovm
yarn test:integration:ovm yarn test:integration:ovm
- name: Test & deploy waffle-example on waffle (regression) - name: Test & deploy waffle-example on Ethereum (regression)
working-directory: ./examples/waffle working-directory: ./examples/waffle
run: | run: |
yarn
yarn compile yarn compile
yarn test:integration yarn test:integration
- name: Test & deploy waffle-example on Optimism - name: Test & deploy waffle-example on Optimistic Ethereum
working-directory: ./examples/waffle working-directory: ./examples/waffle
run: | run: |
yarn compile:ovm yarn compile:ovm
yarn test:integration:ovm yarn test:integration:ovm
- name: Test & deploy truffle-example on truffle (regression) - name: Test & deploy truffle-example on Ethereum (regression)
working-directory: ./examples/truffle working-directory: ./examples/truffle
run: | run: |
yarn
yarn compile yarn compile
yarn test:integration yarn test:integration
yarn deploy yarn deploy
- name: Test & deploy truffle-example on Optimism - name: Test & deploy truffle-example on Optimistic Ethereum
working-directory: ./examples/truffle working-directory: ./examples/truffle
run: | run: |
yarn compile:ovm yarn compile:ovm
yarn test:integration:ovm yarn test:integration:ovm
yarn deploy:ovm yarn deploy:ovm
- name: Test l1-l2-deposit-withdrawal example on Optimistic Ethereum with cross-domain message passing
working-directory: ./examples/l1-l2-deposit-withdrawal
run: |
yarn
yarn compile
yarn compile:ovm
yarn test:integration:ovm
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
"author": "Optimism PBC", "author": "Optimism PBC",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"deploy": "hardhat deploy", "clean": "rimraf ./cache-ovm ./cache ./artifacts-ovm ./artifacts ./deployments",
"deploy:ovm": "hardhat deploy --network optimism",
"compile": "hardhat compile", "compile": "hardhat compile",
"compile:ovm": "hardhat compile --network optimism", "compile:ovm": "hardhat compile --network optimism",
"test:integration": "hardhat test", "test:integration": "hardhat test",
"test:integration:ovm": "hardhat test --network optimism", "test:integration:ovm": "hardhat test --network optimism",
"clean": "rimraf ./cache-ovm ./cache ./artifacts-ovm ./artifacts ./deployments" "deploy": "hardhat deploy",
"deploy:ovm": "hardhat deploy --network optimism"
}, },
"devDependencies": { "devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.1", "@nomiclabs/hardhat-ethers": "^2.0.1",
......
This diff is collapsed.
# Packages
node_modules/
# Hardhat build outputs
artifacts/
artifacts-ovm/
cache/
cache-ovm/
optimism
.env
\ No newline at end of file
# L1/L2 ERC20 Deposit + Withdrawal Example
### For the full README, please see the [guided repository of `l1-l2-deposit-withdrawal`](https://github.com/ethereum-optimism/l1-l2-deposit-withdrawal)
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity >0.6.0 <0.8.0;
/**
* @title ERC20
* @dev A super simple ERC20 implementation! Also *very* insecure. Do not use in prod.
*/
contract ERC20 {
/**********
* Events *
**********/
event Transfer(
address indexed _from,
address indexed _to,
uint256 _value
);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
/*************
* Variables *
*************/
mapping (address => uint256) public balances;
mapping (address => mapping (address => uint256)) public allowances;
// Some optional extra goodies.
uint256 public totalSupply;
string public name;
/***************
* Constructor *
***************/
/**
* @param _initialSupply Initial maximum token supply.
* @param _name A name for our ERC20 (technically optional, but it's fun ok jeez).
*/
constructor(
uint256 _initialSupply,
string memory _name
)
public
{
balances[msg.sender] = _initialSupply;
totalSupply = _initialSupply;
name = _name;
}
/********************
* Public Functions *
********************/
/**
* Checks the balance of an address.
* @param _owner Address to check a balance for.
* @return Balance of the address.
*/
function balanceOf(
address _owner
)
external
view
returns (
uint256
)
{
return balances[_owner];
}
/**
* Transfers a balance from your account to someone else's account!
* @param _to Address to transfer a balance to.
* @param _amount Amount to transfer to the other account.
* @return true if the transfer was successful.
*/
function transfer(
address _to,
uint256 _amount
)
external
returns (
bool
)
{
require(
balances[msg.sender] >= _amount,
"You don't have enough balance to make this transfer!"
);
balances[msg.sender] -= _amount;
balances[_to] += _amount;
emit Transfer(
msg.sender,
_to,
_amount
);
return true;
}
/**
* Transfers a balance from someone else's account to another account. You need an allowance
* from the sending account for this to work!
* @param _from Account to transfer a balance from.
* @param _to Account to transfer a balance to.
* @param _amount Amount to transfer to the other account.
* @return true if the transfer was successful.
*/
function transferFrom(
address _from,
address _to,
uint256 _amount
)
external
returns (
bool
)
{
require(
balances[_from] >= _amount,
"Can't transfer from the desired account because it doesn't have enough balance."
);
require(
allowances[_from][msg.sender] >= _amount,
"Can't transfer from the desired account because you don't have enough of an allowance."
);
balances[_to] += _amount;
balances[_from] -= _amount;
emit Transfer(
_from,
_to,
_amount
);
return true;
}
/**
* Approves an account to spend some amount from your account.
* @param _spender Account to approve a balance for.
* @param _amount Amount to allow the account to spend from your account.
* @return true if the allowance was successful.
*/
function approve(
address _spender,
uint256 _amount
)
external
returns (
bool
)
{
allowances[msg.sender][_spender] = _amount;
emit Approval(
msg.sender,
_spender,
_amount
);
return true;
}
/**
* Checks how much a given account is allowed to spend from another given account.
* @param _owner Address of the account to check an allowance from.
* @param _spender Address of the account trying to spend from the owner.
* @return Allowance for the spender from the owner.
*/
function allowance(
address _owner,
address _spender
)
external
view
returns (
uint256
)
{
return allowances[_owner][_spender];
}
/**
* Internal minting function.
* @param _who Address to mint tokens for.
* @param _amount Number of tokens to mint.
*/
function _mint(
address _who,
uint256 _amount
)
internal
{
totalSupply += _amount;
balances[_who] += _amount;
emit Transfer(address(0), _who, _amount);
}
/**
* Internal burning function.
* @param _who Address to burn tokens from.
* @param _amount Number of tokens to burn.
*/
function _burn(
address _who,
uint256 _amount
)
internal
{
require(
totalSupply >= _amount,
"Can't burn more than total supply."
);
require(
balances[_who] >= _amount,
"Account does not have enough to burn."
);
totalSupply -= _amount;
balances[_who] -= _amount;
emit Transfer(_who, address(0), _amount);
}
}
// SPDX-License-Identifier: MIT LICENSE
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/* Contract Imports */
import { ERC20 } from "./ERC20.sol";
/* Library Imports */
import {
Abs_L2DepositedToken
} from "@eth-optimism/contracts/OVM/bridge/tokens/Abs_L2DepositedToken.sol";
/**
* @title L2DepositedERC20
* @dev An L2 Deposited ERC20 is an ERC20 implementation which represents L1 assets deposited into L2, minting and burning on
* deposits and withdrawals.
*
* `L2DepositedERC20` uses the Abs_L2DepositedToken class provided by optimism to link into a standard L1 deposit contract
* while using the `ERC20`implementation I as a developer want to use.
*
* Compiler used: optimistic-solc
* Runtime target: OVM
*/
contract L2DepositedERC20 is Abs_L2DepositedToken, ERC20 {
/**
* @param _l2CrossDomainMessenger Address of the L2 cross domain messenger.
* @param _name Name for the ERC20 token.
*/
constructor(
address _l2CrossDomainMessenger,
string memory _name
)
Abs_L2DepositedToken(_l2CrossDomainMessenger)
ERC20(0, _name)
{}
/**
* Handler that gets called when a withdrawal is initiated.
* @param _to Address triggering the withdrawal.
* @param _amount Amount being withdrawn.
*/
function _handleInitiateWithdrawal(
address _to,
uint256 _amount
)
internal
override
{
_burn(msg.sender, _amount);
}
/**
* Handler that gets called when a deposit is received.
* @param _to Address receiving the deposit.
* @param _amount Amount being deposited.
*/
function _handleFinalizeDeposit(
address _to,
uint256 _amount
)
internal
override
{
_mint(_to, _amount);
}
}
// Plugins
require('@nomiclabs/hardhat-ethers')
require('@nomiclabs/hardhat-waffle')
require('@eth-optimism/hardhat-ovm')
module.exports = {
networks: {
// Add this network to your config!
optimism: {
url: 'http://127.0.0.1:8545',
// This sets the gas price to 0 for all transactions on L2. We do this
// because account balances are not automatically initiated with an ETH
// balance.
gasPrice: 0,
ovm: true // This sets the network as using the ovm and ensure contract will be compiled against that.
},
},
solidity: '0.7.6',
ovm: {
solcVersion: '0.7.6'
},
mocha: {
timeout: 60000
}
}
{
"name": "l1-l2-deposit-withdrawal",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"clean": "rimraf ./cache ./artifacts ./cache-ovm ./artifacts-ovm",
"compile": "hardhat compile",
"compile:ovm": "hardhat compile --network optimism",
"test:integration:ovm": "yarn hardhat test --network optimism"
},
"devDependencies": {
"@eth-optimism/contracts": "^0.3.0",
"@eth-optimism/core-utils": "^0.4.2",
"@eth-optimism/hardhat-ovm": "^0.2.0",
"@eth-optimism/watcher": "^0.0.1-alpha.9",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"chai": "^4.3.4",
"ethereum-waffle": "^3.3.0",
"ethers": "^5.1.4",
"hardhat": "^2.2.1",
"mocha": "^8.4.0",
"rimraf": "^3.0.2"
}
}
/* External Imports */
const { ethers } = require('hardhat')
const { expect } = require('chai')
const { Watcher } = require('@eth-optimism/watcher')
const { getContractFactory } = require('@eth-optimism/contracts')
/* Internal Imports */
const factory = (name, ovm = false) => {
const artifact = require(`../artifacts${ovm ? '-ovm' : ''}/contracts/${name}.sol/${name}.json`)
return new ethers.ContractFactory(artifact.abi, artifact.bytecode)
}
const factory__L1_ERC20 = factory('ERC20')
const factory__L2_ERC20 = factory('L2DepositedERC20', true)
const factory__L1_ERC20Gateway = getContractFactory('OVM_L1ERC20Gateway')
describe(`L1 <> L2 Deposit and Withdrawal`, () => {
// Set up our RPC provider connections.
const l1RpcProvider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:9545')
const l2RpcProvider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545')
// Constructor arguments for `ERC20.sol`
const INITIAL_SUPPLY = 1234
const L1_ERC20_NAME = 'L1 ERC20'
// L1 messenger address depends on the deployment, this is default for our local deployment.
const l1MessengerAddress = '0x59b670e9fA9D0A427751Af201D676719a970857b'
// L2 messenger address is always the same.
const l2MessengerAddress = '0x4200000000000000000000000000000000000007'
const L2_ERC20_NAME = 'L2 ERC20'
// Set up our wallets (using a default private key with 10k ETH allocated to it).
// Need two wallets objects, one for interacting with L1 and one for interacting with L2.
// Both will use the same private key.
const key = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
const l1Wallet = new ethers.Wallet(key, l1RpcProvider)
const l2Wallet = new ethers.Wallet(key, l2RpcProvider)
// Tool that helps watches and waits for messages to be relayed between L1 and L2.
const watcher = new Watcher({
l1: {
provider: l1RpcProvider,
messengerAddress: l1MessengerAddress
},
l2: {
provider: l2RpcProvider,
messengerAddress: l2MessengerAddress
}
})
let L1_ERC20,
L2_ERC20,
L1_ERC20Gateway
before(`deploy contracts`, async () => {
// Deploy an ERC20 token on L1.
L1_ERC20 = await factory__L1_ERC20.connect(l1Wallet).deploy(
INITIAL_SUPPLY,
L1_ERC20_NAME,
{
gasPrice: 0
}
)
await L1_ERC20.deployTransaction.wait()
// Deploy the paired ERC20 token to L2.
L2_ERC20 = await factory__L2_ERC20.connect(l2Wallet).deploy(
l2MessengerAddress,
L2_ERC20_NAME,
{
gasPrice: 0
}
)
await L2_ERC20.deployTransaction.wait()
// Create a gateway that connects the two contracts.
L1_ERC20Gateway = await factory__L1_ERC20Gateway.connect(l1Wallet).deploy(
L1_ERC20.address,
L2_ERC20.address,
l1MessengerAddress,
{
gasPrice: 0
}
)
await L1_ERC20Gateway.deployTransaction.wait()
})
describe('Initialization and initial balances', async () => {
it(`should initialize L2 ERC20`, async () => {
const tx = await L2_ERC20.init(L1_ERC20Gateway.address, { gasPrice: 0 })
await tx.wait()
const txHashPrefix = tx.hash.slice(0, 2)
expect(txHashPrefix).to.eq('0x')
})
it(`should have initial L1 balance of 1234 and initial L2 balance of 0`, async () => {
const l1Balance = await L1_ERC20.balanceOf(l1Wallet.address)
const l2Balance = await L2_ERC20.balanceOf(l1Wallet.address)
expect(l1Balance).to.eq(1234)
expect(l2Balance).to.eq(0)
})
})
describe('L1 to L2 deposit', async () => {
let l1Tx1
it(`should approve 1234 tokens for ERC20 gateway`, async () => {
const tx = await L1_ERC20.approve(L1_ERC20Gateway.address, 1234)
await tx.wait()
const txHashPrefix = tx.hash.slice(0, 2)
expect(txHashPrefix).to.eq('0x')
})
it(`should deposit 1234 tokens into L2 ERC20`, async () => {
l1Tx1 = await L1_ERC20Gateway.deposit(1234, { gasPrice: 0 })
await l1Tx1.wait()
const txHashPrefix = l1Tx1.hash.slice(0, 2)
expect(txHashPrefix).to.eq('0x')
})
it(`should relay deposit message to L2`, async () => {
// Wait for the message to be relayed to L2.
const [ msgHash1 ] = await watcher.getMessageHashesFromL1Tx(l1Tx1.hash)
const l2TxReceipt = await watcher.getL2TransactionReceipt(msgHash1)
expect(l2TxReceipt.to).to.eq(l2MessengerAddress)
})
it(`should have changed L1 balance to 0 and L2 balance to 1234`, async () => {
const l1Balance = await L1_ERC20.balanceOf(l1Wallet.address)
const l2Balance = await L2_ERC20.balanceOf(l1Wallet.address)
expect(l1Balance).to.eq(0)
expect(l2Balance).to.eq(1234)
})
})
describe('L2 to L1 withdrawal', async () => {
let l2Tx1
it(`should withdraw tokens back to L1 ERC20 and relay the message`, async () => {
// Burn the tokens on L2 and ask the L1 contract to unlock on our behalf.
l2Tx1 = await L2_ERC20.withdraw(1234, { gasPrice: 0 })
await l2Tx1.wait()
const txHashPrefix = l2Tx1.hash.slice(0, 2)
expect(txHashPrefix).to.eq('0x')
})
it(`should relay withdrawal message to L1`, async () => {
const [ msgHash2 ] = await watcher.getMessageHashesFromL2Tx(l2Tx1.hash)
const l1TxReceipt = await watcher.getL1TransactionReceipt(msgHash2)
expect(l1TxReceipt.to).to.eq(l1MessengerAddress)
})
it(`should have changed L1 balance back to 1234 and L2 balance back to 0`, async () => {
const l1Balance = await L1_ERC20.balanceOf(l1Wallet.address)
const l2Balance = await L2_ERC20.balanceOf(l1Wallet.address)
expect(l1Balance).to.eq(1234)
expect(l2Balance).to.eq(0)
})
})
})
This diff is collapsed.
...@@ -22,7 +22,7 @@ module.exports = { ...@@ -22,7 +22,7 @@ module.exports = {
compilers: { compilers: {
solc: { solc: {
// Add path to the optimism solc fork // Add path to the optimism solc fork
version: '../../node_modules/@eth-optimism/solc', version: './node_modules/@eth-optimism/solc',
settings: { settings: {
optimizer: { optimizer: {
enabled: true, enabled: true,
......
This diff is collapsed.
{ {
"compilerVersion": "./../../node_modules/@eth-optimism/solc", "compilerVersion": "./node_modules/@eth-optimism/solc",
"sourceDirectory": "./contracts", "sourceDirectory": "./contracts",
"outputDirectory": "./build-ovm" "outputDirectory": "./build-ovm"
} }
\ No newline at end of file
{ {
"compilerVersion": "./../../node_modules/solc", "compilerVersion": "./node_modules/solc",
"sourceDirectory": "./contracts", "sourceDirectory": "./contracts",
"outputDirectory": "./build" "outputDirectory": "./build"
} }
\ No newline at end of file
...@@ -509,6 +509,24 @@ ...@@ -509,6 +509,24 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/chai@^4.2.17":
version "4.2.18"
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4"
integrity sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ==
"@types/glob@*":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
dependencies:
"@types/minimatch" "*"
"@types/node" "*"
"@types/minimatch@*":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21"
integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==
"@types/mkdirp@^0.5.2": "@types/mkdirp@^0.5.2":
version "0.5.2" version "0.5.2"
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f"
...@@ -516,6 +534,11 @@ ...@@ -516,6 +534,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/mocha@^7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce"
integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==
"@types/node-fetch@^2.5.5": "@types/node-fetch@^2.5.5":
version "2.5.10" version "2.5.10"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132"
...@@ -553,6 +576,14 @@ ...@@ -553,6 +576,14 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/rimraf@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f"
integrity sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ==
dependencies:
"@types/glob" "*"
"@types/node" "*"
"@types/secp256k1@^4.0.1": "@types/secp256k1@^4.0.1":
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.2.tgz#20c29a87149d980f64464e56539bf4810fdb5d1d" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.2.tgz#20c29a87149d980f64464e56539bf4810fdb5d1d"
......
...@@ -3,13 +3,15 @@ ...@@ -3,13 +3,15 @@
"version": "1.0.0", "version": "1.0.0",
"author": "Optimism PBC", "author": "Optimism PBC",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": {
"packages/*", "packages": [
"l2geth", "packages/*",
"integration-tests", "l2geth",
"specs", "integration-tests",
"examples/*" "specs"
], ],
"nohoist": ["examples/*"]
},
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"lerna": "^4.0.0", "lerna": "^4.0.0",
......
This diff is collapsed.
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