Commit a30817e5 authored by platocrat's avatar platocrat Committed by GitHub

docs: add truffle example + ci (#666)

* docs: add truffle example

* fix: add gasPrice: 0 to all ctc calls and deploys

* chore(truffle-example): use correct truffle command

* fix: install missing packages

* style: fix missing newlines

* fix: attempt to fix yarn.lock (2)

* Update integration.yml

* specify truffle config and network

* correctly specify path and version

* relayer: logging cleanup (#807)

* relayer: don't log options at startup

* chore: add changeset

* relayer: log specifc config options

* config: message relayer (#809)

* relayer: migrate towards prefixed config w/ backwards compat

* chore: update relayer config parsing

* env: use start-offset instead of block offset

* lint: fix

* deps: add bcfg

* message-relayer: cleaner config parsing

* lint: fix

* Version Packages (#833)
Co-authored-by: default avatargithub-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix: geth miner timestamp bug (#836)

* l2geth: use correct timestamp protection

* chore: add changeset

* Version Packages (#837)
Co-authored-by: default avatargithub-actions[bot] <github-actions[bot]@users.noreply.github.com>

* style: require curly braces for if statements (#835)

* test[integration-tests]: l2geth call and creation OOG (#839)

* fix: WE DID IT
Co-authored-by: default avatarGeorgios Konstantopoulos <me@gakonst.com>
Co-authored-by: default avatarKelvin Fichter <kelvinfichter@gmail.com>
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarben-chain <ben@pseudonym.party>
parent 8d67991a
......@@ -62,8 +62,23 @@ jobs:
run: |
yarn compile
yarn test:integration
- name: Test & deploy waffle-example on Optimism
working-directory: ./examples/waffle
run: |
yarn compile:ovm
yarn test:integration:ovm
- name: Test & deploy truffle-example on truffle (regression)
working-directory: ./examples/truffle
run: |
yarn compile
yarn test:integration
yarn deploy
- name: Test & deploy truffle-example on Optimism
working-directory: ./examples/truffle
run: |
yarn compile:ovm
yarn test:integration:ovm
yarn deploy:ovm
# Project specific
build
build-ovm
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
.DS_Store
build/
# Getting Started with Optimistic Ethereum: Simple ERC20 Token Truffle Tutorial
### For the full README, please see the [guided repository, `Truffle-ER20-Example`](https://github.com/ethereum-optimism/Truffle-ERC20-Example)
\ No newline at end of file
// SPDX-License-Identifier: MIT LICENSE
/* Implements ERC20 token standard: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md */
pragma solidity ^0.7.6;
import "./IERC20.sol";
contract ERC20 is IERC20 {
uint256 private constant 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;
uint8 public decimals;
string public symbol;
uint256 public totalSupply;
constructor(
uint256 _initialAmount,
string memory _tokenName,
uint8 _decimalUnits,
string memory _tokenSymbol
) {
balances[msg.sender] = _initialAmount; // Give the creator all initial tokens
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
}
function transfer(address _to, uint256 _value)
public
override
returns (bool success)
{
require(balances[msg.sender] >= _value);
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value); //solhint-disable-line indent, no-unused-vars
return true;
}
function transferFrom(
address _from,
address _to,
uint256 _value
) public 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); //solhint-disable-line indent, no-unused-vars
return true;
}
function balanceOf(address _owner)
public
view
override
returns (uint256 balance)
{
return balances[_owner];
}
function approve(address _spender, uint256 _value)
public
override
returns (bool success)
{
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value); //solhint-disable-line indent, no-unused-vars
return true;
}
function allowance(address _owner, address _spender)
public
view
override
returns (uint256 remaining)
{
return allowed[_owner][_spender];
}
}
// SPDX-License-Identifier: MIT LICENSE
// Abstract contract for the full ERC20 Token standard
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
pragma solidity ^0.7.6;
interface IERC20 {
/// @param _owner The address from which the balance will be retrieved
/// @return balance The balance
function balanceOf(address _owner) external view returns (uint256 balance);
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return success Whether the transfer was successful or not
function transfer(address _to, uint256 _value)
external
returns (bool success);
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return success Whether the transfer was successful or not
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
/// @notice `msg.sender` approves `_spender` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of tokens to be approved for transfer
/// @return success Whether the approval was successful or not
function approve(address _spender, uint256 _value)
external
returns (bool success);
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return remaining Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender)
external
view
returns (uint256 remaining);
// solhint-disable-next-line no-simple-event-func-name
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
}
const ERC20 = artifacts.require('ERC20')
module.exports = function (deployer, accounts) {
const tokenName = 'My Optimistic Coin'
const tokenSymbol = 'OPT'
const tokenDecimals = 1
// deployment steps
deployer.deploy(
ERC20,
10000,
tokenName,
tokenDecimals,
tokenSymbol,
{ gasPrice: 0 }
)
}
{
"name": "@ethereum-optimism/Truffle-ERC20-Example",
"private": true,
"version": "1.0.0-alpha.1",
"description": "Example of using Optimistic Ethereum compiler & local Optimistic Ethereum nodes with Truffle to run a full ERC20 test suite",
"scripts": {
"clean": "rimraf build build-ovm",
"compile": "truffle compile",
"compile:ovm": "truffle compile --config truffle-config-ovm.js",
"test:integration": "truffle test",
"test:integration:ovm": "truffle test --network optimism --config truffle-config-ovm.js",
"deploy": "truffle migrate --network ethereum --config truffle-config",
"deploy:ovm": "truffle migrate --network optimism --config truffle-config-ovm.js"
},
"keywords": [
"optimism",
"rollup",
"optimistic",
"ethereum",
"truffle",
"ovm",
"example",
"ERC20",
"token"
],
"homepage": "https://github.com/ethereum-optimism/Truffle-ERC20-Example#readme",
"license": "MIT",
"author": "Optimism PBC",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/Truffle-ERC20-Example.git"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@eth-optimism/solc": "0.7.6-alpha.1",
"rimraf": "^3.0.2",
"truffle": "^5.3.6",
"@truffle/hdwallet-provider": "^1.4.0"
}
}
\ No newline at end of file
let token
const ERC20 = artifacts.require('ERC20')
contract('ERC20', (accounts) => {
const tokenName = 'My Optimistic Coin'
const tokenSymbol = 'OPT'
const tokenDecimals = 1
beforeEach(async () => {
token = await ERC20.new(10000, tokenName, tokenDecimals, tokenSymbol, { from: accounts[ 0 ], gasPrice: 0 })
})
it('creation: should create an initial balance of 10000 for the creator', async () => {
const balance = await token.balanceOf.call(accounts[ 0 ], {
gasPrice: 0
})
assert.strictEqual(balance.toNumber(), 10000)
})
it('creation: test correct setting of vanity information', async () => {
const name = await token.name.call()
assert.strictEqual(name, tokenName)
const decimals = await token.decimals.call()
assert.strictEqual(decimals.toNumber(), tokenDecimals)
const symbol = await token.symbol.call()
assert.strictEqual(symbol, tokenSymbol)
})
it('creation: should succeed in creating over 2^256 - 1 (max) tokens', async () => {
// 2^256 - 1
const token2 = await ERC20.new('115792089237316195423570985008687907853269984665640564039457584007913129639935', 'Simon Bucks', 1, 'SBX', { from: accounts[ 0 ], gasPrice: 0 })
const totalSupply = await token2.totalSupply()
assert.strictEqual(totalSupply.toString(), '115792089237316195423570985008687907853269984665640564039457584007913129639935')
})
// TRANSERS
// normal transfers without approvals
it('transfers: ether transfer should be reversed.', async () => {
const balanceBefore = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balanceBefore.toNumber(), 10000)
let threw = false
try {
await web3.eth.sendTransaction({ from: accounts[ 0 ], to: token.address, value: web3.utils.toWei('10', 'Ether'), gasPrice: 0 })
} catch (e) {
threw = true
}
assert.equal(threw, true)
const balanceAfter = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balanceAfter.toNumber(), 10000)
})
it('transfers: should transfer 10000 to accounts[1] with accounts[0] having 10000', async () => {
await token.transfer(accounts[ 1 ], 10000, { from: accounts[ 0 ], gasPrice: 0 })
const balance = await token.balanceOf.call(accounts[ 1 ])
assert.strictEqual(balance.toNumber(), 10000)
})
it('transfers: should fail when trying to transfer 10001 to accounts[1] with accounts[0] having 10000', async () => {
let threw = false
try {
await token.transfer.call(accounts[ 1 ], 10001, { from: accounts[ 0 ], gasPrice: 0 })
} catch (e) {
threw = true
}
assert.equal(threw, true)
})
it('transfers: should handle zero-transfers normally', async () => {
assert(await token.transfer.call(accounts[ 1 ], 0, { from: accounts[ 0 ], gasPrice: 0 }), 'zero-transfer has failed')
})
// NOTE: testing uint256 wrapping is impossible since you can't supply > 2^256 -1
// todo: transfer max amounts
// APPROVALS
it('approvals: msg.sender should approve 100 to accounts[1]', async () => {
await token.approve(accounts[ 1 ], 100, { from: accounts[ 0 ], gasPrice: 0 })
const allowance = await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance.toNumber(), 100)
})
// bit overkill. But is for testing a bug
it('approvals: msg.sender approves accounts[1] of 100 & withdraws 20 once.', async () => {
const balance0 = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balance0.toNumber(), 10000)
await token.approve(accounts[ 1 ], 100, { from: accounts[ 0 ], gasPrice: 0 }) // 100
const balance2 = await token.balanceOf.call(accounts[ 2 ])
assert.strictEqual(balance2.toNumber(), 0, 'balance2 not correct')
await token.transferFrom.call(accounts[ 0 ], accounts[ 2 ], 20, { from: accounts[ 1 ], gasPrice: 0 })
await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
await token.transferFrom(accounts[ 0 ], accounts[ 2 ], 20, { from: accounts[ 1 ], gasPrice: 0 }) // -20
const allowance01 = await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance01.toNumber(), 80) // =80
const balance22 = await token.balanceOf.call(accounts[ 2 ])
assert.strictEqual(balance22.toNumber(), 20)
const balance02 = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balance02.toNumber(), 9980)
})
// should approve 100 of msg.sender & withdraw 50, twice. (should succeed)
it('approvals: msg.sender approves accounts[1] of 100 & withdraws 20 twice.', async () => {
await token.approve(accounts[ 1 ], 100, { from: accounts[ 0 ], gasPrice: 0 })
const allowance01 = await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance01.toNumber(), 100)
await token.transferFrom(accounts[ 0 ], accounts[ 2 ], 20, { from: accounts[ 1 ], gasPrice: 0 })
const allowance012 = await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance012.toNumber(), 80)
const balance2 = await token.balanceOf.call(accounts[ 2 ])
assert.strictEqual(balance2.toNumber(), 20)
const balance0 = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balance0.toNumber(), 9980)
// FIRST tx done.
// onto next.
await token.transferFrom(accounts[ 0 ], accounts[ 2 ], 20, { from: accounts[ 1 ], gasPrice: 0 })
const allowance013 = await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance013.toNumber(), 60)
const balance22 = await token.balanceOf.call(accounts[ 2 ])
assert.strictEqual(balance22.toNumber(), 40)
const balance02 = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balance02.toNumber(), 9960)
})
// should approve 100 of msg.sender & withdraw 50 & 60 (should fail).
it('approvals: msg.sender approves accounts[1] of 100 & withdraws 50 & 60 (2nd tx should fail)', async () => {
await token.approve(accounts[ 1 ], 100, { from: accounts[ 0 ], gasPrice: 0 })
const allowance01 = await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance01.toNumber(), 100)
await token.transferFrom(accounts[ 0 ], accounts[ 2 ], 50, { from: accounts[ 1 ], gasPrice: 0 })
const allowance012 = await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance012.toNumber(), 50)
const balance2 = await token.balanceOf.call(accounts[ 2 ])
assert.strictEqual(balance2.toNumber(), 50)
const balance0 = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balance0.toNumber(), 9950)
// FIRST tx done.
// onto next.
let threw = false
try {
await token.transferFrom.call(accounts[ 0 ], accounts[ 2 ], 60, { from: accounts[ 1 ], gasPrice: 0 })
} catch (e) {
threw = true
}
assert.equal(threw, true)
})
it('approvals: attempt withdrawal from account with no allowance (should fail)', async () => {
let threw = false
try {
await token.transferFrom.call(accounts[ 0 ], accounts[ 2 ], 60, { from: accounts[ 1 ], gasPrice: 0 })
} catch (e) {
threw = true
}
assert.equal(threw, true)
})
it('approvals: allow accounts[1] 100 to withdraw from accounts[0]. Withdraw 60 and then approve 0 & attempt transfer.', async () => {
await token.approve(accounts[ 1 ], 100, { from: accounts[ 0 ], gasPrice: 0 })
await token.transferFrom(accounts[ 0 ], accounts[ 2 ], 60, { from: accounts[ 1 ], gasPrice: 0 })
await token.approve(accounts[ 1 ], 0, { from: accounts[ 0 ], gasPrice: 0 })
let threw = false
try {
await token.transferFrom.call(accounts[ 0 ], accounts[ 2 ], 10, { from: accounts[ 1 ], gasPrice: 0 })
} catch (e) {
threw = true
}
assert.equal(threw, true)
})
it('approvals: approve max (2^256 - 1)', async () => {
await token.approve(accounts[ 1 ], '115792089237316195423570985008687907853269984665640564039457584007913129639935', { from: accounts[ 0 ], gasPrice: 0 })
const allowance = await token.allowance(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance.toString(), '115792089237316195423570985008687907853269984665640564039457584007913129639935')
})
// should approve max of msg.sender & withdraw 20 without changing allowance (should succeed).
it('approvals: msg.sender approves accounts[1] of max (2^256 - 1) & withdraws 20', async () => {
const balance0 = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balance0.toNumber(), 10000)
const max = '115792089237316195423570985008687907853269984665640564039457584007913129639935'
await token.approve(accounts[ 1 ], max, { from: accounts[ 0 ], gasPrice: 0 })
const balance2 = await token.balanceOf.call(accounts[ 2 ])
assert.strictEqual(balance2.toNumber(), 0, 'balance2 not correct')
await token.transferFrom(accounts[ 0 ], accounts[ 2 ], 20, { from: accounts[ 1 ], gasPrice: 0 })
const allowance01 = await token.allowance.call(accounts[ 0 ], accounts[ 1 ])
assert.strictEqual(allowance01.toString(), max)
const balance22 = await token.balanceOf.call(accounts[ 2 ])
assert.strictEqual(balance22.toNumber(), 20)
const balance02 = await token.balanceOf.call(accounts[ 0 ])
assert.strictEqual(balance02.toNumber(), 9980)
})
/* eslint-disable no-underscore-dangle */
it('events: should fire Transfer event properly', async () => {
const res = await token.transfer(accounts[ 1 ], '2666', { from: accounts[ 0 ], gasPrice: 0 })
const transferLog = res.logs.find(
element => element.event.match('Transfer') &&
element.address.match(token.address)
)
assert.strictEqual(transferLog.args._from, accounts[ 0 ])
// L2 ETH transfer also emits a transfer event
assert.strictEqual(transferLog.args._to, accounts[ 1 ])
assert.strictEqual(transferLog.args._value.toString(), '2666')
})
it('events: should fire Transfer event normally on a zero transfer', async () => {
const res = await token.transfer(accounts[ 1 ], '0', { from: accounts[ 0 ], gasPrice: 0 })
const transferLog = res.logs.find(
element => element.event.match('Transfer') &&
element.address.match(token.address)
)
assert.strictEqual(transferLog.args._from, accounts[ 0 ])
assert.strictEqual(transferLog.args._to, accounts[ 1 ])
assert.strictEqual(transferLog.args._value.toString(), '0')
})
it('events: should fire Approval event properly', async () => {
const res = await token.approve(accounts[ 1 ], '2666', { from: accounts[ 0 ], gasPrice: 0 })
const approvalLog = res.logs.find(element => element.event.match('Approval'))
assert.strictEqual(approvalLog.args._owner, accounts[ 0 ])
assert.strictEqual(approvalLog.args._spender, accounts[ 1 ])
assert.strictEqual(approvalLog.args._value.toString(), '2666')
})
})
const mnemonicPhrase = "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"
const HDWalletProvider = require('@truffle/hdwallet-provider')
module.exports = {
contracts_build_directory: './build-ovm',
networks: {
optimism: {
provider: function () {
return new HDWalletProvider({
mnemonic: {
phrase: mnemonicPhrase
},
providerOrUrl: 'http://127.0.0.1:8545'
})
},
network_id: 420,
host: '127.0.0.1',
port: 8545,
gasPrice: 0,
}
},
compilers: {
solc: {
// Add path to the optimism solc fork
version: '../../node_modules/@eth-optimism/solc',
settings: {
optimizer: {
enabled: true,
runs: 1
},
}
}
}
}
\ No newline at end of file
module.exports = {
contracts_build_directory: './build',
networks: {
ethereum: {
network_id: 31337,
host: '127.0.0.1',
port: 9545,
gasPrice: 0,
},
},
// Configure your compilers
compilers: {
solc: {
version: "0.7.6", // Fetch exact version from solc-bin (default: truffle's version)
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
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