├── waffle.json ├── test ├── getArtifact.js └── erc20.spec.js ├── package.json ├── LICENSE ├── .gitignore ├── contracts ├── IERC20.sol └── ERC20.sol └── README.md /waffle.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerVersion": "./node_modules/solc", 3 | "sourceDirectory": "./contracts", 4 | "outputDirectory": "./build" 5 | } -------------------------------------------------------------------------------- /test/getArtifact.js: -------------------------------------------------------------------------------- 1 | const getArtifact = (useL2) => { 2 | const buildFolder = useL2 ? 'build-ovm' : 'build' 3 | const ERC20Artifact = require(`../${buildFolder}/ERC20.json`) 4 | return ERC20Artifact 5 | } 6 | 7 | module.exports = { getArtifact } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eth-optimism/ERC20-Example", 3 | "version": "0.0.1-alpha.1", 4 | "description": "Basic example of how to test a basic token contract with Waffle in the OVM", 5 | "scripts": { 6 | "clean": "rimraf build build-ovm" 7 | }, 8 | "keywords": [ 9 | "optimism", 10 | "rollup", 11 | "optimistic", 12 | "ethereum", 13 | "virtual", 14 | "machine", 15 | "OVM", 16 | "ERC20", 17 | "waffle" 18 | ], 19 | "homepage": "https://github.com/ethereum-optimism/ERC20-Example#readme", 20 | "license": "MIT", 21 | "author": "Optimism PBC", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/ethereum-optimism/ERC20-Example.git" 25 | }, 26 | "devDependencies": { 27 | "chai": "^4.3.4", 28 | "dotenv": "^8.2.0", 29 | "ethereum-waffle": "^3.0.0", 30 | "mocha": "^7.0.1", 31 | "rimraf": "^2.6.3" 32 | }, 33 | "publishConfig": { 34 | "access": "public" 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OptimismPBC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | waffle-ovm.json 2 | optimism-integration 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Microbundle cache 60 | .rpt2_cache/ 61 | .rts2_cache_cjs/ 62 | .rts2_cache_es/ 63 | .rts2_cache_umd/ 64 | 65 | # Optional REPL history 66 | .node_repl_history 67 | 68 | # Output of 'npm pack' 69 | *.tgz 70 | 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | # dotenv environment variables file 75 | .env 76 | .env.test 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | 81 | # Next.js build output 82 | .next 83 | 84 | # Nuxt.js build / generate output 85 | .nuxt 86 | dist 87 | 88 | # Gatsby files 89 | .cache/ 90 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 91 | # https://nextjs.org/blog/next-9-1#public-directory-support 92 | # public 93 | 94 | # vuepress build output 95 | .vuepress/dist 96 | 97 | # Serverless directories 98 | .serverless/ 99 | 100 | # FuseBox cache 101 | .fusebox/ 102 | 103 | # DynamoDB Local files 104 | .dynamodb/ 105 | 106 | # TernJS port file 107 | .tern-port 108 | 109 | .DS_Store 110 | 111 | build/ 112 | build-ovm/ -------------------------------------------------------------------------------- /contracts/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | // Abstract contract for the full ERC 20 Token standard 3 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 4 | pragma solidity ^0.7.6; 5 | 6 | interface IERC20 { 7 | /* This is a slight change to the ERC20 base standard. 8 | function totalSupply() constant returns (uint256 supply); 9 | is replaced with: 10 | uint256 public totalSupply; 11 | This automatically creates a getter function for the totalSupply. 12 | This is moved to the base contract since public getter functions are not 13 | currently recognised as an implementation of the matching abstract 14 | function by the compiler. 15 | */ 16 | /// total amount of tokens 17 | function totalSupply() external view returns (uint256); 18 | 19 | /// @param _owner The address from which the balance will be retrieved 20 | /// @return balance 21 | function balanceOf(address _owner) external view returns (uint256 balance); 22 | 23 | /// @notice send `_value` token to `_to` from `msg.sender` 24 | /// @param _to The address of the recipient 25 | /// @param _value The amount of token to be transferred 26 | /// @return success Whether the transfer was successful or not 27 | function transfer(address _to, uint256 _value) 28 | external 29 | returns (bool success); 30 | 31 | /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` 32 | /// @param _from The address of the sender 33 | /// @param _to The address of the recipient 34 | /// @param _value The amount of token to be transferred 35 | /// @return success Whether the transfer was successful or not 36 | function transferFrom( 37 | address _from, 38 | address _to, 39 | uint256 _value 40 | ) external returns (bool success); 41 | 42 | /// @notice `msg.sender` approves `_spender` to spend `_value` tokens 43 | /// @param _spender The address of the account able to transfer the tokens 44 | /// @param _value The amount of tokens to be approved for transfer 45 | /// @return success Whether the approval was successful or not 46 | function approve(address _spender, uint256 _value) 47 | external 48 | returns (bool success); 49 | 50 | /// @param _owner The address of the account owning tokens 51 | /// @param _spender The address of the account able to transfer the tokens 52 | /// @return remaining Amount of remaining tokens allowed to spent 53 | function allowance(address _owner, address _spender) 54 | external 55 | view 56 | returns (uint256 remaining); 57 | 58 | // solhint-disable-next-line no-simple-event-func-name 59 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 60 | event Approval( 61 | address indexed _owner, 62 | address indexed _spender, 63 | uint256 _value 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | /* 3 | Implements ERC20 token standard: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 4 | .*/ 5 | 6 | pragma solidity ^0.7.6; 7 | 8 | import "./IERC20.sol"; 9 | 10 | contract ERC20 is IERC20 { 11 | uint256 private constant MAX_UINT256 = 2**256 - 1; 12 | mapping(address => uint256) public balances; 13 | mapping(address => mapping(address => uint256)) public allowed; 14 | /* 15 | NOTE: 16 | The following variables are OPTIONAL vanities. One does not have to include them. 17 | They allow one to customise the token contract & in no way influences the core functionality. 18 | Some wallets/interfaces might not even bother to look at this information. 19 | */ 20 | string public name; //fancy name: eg OVM Coin 21 | uint8 public decimals; //How many decimals to show. 22 | string public symbol; //An identifier: eg OVM 23 | uint256 public override totalSupply; 24 | 25 | constructor( 26 | uint256 _initialAmount, 27 | string memory _tokenName, 28 | uint8 _decimalUnits, 29 | string memory _tokenSymbol 30 | ) { 31 | balances[msg.sender] = _initialAmount; // Give the creator all initial tokens 32 | totalSupply = _initialAmount; // Update total supply 33 | name = _tokenName; // Set the name for display purposes 34 | decimals = _decimalUnits; // Amount of decimals for display purposes 35 | symbol = _tokenSymbol; // Set the symbol for display purposes 36 | } 37 | 38 | function transfer(address _to, uint256 _value) 39 | external 40 | override 41 | returns (bool success) 42 | { 43 | require(balances[msg.sender] >= _value); 44 | balances[msg.sender] -= _value; 45 | balances[_to] += _value; 46 | emit Transfer(msg.sender, _to, _value); 47 | return true; 48 | } 49 | 50 | function transferFrom( 51 | address _from, 52 | address _to, 53 | uint256 _value 54 | ) external override returns (bool success) { 55 | uint256 allowance_ = allowed[_from][msg.sender]; 56 | require(balances[_from] >= _value && allowance_ >= _value); 57 | balances[_to] += _value; 58 | balances[_from] -= _value; 59 | if (allowance_ < MAX_UINT256) { 60 | allowed[_from][msg.sender] -= _value; 61 | } 62 | emit Transfer(_from, _to, _value); 63 | return true; 64 | } 65 | 66 | function balanceOf(address _owner) 67 | external 68 | view 69 | override 70 | returns (uint256 balance) 71 | { 72 | return balances[_owner]; 73 | } 74 | 75 | function approve(address _spender, uint256 _value) 76 | external 77 | override 78 | returns (bool success) 79 | { 80 | allowed[msg.sender][_spender] = _value; 81 | emit Approval(msg.sender, _spender, _value); 82 | return true; 83 | } 84 | 85 | function allowance(address _owner, address _spender) 86 | external 87 | view 88 | override 89 | returns (uint256 remaining) 90 | { 91 | return allowed[_owner][_spender]; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/erc20.spec.js: -------------------------------------------------------------------------------- 1 | /* External imports */ 2 | require('dotenv/config') 3 | const { use, expect } = require('chai') 4 | const { ethers } = require('ethers') 5 | const { solidity } = require('ethereum-waffle') 6 | 7 | /* Internal imports */ 8 | const { getArtifact } = require('./getArtifact') 9 | 10 | use(solidity) 11 | 12 | describe('ERC20 smart contract', () => { 13 | let ERC20, 14 | provider, 15 | wallet, 16 | walletTo, 17 | walletEmpty, 18 | walletAddress, 19 | walletToAddress, 20 | walletEmptyAddress 21 | 22 | const privateKey = ethers.Wallet.createRandom().privateKey 23 | const privateKeyEmpty = ethers.Wallet.createRandom().privateKey 24 | const useL2 = process.env.TARGET === 'OVM' 25 | 26 | if (useL2 == true) { 27 | provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545') 28 | provider.pollingInterval = 100 29 | provider.getGasPrice = async () => ethers.BigNumber.from(0) 30 | } else { 31 | provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:9545') 32 | } 33 | 34 | walletTo = new ethers.Wallet(privateKey, provider) 35 | walletEmpty = new ethers.Wallet(privateKeyEmpty, provider) 36 | 37 | // parameters to use for our test coin 38 | const COIN_NAME = 'OVM Test Coin' 39 | const TICKER = 'OVM' 40 | const NUM_DECIMALS = 1 41 | 42 | describe('when using a deployed contract instance', () => { 43 | before(async () => { 44 | wallet = await provider.getSigner(0) 45 | walletAddress = await wallet.getAddress() 46 | walletToAddress = await walletTo.getAddress() 47 | walletEmptyAddress = await walletEmpty.getAddress() 48 | 49 | const Artifact__ERC20 = getArtifact(useL2) 50 | const Factory__ERC20 = new ethers.ContractFactory( 51 | Artifact__ERC20.abi, 52 | Artifact__ERC20.bytecode, 53 | wallet 54 | ) 55 | 56 | // TODO: Remove this hardcoded gas limit 57 | ERC20 = await Factory__ERC20.connect(wallet).deploy( 58 | 1000, 59 | COIN_NAME, 60 | NUM_DECIMALS, 61 | TICKER 62 | ) 63 | await ERC20.deployTransaction.wait() 64 | }) 65 | 66 | it('should assigns initial balance', async () => { 67 | expect(await ERC20.balanceOf(walletAddress)).to.equal(1000) 68 | }) 69 | 70 | it('should correctly set vanity information', async () => { 71 | const name = await ERC20.name() 72 | expect(name).to.equal(COIN_NAME) 73 | 74 | const decimals = await ERC20.decimals() 75 | expect(decimals).to.equal(NUM_DECIMALS) 76 | 77 | const symbol = await ERC20.symbol() 78 | expect(symbol).to.equal(TICKER) 79 | }) 80 | 81 | it('should transfer amount to destination account', async () => { 82 | const tx = await ERC20.connect(wallet).transfer(walletToAddress, 7) 83 | await tx.wait() 84 | const walletToBalance = await ERC20.balanceOf(walletToAddress) 85 | expect(walletToBalance.toString()).to.equal('7') 86 | }) 87 | 88 | it('should emit Transfer event', async () => { 89 | const tx = ERC20.connect(wallet).transfer(walletToAddress, 7) 90 | await expect(tx) 91 | .to.emit(ERC20, 'Transfer') 92 | .withArgs(walletAddress, walletToAddress, 7) 93 | }) 94 | 95 | it('should not transfer above the amount', async () => { 96 | const walletToBalanceBefore = await ERC20.balanceOf(walletToAddress) 97 | await expect(ERC20.transfer(walletToAddress, 1007)).to.be.reverted 98 | const walletToBalanceAfter = await ERC20.balanceOf(walletToAddress) 99 | expect(walletToBalanceBefore).to.eq(walletToBalanceAfter) 100 | }) 101 | 102 | it('should not transfer from empty account', async () => { 103 | const ERC20FromOtherWallet = ERC20.connect(walletEmpty) 104 | await expect(ERC20FromOtherWallet.transfer(walletEmptyAddress, 1)).to.be 105 | .reverted 106 | }) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **[DEPRECATED]** This repository is now deprecated in favour of the new development [monorepo](https://github.com/ethereum-optimism/optimism-monorepo). 2 | 3 | # Getting Started with the Optimistic Ethereum: Simple ERC20 Token Waffle Tutorial 4 | 5 | Hi there! Welcome to our Optimistic Ethereum ERC20 Waffle example! 6 | 7 | If your preferred smart contract testing framework is Truffle, see our Optimistic Ethereum ERC20 Truffle tutorial [here](https://github.com/ethereum-optimism/Truffle-ERC20-Example). 8 | 9 | If you're interested in writing your first L2-compatible smart contract using Waffle as your smart contract testing framework, then you've come to the right place! 10 | This repo serves as an example for how go through and compile & test your contracts on both Ethereum and Optimistic Ethereum. 11 | 12 | Let's begin! 13 | 14 | ## Prerequisites 15 | 16 | Please make sure you've installed the following before continuing: 17 | 18 | - [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 19 | - [Node.js](https://nodejs.org/en/download/) 20 | - [Yarn 1](https://classic.yarnpkg.com/en/docs/install#mac-stable) 21 | - [Docker](https://docs.docker.com/engine/install/) 22 | 23 | ## Setup 24 | 25 | To start, clone this `Waffle-ERC20-Example` repo, enter it, and install all of its dependencies: 26 | 27 | ```sh 28 | git clone https://github.com/ethereum-optimism/Waffle-ERC20-Example.git 29 | cd Waffle-ERC20-Example 30 | yarn install 31 | ``` 32 | 33 | ## Step 1: Compile your contracts for Optimistic Ethereum 34 | 35 | Compiling a contract for Optimistic Ethereum is pretty easy! 36 | First we'll need to install the [`@eth-optimism/solc`](https://www.npmjs.com/package/@eth-optimism/solc) and [`solc`](https://www.npmjs.com/package/solc) packages. 37 | Since we currently only support `solc` versions `0.5.16`, `0.6.12`, and `0.7.6` for Optimistic Ethereum contracts, we'll be using version `0.7.6` in this example. 38 | Let's add these two packages: 39 | 40 | ```sh 41 | yarn add @eth-optimism/solc@0.7.6-alpha.1 42 | yarn add solc@0.7.6 43 | ``` 44 | 45 | Next we just need to add a new Waffle config to compile our contracts. 46 | Create `waffle-ovm.json` and add this to it: 47 | 48 | ```json 49 | { 50 | "compilerVersion": "./node_modules/@eth-optimism/solc", 51 | "sourceDirectory": "./contracts", 52 | "outputDirectory": "./build-ovm" 53 | } 54 | ``` 55 | 56 | Here, we specify the new custom Optimistic Ethereum compiler we just installed and the new build path for our compiled contracts. 57 | 58 | And we're ready to compile! All you have to do is specify the `waffle-ovm.json` config in your `waffle` command: 59 | 60 | ```sh 61 | yarn waffle waffle-ovm.json 62 | ``` 63 | 64 | Our `waffle-ovm.json` config file tells Waffle that we want to use the Optimistic Ethereum solidity compiler. 65 | 66 | Yep, it's that easy. You can verify that everything went well by looking for the `build-ovm` directory. 67 | 68 | Here, `build-ovm` signifies that the contracts contained in this directory have been compiled for the OVM, the Optimistic Virtual Machine, as opposed to the Ethereum Virtual Machine. Now let's move on to testing! 69 | 70 | ## Step 2: Testing your Optimistic Ethereum contracts 71 | 72 | Testing with Waffle is easy. We've included a simple set of ERC20 tests inside [`Waffle-ERC20-Example/test/erc20.spec.js`](https://github.com/ethereum-optimism/Waffle-ERC20-Example/blob/main/test/erc20.test.js). Let's run these tests with `waffle`: 73 | 74 | ```sh 75 | yarn mocha 'test/*.spec.js' --timeout 10000 76 | ``` 77 | 78 | If everything went well, you should see a bunch of green checkmarks. 79 | 80 | ### Testing an Optimistic Ethereum contract 81 | 82 | Woot! It's finally time to test our contract on top of Optimistic Ethereum. 83 | But first we'll need to get a local version of an Optimistic Ethereum node running... 84 | 85 | ------- 86 | 87 | Fortunately, we have some handy dandy tools that make it easy to spin up a local Optimistic Ethereum node! 88 | 89 | Since we're going to be using Docker, make sure that Docker is installed on your machine prior to moving on (info on how to do that [here](https://docs.docker.com/engine/install/)). 90 | **We recommend opening up a second terminal for this part.** 91 | This way you'll be able to keep the Optimistic Ethereum node running so you can execute some contract tests. 92 | 93 | Now we just need to download, build, and install our Optimistic Ethereum node by running the following commands. 94 | Please note that `docker-compose build` *will* take a while. 95 | We're working on improving this (sorry)! 96 | 97 | ```sh 98 | git clone git@github.com:ethereum-optimism/optimism.git 99 | cd optimism 100 | yarn install 101 | yarn build 102 | cd ops 103 | docker-compose build 104 | docker-compose up 105 | ``` 106 | 107 | You now have your very own locally deployed instance of Optimistic Ethereum! 🙌 108 | 109 | ------- 110 | 111 | With your local instance of Optimistic Ethereum up and running, let's test your contracts! Since the two JSON RPC provider URLs (one for your local instance Ethereum and Optimistic Ethereum) have already been specified in your `.env` file, all we need to do next is run the test command. 112 | 113 | To do that, run: 114 | 115 | ```sh 116 | yarn TARGET=OVM mocha 'test/*.spec.js' --timeout 50000 117 | ``` 118 | 119 | Notice that we use the `TARGET=OVM` flag to let `mocha` know that we want to use the `build-ovm` folder as our path to our JSON files. 120 | (Remember that these JSON files were compiled using the Optimistic Ethereum solidity compiler!) 121 | 122 | You should see a set of passing tests for your ERC20 contract. If so, congrats! 123 | You're ready to deploy an application to Optimistic Ethereum. 124 | It really is that easy. 125 | 126 | And uh... yeah. That's pretty much it. 127 | Tutorial complete. 128 | Hopefully now you know the basics of working with Optimistic Ethereum! 🅾️ 129 | 130 | ------ 131 | 132 | ## Further Reading 133 | 134 | ### OVM vs. EVM Incompatibilities 135 | 136 | Our goal is to bring the OVM as close to 100% compatibility with all existing Ethereum projects, but our software is still in an early stage. [Our community hub docs](https://community.optimism.io/docs/protocol/evm-comparison.html) will maintain the most up to date list of known incompatibilities between the OVM and EVM, along with our plans to fix them. 137 | 138 | ### Wasn't that easy? 139 | 140 | The OVM provides a fresh new take on layer 2 development: it's _mostly_ identical to layer 1 development. 141 | However, there are a few differences that are worth noting, which you can read more about in our [EVM comparison documentation](https://community.optimism.io/docs/protocol/evm-comparison.html). 142 | No hoops, no tricks--the Ethereum you know and love, ready to scale up with L2. 143 | For more info on our progress and what's going on behind the scenes, you can follow us on [Twitter](https://twitter.com/optimismPBC). 144 | 145 | Want to try deploying contracts to the Optimistic Ethereum testnet next? [Check out the full integration guide](https://community.optimism.io/docs/developers/integration.html) on the Optimism community hub. 146 | 147 | ------ 148 | 149 | ## Troubleshooting 150 | 151 | Example project not working? [Create a Github Issue](https://github.com/ethereum-optimism/Truffle-ERC20-Example/issues), or hop in our [Discord](https://discordapp.com/invite/jrnFEvq) channel and ask away. 152 | --------------------------------------------------------------------------------