diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e221b7..b1a1b8d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,92 +1,125 @@ -aliases: - - &update-npm - name: Update npm to the latest version - command: sudo npm install -g npm@latest +version: 2.1 - - &save-dependencies-cache - key: v1-dependency-cache-{{ checksum "package.json" }} - paths: - - ./node_modules +executors: + node: + docker: + - image: circleci/node:11.10.0 + working_directory: ~/ERC777 - - &restore-dependencies-cache - keys: - - v1-dependency-cache-{{ checksum "package.json" }} - - v1-dependency-cache- - - - &save-build-cache - key: v1-build-cache-{{ .BuildNum }} - paths: - - ./build - - - &restore-build-cache - keys: - - v1-build-cache-{{ .BuildNum }} - - v1-build-cache- - - &filter-all-tags - tags: - only: /.*/ +commands: + restore-dependencies: + steps: + - restore_cache: + keys: + - v1-dependency-cache-{{ checksum "package.json" }} + - v1-dependency-cache- -defaults: &defaults - docker: - - image: circleci/node:10.6.0 - working_directory: ~/erc777 + restore-build: + steps: + - restore_cache: + keys: + - v1-build-cache-{{ .BuildNum }} + - v1-build-cache- -version: 2 + restore-solc: + steps: + - run: + name: Set write permissions + command: sudo chmod go+w /opt + - restore_cache: + keys: + - v1-solc-cache-{{ .BuildNum }} + - v1-solc-cache- + - run: + name: Update $PATH + command: echo 'export PATH="/opt:$PATH"' >> $BASH_ENV jobs: - install-dependencies: - <<: *defaults + install solc: + executor: node + steps: + - run: + name: Download solc + command: curl -L "https://github.com/ethereum/solidity/releases/download/v0.5.3/solc-static-linux" -o /tmp/solc + - run: + name: Verify solc download + command: echo "be08eb95cb3a1da52e918cf51a0c0397fbe7f0693145eb31835bf2924209f1e0 /tmp/solc" | sha256sum -c - + - run: + name: Make solc executable + command: chmod +x /tmp/solc + - run: + name: Install solc + command: sudo mv /tmp/solc /opt/solc + - save_cache: + key: v1-solc-cache-{{ .BuildNum }} + paths: + - /opt/solc + - run: + name: Solc version + command: /opt/solc --version + + install dependencies: + executor: node steps: - - run: *update-npm - checkout - run: name: Install npm dependencies with npm ci command: npm ci - - save_cache: *save-dependencies-cache + - save_cache: + key: v1-dependency-cache-{{ checksum "package.json" }} + paths: + - ./node_modules + build: - <<: *defaults + executor: node steps: - - run: *update-npm - checkout - - restore_cache: *restore-dependencies-cache + - restore-solc + - restore-dependencies - run: name: Build Solidity command: npm run build - - save_cache: *save-build-cache - lint-solidity: - <<: *defaults + - save_cache: + key: v1-build-cache-{{ .BuildNum }} + paths: + - ./artifacts + + lint solidity: + executor: node steps: - checkout - - restore_cache: *restore-dependencies-cache + - restore-dependencies - run: name: Lint Solidity command: npm run lint:sol - lint-javascript: - <<: *defaults + + lint javascript: + executor: node steps: - - run: *update-npm - checkout - - restore_cache: *restore-dependencies-cache + - restore-dependencies - run: - name: Lint Javascript + name: Lint JavaScript command: npm run lint:js + test: - <<: *defaults + executor: node steps: - - run: *update-npm - checkout - - restore_cache: *restore-dependencies-cache - - restore_cache: *restore-build-cache + - restore-dependencies + - restore-build + - restore-solc - run: name: Test command: npm run test + coverage: - <<: *defaults + executor: node steps: - - run: *update-npm - checkout - - restore_cache: *restore-dependencies-cache - - restore_cache: *restore-build-cache + - restore-dependencies + - restore-build + - restore-solc - run: name: Tests with coverage command: npm run coverage @@ -99,13 +132,17 @@ jobs: - run: name: Enforce minimum coverage threshold command: npx istanbul check-coverage --statements 95 --functions 95 --branches 95 --lines 95 + publish: - <<: *defaults + executor: node steps: - - run: *update-npm - checkout - - restore_cache: *restore-dependencies-cache - - restore_cache: *restore-build-cache + - run: + name: Verify branch & version tag + command: .circleci/deploy-check.sh + - restore-dependencies + - restore-build + - restore-solc - run: name: Set npm auth token command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc @@ -117,37 +154,31 @@ workflows: version: 2 ci_cd: jobs: - - install-dependencies: - filters: *filter-all-tags + - install solc + - install dependencies - build: requires: - - install-dependencies - filters: *filter-all-tags - - lint-solidity: + - install solc + - install dependencies + - lint solidity: requires: - - install-dependencies - filters: *filter-all-tags - - lint-javascript: + - install dependencies + - lint javascript: requires: - - install-dependencies - filters: *filter-all-tags + - install dependencies - test: requires: + - lint solidity + - lint javascript - build - filters: *filter-all-tags - coverage: requires: + - lint solidity + - lint javascript - build - filters: *filter-all-tags - publish: requires: - - build - test - - coverage - - lint-solidity - - lint-javascript filters: - tags: - only: /v[0-9]+(\.[0-9]+)*/ branches: - ignore: /.*/ + only: master diff --git a/.circleci/deploy-check.sh b/.circleci/deploy-check.sh new file mode 100755 index 0000000..8e13b74 --- /dev/null +++ b/.circleci/deploy-check.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# Simple script to make sure the current build is on the production branch and +# has the correct version tag. +# This script is used as a check to prevent from deploying a wrong version to +# the docs. + +DIR=$(dirname "$(realpath $0)") + +say() { + printf "[%s] %s\n" "$0" "$*" >&2 +} + +die() { + say "$*" + exit 100 +} + +check_branch() { + if [[ "$CIRCLE_BRANCH" != "master" ]]; then + die "Error: Invalid branch name '$CIRCLE_BRANCH', expected master" + fi +} + +check_tag() { + NPM_VERSION=$(node -p "require('${DIR}/../package.json').version") + DISTANCE=$(git rev-list --count "v$NPM_VERSION..HEAD") + if [[ "$DISTANCE" != "0" ]]; then + die "Error: Production branch must point to the current version tag ($DISTANCE commits ahead)" + fi +} + +check_branch +check_tag + diff --git a/.eslintrc.js b/.eslintrc.js index 465cde2..7e72a2c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,7 +50,7 @@ module.exports = { "no-debugger": 0, "no-undef": "error", "object-curly-spacing": ["error", "always"], - "max-len": ["error", 80, 2], + "max-len": ["error", 120, 2], "generator-star-spacing": ["error", "before"], "promise/avoid-new": 0, "promise/always-return": 0 diff --git a/.gitignore b/.gitignore index e03281b..033cc9a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build node_modules coverage coverage.json +artifacts diff --git a/contracts/examples/ExampleTokensRecipient.sol b/ExampleTokensRecipient.sol similarity index 100% rename from contracts/examples/ExampleTokensRecipient.sol rename to ExampleTokensRecipient.sol diff --git a/contracts/examples/ExampleTokensSender.sol b/ExampleTokensSender.sol similarity index 100% rename from contracts/examples/ExampleTokensSender.sol rename to ExampleTokensSender.sol diff --git a/contracts/examples/ReferenceToken.sol b/contracts/examples/ReferenceToken.sol index 510952f..072d2a0 100644 --- a/contracts/examples/ReferenceToken.sol +++ b/contracts/examples/ReferenceToken.sol @@ -21,19 +21,17 @@ contract ReferenceToken is ERC777ERC20BaseToken, Ownable { event ERC20Enabled(); event ERC20Disabled(); - address private mBurnOperator; + mapping(address => bool) private mBurnOperators; constructor( string memory _name, string memory _symbol, uint256 _granularity, address[] memory _defaultOperators, - address _burnOperator, uint256 _initialSupply ) public ERC777ERC20BaseToken(_name, _symbol, _granularity, _defaultOperators) { - mBurnOperator = _burnOperator; doMint(msg.sender, _initialSupply, "", ""); } @@ -53,22 +51,12 @@ contract ReferenceToken is ERC777ERC20BaseToken, Ownable { emit ERC20Enabled(); } - /* -- Mint And Burn Functions (not part of the ERC777 standard, only the Events/tokensReceived call are) -- */ - // - /// @notice Generates `_amount` tokens to be assigned to `_tokenHolder` - /// Sample mint function to showcase the use of the `Minted` event and the logic to notify the recipient. - /// @param _tokenHolder The address that will be assigned the new tokens - /// @param _amount The quantity of tokens generated - /// @param _operatorData Data that will be passed to the recipient as a first transfer - function mint( - address _tokenHolder, - uint256 _amount, - bytes calldata _data, - bytes calldata _operatorData - ) - external onlyOwner - { - doMint(_tokenHolder, _amount, _data, _operatorData); + function allowBurn(address burnOperator) external onlyOwner { + mBurnOperators[burnOperator] = true; + } + + function revokeBurn(address burnOperator) external onlyOwner { + mBurnOperators[burnOperator] = true; } /// @notice Burns `_amount` tokens from `msg.sender` @@ -92,13 +80,31 @@ contract ReferenceToken is ERC777ERC20BaseToken, Ownable { bytes calldata _data, bytes calldata _operatorData ) - external + external { - require(msg.sender == mBurnOperator, "Not a burn operator"); + require(mBurnOperators[msg.sender], "Not a burn operator"); require(isOperatorFor(msg.sender, _tokenHolder), "Not an operator"); doBurn(msg.sender, _tokenHolder, _amount, _data, _operatorData); } + /* -- Mint Function (not part of the ERC777 standard, only the Events/tokensReceived call are) -- */ + // + /// @notice Generates `_amount` tokens to be assigned to `_tokenHolder` + /// Sample mint function to showcase the use of the `Minted` event and the logic to notify the recipient. + /// @param _tokenHolder The address that will be assigned the new tokens + /// @param _amount The quantity of tokens generated + /// @param _operatorData Data that will be passed to the recipient as a first transfer + function mint( + address _tokenHolder, + uint256 _amount, + bytes calldata _data, + bytes calldata _operatorData + ) + external onlyOwner + { + doMint(_tokenHolder, _amount, _data, _operatorData); + } + function doMint(address _tokenHolder, uint256 _amount, bytes memory _data, bytes memory _operatorData) private { requireMultiple(_amount); mTotalSupply = mTotalSupply.add(_amount); diff --git a/contracts/test/ContractAccount.sol b/contracts/test/ContractAccount.sol new file mode 100644 index 0000000..72fbff3 --- /dev/null +++ b/contracts/test/ContractAccount.sol @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This code has not been reviewed. + * Do not use or deploy this code before reviewing it personally first. + */ +pragma solidity 0.5.3; + +import { ERC1820Client } from "erc1820/contracts/ERC1820Client.sol"; +import { ERC777TokensSender } from "../ERC777TokensSender.sol"; +import { ERC777TokensRecipient } from "../ERC777TokensRecipient.sol"; +import { ERC777Token } from "../ERC777Token.sol"; + + +contract ContractAccount is ERC1820Client, ERC777TokensSender, ERC777TokensRecipient { + bool private allowTokensToSend; + bool private allowTokensReceived; + + event NotifiedTokensToSend( + address token, + address operator, + address from, + address to, + uint256 amount, + uint256 balanceFrom, + uint256 balanceTo, + bytes data, + bytes operatorData + ); + + event NotifiedTokensReceived( + address token, + address operator, + address from, + address to, + uint256 amount, + uint256 balanceFrom, + uint256 balanceTo, + bytes data, + bytes operatorData + ); + + constructor(bool _allowTokensToSend, bool _allowTokensReceived) public { + setInterfaceImplementation("ERC777TokensSender", address(this)); + setInterfaceImplementation("ERC777TokensRecipient", address(this)); + allowTokensToSend = _allowTokensToSend; + allowTokensReceived = _allowTokensReceived; + } + + function tokensToSend( + address _operator, + address _from, + address _to, + uint256 _amount, + bytes calldata _data, + bytes calldata _operatorData + ) + external + { + require(allowTokensToSend, "Send not allowed"); + emit NotifiedTokensToSend( + msg.sender, + _operator, + _from, + _to, + _amount, + ERC777Token(msg.sender).balanceOf(_from), + ERC777Token(msg.sender).balanceOf(_to), + _data, + _operatorData + ); + } + + function tokensReceived( + address _operator, + address _from, + address _to, + uint256 _amount, + bytes calldata _data, + bytes calldata _operatorData + ) + external + { + require(allowTokensReceived, "Send not allowed"); + emit NotifiedTokensReceived( + msg.sender, + _operator, + _from, + _to, + _amount, + ERC777Token(msg.sender).balanceOf(_from), + ERC777Token(msg.sender).balanceOf(_to), + _data, + _operatorData + ); + } + + function send(address token, address to, uint256 amount, bytes calldata data) external { + ERC777Token(token).send(to, amount, data); + } + + function burn(address token, uint256 amount, bytes calldata data) external { + ERC777Token(token).operatorBurn(address(this), amount, data, ""); + } +} diff --git a/js/artifacts.js b/js/artifacts.js new file mode 100644 index 0000000..901137c --- /dev/null +++ b/js/artifacts.js @@ -0,0 +1,45 @@ +const path = require('path'); +const DEFAULT_ARTIFACTS_PATH = path.resolve(__dirname, '../artifacts/combined.json'); + +module.exports = function loadArtifacts(jsonPath = DEFAULT_ARTIFACTS_PATH) { + const solcJson = require(jsonPath); + const artifacts = Object.assign({}, solcJson, {contracts: {}}); + + Object.keys(solcJson.contracts).forEach(contractPath => { + let path = (contractPath.startsWith('./') ? contractPath.slice(2) : contractPath) + .replace('.sol:', '.').replace(/\//g, '.').split('.'); + let contractName = path.pop(); + + let last = path.reduce((obj, key) => obj[key] = obj[key] || {}, artifacts); + last[contractName] = Object.assign({}, solcJson.contracts[contractPath]); + last[contractName].abi = JSON.parse(last[contractName].abi); + + last[contractName].deploy = async (web3, options = {}) => { + if (!last[contractName].bin || last[contractName].bin === '0x') { + throw new Error(`Missing bytecode for ${contractPath}`); + } + if (!last[contractName].abi) { + throw new Error(`Missing abi for ${contractPath}`); + } + if (!options.from) { + options.from = (await web3.eth.getAccounts())[0]; + } + + let contract = new web3.eth.Contract(last[contractName].abi, null, { from: options.from }); + const gas = await contract.deploy({ data: last[contractName].bin, arguments: options.arguments }) + .estimateGas({ from: options.from }); + if (!options.gas) { + options.gas = gas; + } else if (options.gas < gas) { + console.warn(`Specified gas amount ${options.gas} is lower than the estimated ${gas}.`); + } + return await contract.deploy({ arguments: options.arguments, data: last[contractName].bin }) + .send({ from: options.from, gas: options.gas, gasLimit: options.gasLimit }); + }; + + last[contractName] + .instance = (web3, address, options = {}) => new web3.eth.Contract(last[contractName].abi, address, options); + }); + + return artifacts +}; diff --git a/package.json b/package.json index 44c11ee..1138ead 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,13 @@ }, "scripts": { "lint:sol": "solium --reporter pretty --dir contracts", - "lint:js": "eslint --max-warnings 0 --format eslint-formatter-stylish-verbose test migrations truffle-config.js", + "lint:js": "eslint --max-warnings 0 --format eslint-formatter-stylish-verbose test", "lint": "npm run lint:sol && npm run lint:js", - "build:sol": "truffle compile", - "build": "npm run clean && truffle compile --all", - "test": "scripts/test.sh --network 'test'", - "coverage": "SOLIDITY_COVERAGE=true scripts/test.sh", + "build:sol": "solc 'erc1820=node_modules/erc1820' 'openzeppelin-solidity=node_modules/openzeppelin-solidity' --allow-paths=',$(pwd)/node_modules' --overwrite --optimize --optimize-runs 200 --metadata-literal --output-dir ./artifacts --combined-json abi,asm,bin,devdoc,hashes,interface,metadata,opcodes,srcmap,userdoc ./contracts/**/*.sol", + "build": "npm run clean && npm run build:sol", + "test": "mocha", + "test-old": "scripts/test.sh --network 'test-old'", + "coverage": "echo 'Coverage not supported with Solidity v0.5' && exit 1", "check": "npm run lint && npm run build && npm run test", "clean": "rm -rf ./build ./coverage ./coverage.json" }, diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100755 index a92b578..0000000 --- a/scripts/test.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash - -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Exit script as soon as a command fails. -set -o errexit - -# Executes cleanup function at script exit. -trap cleanup EXIT - -cleanup() { - # Kill the ganache instance that we started (if we started one and if it's still running). - if [ -n "$ganache_pid" ] && ps -p $ganache_pid > /dev/null; then - echo "Stoppping ganache instance (PID $ganache_pid)" - kill -9 $ganache_pid - fi -} - - -if [ "$SOLIDITY_COVERAGE" = true ]; then - ganache_cmd="testrpc-sc" - ganache_port=8555 -else - ganache_cmd="ganache-cli" - ganache_port=8545 -fi - -ganache_running() { - nc -z localhost "$ganache_port" -} - -start_ganache() { - local accounts=( # Account - --account="0x0000000000000000000000000000000000000000000000000000000000000021,1000000000000000000000000" # 0x0.. - --account="0x000000000000000000000000000000000000000000000000000000000000001b,1000000000000000000000000" # 0x1.. - --account="0x00000000000000000000000000000000000000000000000000000000000000bf,1000000000000000000000000" # 0x2.. - --account="0x000000000000000000000000000000000000000000000000000000000000009e,1000000000000000000000000" # 0x3.. - --account="0x0000000000000000000000000000000000000000000000000000000000000013,1000000000000000000000000" # 0x4.. - --account="0x0000000000000000000000000000000000000000000000000000000000000058,1000000000000000000000000" # 0x5.. - --account="0x000000000000000000000000000000000000000000000000000000000000001f,1000000000000000000000000" # 0x6.. - --account="0x0000000000000000000000000000000000000000000000000000000000000023,1000000000000000000000000" # 0x7.. - --account="0x0000000000000000000000000000000000000000000000000000000000000070,1000000000000000000000000" # 0x8.. - --account="0x0000000000000000000000000000000000000000000000000000000000000019,1000000000000000000000000" # 0x9.. -) - npx ${ganache_cmd} --gasLimit 0xfffffffffff --port "$ganache_port" "${accounts[@]}" > /dev/null & - ganache_pid=$! - echo " (PID $ganache_pid)" -} - -if ganache_running; then - echo "Using existing ganache instance" -else - printf "Starting new ganache instance" - start_ganache -fi - -if [ "$SOLIDITY_COVERAGE" = true ]; then - npx solidity-coverage -else - npx truffle test "$@" -fi diff --git a/test/ReferenceToken.test.js b/test/ReferenceToken.test.js index fd6f617..14c57a2 100644 --- a/test/ReferenceToken.test.js +++ b/test/ReferenceToken.test.js @@ -1,121 +1,65 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -const assert = chai.assert; -chai.use(require('chai-as-promised')).should(); -const { URL } = require('url'); -const Web3 = require('web3'); -const ERC1820 = require('erc1820'); -const OldReferenceToken = artifacts.require('ReferenceToken'); -const utils = require('./utils'); -contract('ReferenceToken', function(accounts) { - const provider = new URL(this.web3.currentProvider.host); - provider.protocol = 'ws'; - const web3 = new Web3(provider.toString()); - - accounts = accounts.map(web3.utils.toChecksumAddress); // normalize addresses - - const ReferenceToken = new web3.eth.Contract( - OldReferenceToken.abi, - { data: OldReferenceToken.bytecode } - ); - - let token = { - name: 'ReferenceToken', - symbol: 'XRT', - granularity: '0.01', - defaultOperators: [accounts[6], accounts[7]], - burnOperator: accounts[8], - defaultBalance: 0, - initialSupply: 10, - }; - - const deployContract = ReferenceToken - .deploy({ arguments: [ - token.name, - token.symbol, - web3.utils.toWei(token.granularity), - token.defaultOperators, - token.burnOperator, - web3.utils.toWei(token.initialSupply.toString()), - ] }); +const artifacts = require('../js/artifacts')(); +const suites = require('./suites'); +describe('ReferenceToken', function() { beforeEach(async function() { - let erc1820Registry = await ERC1820.deploy(web3, accounts[0]); - assert.ok(erc1820Registry.options.address); - - // Use Web3.js 1.0 - const estimateGas = await deployContract.estimateGas(); - token.contract = await deployContract - .send({ from: accounts[0], gasLimit: estimateGas }); - assert.ok(token.contract.options.address); - - token.disableERC20 = async function() { - await token.contract.methods - .disableERC20() - .send({ gas: 300000, from: accounts[0] }); + this.erc20 = true; + + this.token = { + name: 'ReferenceToken', + symbol: 'XRT', + granularity: '0.01', + defaultOperators: [this.accounts[6], this.accounts[7]], + initialSupply: '10', }; - token.mintForAccount = async function(account, amount, operator) { - const mintTx = token.contract.methods - .mint(account, web3.utils.toWei(amount), '0xbeef', '0xcafe'); - const gas = await mintTx.estimateGas(); - await mintTx.send({ gas: gas, from: operator }); + this.mint = { + holder: this.accounts[3], + amount: '5.3', + operator: this.accounts[0], }; - }); - - after(async function() { await web3.currentProvider.connection.close(); }); - describe('Creation', function() { - it('should not deploy the token with a granularity of 0', async function() { - const estimateGas = await deployContract.estimateGas(); - await ReferenceToken - .deploy({ arguments: [ - token.name, - token.symbol, - web3.utils.toWei('0'), - token.defaultOperators, - token.burnOperator, - web3.utils.toWei(token.initialSupply.toString()), - ] }) - .send({ from: accounts[0], gasLimit: estimateGas }) - .should.be.rejectedWith('revert'); - }); - }); - - require('./utils/attributes').test(web3, accounts, token); - require('./utils/mint').test(web3, accounts, token); - require('./utils/burn').test(web3, accounts, token); - require('./utils/send').test(web3, accounts, token); - require('./utils/operator').test(web3, accounts, token); - require('./utils/operatorBurn').test(web3, accounts, token); - require('./utils/operatorSend').test(web3, accounts, token); - require('./utils/tokensSender').test(web3, accounts, token); - require('./utils/tokensRecipient').test(web3, accounts, token); - require('./utils/erc20Compatibility').test(web3, accounts, token); - - describe('ERC20 Disable', function() { - it('should disable ERC20 compatibility', async function() { - let erc1820Registry = utils.getERC1820Registry(web3); - let erc20Hash = web3.utils.keccak256('ERC20Token'); - let erc20Addr = await erc1820Registry.methods - .getInterfaceImplementer(token.contract.options.address, erc20Hash) - .call(); - - assert.strictEqual(erc20Addr, token.contract.options.address); - - await token.disableERC20(); + this.mint.method = async function mint(_parameters, _options) { + const parameters = Object.assign({ + holder: this.mint.holder, + amount: this.mint.amount, + data: '0x', + operatorData: '0x', + }, _parameters); + const options = Object.assign({ gas: 300000, from: this.mint.operator }, _options); + + return this.token.contract.methods + .mint( + parameters.holder, + this.web3.utils.toWei(parameters.amount, 'ether'), + parameters.data, + parameters.operatorData, + ).send(options); + }; - await utils.getBlock(web3); - erc20Addr = await erc1820Registry.methods - .getInterfaceImplementer(token.contract.options.address, erc20Hash) - .call(); + this.burn = { + operator: this.accounts[8], + self: this.accounts[0], + holder: this.accounts[5], + }; - assert.strictEqual(erc20Addr, utils.zeroAddress); - }); + this.token.contract = await artifacts.contracts.examples.ReferenceToken.ReferenceToken.deploy( + this.web3, { + from: this.accounts[0], + arguments: [ + this.token.name, + this.token.symbol, + this.web3.utils.toWei(this.token.granularity, 'ether'), + this.token.defaultOperators, + this.web3.utils.toWei(this.test.ctx.token.initialSupply, 'ether'), + ], + } + ); + await this.token.contract.methods.allowBurn(this.burn.operator).send({ from: this.accounts[0] }); }); - - require('./utils/erc20Disabled').test(web3, accounts, token); + suites.apply(this); }); diff --git a/test/hooks.js b/test/hooks.js new file mode 100644 index 0000000..94b2109 --- /dev/null +++ b/test/hooks.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const ERC1820 = require('erc1820'); +const utils = require('./utils'); +const host = '127.0.0.1'; +const port = 8546; + +before(function() { + this.skipTest = () => { + this.ganacheServer.close(); // server must be closed now as the afterEach hook will not be triggered. + this.skip(); + }; +}); + +beforeEach(async function() { + const { ganacheServer, web3 } = utils.init(host, port); + this.web3 = web3; + this.ganacheServer = ganacheServer; + this.accounts = await utils.getAccounts(web3); + this.erc1820Registry = await ERC1820.deploy(web3, this.accounts[0]); +}); + +afterEach(async function() { + this.ganacheServer.close(); +}); diff --git a/test/suites/attributes.js b/test/suites/attributes.js new file mode 100644 index 0000000..3638750 --- /dev/null +++ b/test/suites/attributes.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const chai = require('chai'); +const assert = chai.assert; +chai.use(require('chai-as-promised')).should(); +const utils = require('../utils'); + +module.exports = function suite() { + it('ERC1820 registration', async function() { + const address = await this.test.ctx.erc1820Registry.methods.getInterfaceImplementer( + this.test.ctx.token.contract.options.address, + '0xac7fbab5f54a3ca8194167523c6753bfeb96a445279294b6125b68cce2177054' + ).call(); + + assert.equal(this.test.ctx.token.contract.options.address, address); + }); + + xit('ERC1820 registration (ERC20)'); + + it('name', async function() { + const name = await this.test.ctx.token.contract.methods.name().call(); + assert.strictEqual(name, this.test.ctx.token.name); + }); + + it('symbol', async function() { + const symbol = await this.test.ctx.token.contract.methods.symbol().call(); + assert.strictEqual(symbol, this.test.ctx.token.symbol); + }); + + it('granularity', + async function() { + const granularity = ( + await this.test.ctx.token.contract.methods.granularity().call()).toString(); + assert.strictEqual( + this.test.ctx.web3.utils.fromWei(granularity), + this.test.ctx.token.granularity + ); + } + ); + + it('total supply', async function() { + assert.strictEqual( + await utils.getTotalSupply(this.test.ctx.web3, this.test.ctx.token), + this.test.ctx.token.initialSupply); + }); +}; diff --git a/test/suites/burn.js b/test/suites/burn.js new file mode 100644 index 0000000..d3a8515 --- /dev/null +++ b/test/suites/burn.js @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const chai = require('chai'); +const assert = chai.assert; +chai.use(require('chai-as-promised')).should(); + +module.exports = function suite() { + const sendInputs = [ + { amount: '3.6', data: null, reason: 'without data' }, + { amount: '3.6', data: '0xcafe', reason: 'with data' }, + { amount: '0', data: null, reason: '0 tokens' }, + ]; + + for (let input of sendInputs) { + it(`burn (${input.reason})`, async function() { + const expectedBalanceFrom = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.burn.self).call() + ); + await this.test.ctx.mint.method.apply(this, [{ amount: input.amount, holder: this.test.ctx.burn.self }]); + + const totalSupplyBefore = await this.test.ctx.token.contract.methods.totalSupply().call(); + + const tx = await this.test.ctx.token.contract.methods + .burn(this.test.ctx.web3.utils.toWei(input.amount, 'ether'), input.data === null ? '0x' : input.data) + .send({ from: this.test.ctx.burn.self }); + + const totalSupplyAfter = await this.test.ctx.token.contract.methods.totalSupply().call(); + const balanceFromAfter = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.burn.self).call() + ); + + assert.nestedProperty(tx, 'events.Burned'); + assert.deepInclude(tx.events.Burned, { event: 'Burned' }); + assert.deepInclude(tx.events.Burned, { + returnValues: { + '0': this.test.ctx.burn.self, + '1': this.test.ctx.burn.self, + '2': this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + '3': input.data, + '4': null, + operator: this.test.ctx.burn.self, + from: this.test.ctx.burn.self, + amount: this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + data: input.data, + operatorData: null, + }, + }); + + assert.equal(expectedBalanceFrom, balanceFromAfter); + + assert.equal( + '0', + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf('0x0000000000000000000000000000000000000000').call(), + 'ether' + ) + ); + + assert.equal( + this.test.ctx.web3.utils.fromWei((totalSupplyBefore - totalSupplyAfter).toString(), 'ether'), + input.amount + ); + }); + } + xit('burn (ERC20)'); + + const notSendInput = [ + { mintAmount: '5', amount: '-4.7', data: '0xbeef', reason: 'negative amount' }, + { mintAmount: '5', amount: '63.8', data: '0xbeef', reason: 'insufficient funds' }, + { mintAmount: '5', amount: '2.789', data: '0xbeef', reason: 'granularity' }, + ]; + + for (let input of notSendInput) { + it(`prevent burn (${input.reason})`, async function() { + await this.test.ctx.mint.method.apply(this, [{ amount: input.mintAmount, holder: this.test.ctx.burn.self }]); + + const totalSupplyBefore = await this.test.ctx.token.contract.methods.totalSupply().call(); + const balanceFromBefore = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.burn.self).call() + ); + + await this.test.ctx.token.contract.methods + .burn(this.test.ctx.web3.utils.toWei(input.amount, 'ether'), input.data === null ? '0x' : input.data) + .send({ from: this.test.ctx.burn.self }) + .should.be.rejectedWith('revert'); + + const totalSupplyAfter = await this.test.ctx.token.contract.methods.totalSupply().call(); + const balanceFromAfter = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.burn.self).call() + ); + + assert.equal(balanceFromBefore, balanceFromAfter); + + assert.equal( + '0', + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf('0x0000000000000000000000000000000000000000').call()) + ); + + assert.equal(totalSupplyBefore, totalSupplyAfter); + }); + } +}; diff --git a/test/suites/index.js b/test/suites/index.js new file mode 100644 index 0000000..15eb899 --- /dev/null +++ b/test/suites/index.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const attributes = require('./attributes'); +const operators = require('./operators'); +const mint = require('./mint'); +const send = require('./send'); +const operatorSend = require('./operatorSend'); +const burn = require('./burn'); +const operatorBurn = require('./operatorBurn'); +const tokensToSend = require('./tokensToSend'); +const tokensReceived = require('./tokensReceived'); + +module.exports = function suites() { + describe('Attributes', attributes.bind(this)); + describe('Operators', operators.bind(this)); + describe('Mint', mint.bind(this)); + describe('Send', send.bind(this)); + describe('OperatorSend', operatorSend.bind(this)); + describe('Burn', burn.bind(this)); + describe('OperatorBurn', operatorBurn.bind(this)); + describe('TokensToSend Hook', tokensToSend.bind(this)); + describe('TokensReceived Hook', tokensReceived.bind(this)); +}; diff --git a/test/suites/mint.js b/test/suites/mint.js new file mode 100644 index 0000000..5fcd06c --- /dev/null +++ b/test/suites/mint.js @@ -0,0 +1,158 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const chai = require('chai'); +const assert = chai.assert; +chai.use(require('chai-as-promised')).should(); + +module.exports = function suite() { + it('mint', async function() { + const balanceBefore = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.mint.holder).call() + ); + + const tx = await this.test.ctx.mint.method.apply(this); + + assert.nestedProperty(tx, 'events.Minted'); + assert.deepInclude(tx.events.Minted, { event: 'Minted' }); + assert.deepInclude(tx.events.Minted, { + returnValues: { + '0': this.test.ctx.mint.operator, + '1': this.test.ctx.mint.holder, + '2': this.test.ctx.web3.utils.toWei(this.test.ctx.mint.amount, 'ether'), + '3': null, + '4': null, + operator: this.test.ctx.mint.operator, + to: this.test.ctx.mint.holder, + amount: this.test.ctx.web3.utils.toWei(this.test.ctx.mint.amount, 'ether'), + data: null, + operatorData: null, + }, + }); + + const expectedBalance = [balanceBefore, this.test.ctx.mint.amount] + .map(val => parseInt(this.test.ctx.web3.utils.toWei(val, 'ether'))) + .reduce((acc, current) => acc + current, 0) + .toString(); + + assert.equal( + this.test.ctx.web3.utils.fromWei(expectedBalance, 'ether'), + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.mint.holder).call()) + ); + }); + + it('mint 0 tokens', async function() { + const balanceBefore = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.mint.holder).call() + ); + + const tx = await this.test.ctx.mint.method.apply(this, [{ amount: '0' }]); + + assert.nestedProperty(tx, 'events.Minted'); + assert.deepInclude(tx.events.Minted, { event: 'Minted' }); + assert.deepInclude(tx.events.Minted, { + returnValues: { + '0': this.test.ctx.mint.operator, + '1': this.test.ctx.mint.holder, + '2': this.test.ctx.web3.utils.toWei('0', 'ether'), + '3': null, + '4': null, + operator: this.test.ctx.mint.operator, + to: this.test.ctx.mint.holder, + amount: this.test.ctx.web3.utils.toWei('0', 'ether'), + data: null, + operatorData: null, + }, + }); + + assert.equal( + balanceBefore, + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.mint.holder).call()) + ); + }); + + xit('mint (ERC20)', async function() { + if (!this.test.ctx.erc20) { this.test.ctx.skip.apply(this); } + + const balanceBefore = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.mint.holder).call() + ); + + const tx = await this.test.ctx.mint.method.apply(this); + + assert.nestedProperty(tx, 'events.Minted'); + assert.deepInclude(tx.events.Minted, { event: 'Minted' }); + assert.deepInclude(tx.events.Minted, { + returnValues: { + '0': this.test.ctx.mint.operator, + '1': this.test.ctx.mint.holder, + '2': this.test.ctx.web3.utils.toWei(this.test.ctx.mint.amount, 'ether'), + '3': null, + '4': null, + operator: this.test.ctx.mint.operator, + to: this.test.ctx.mint.holder, + amount: this.test.ctx.web3.utils.toWei(this.test.ctx.mint.amount, 'ether'), + data: null, + operatorData: null, + }, + }); + + let transferPresent = false; + try { + assert.nestedProperty(tx, 'events.Transfer', 'No ERC20 Transfer event for minting'); + assert.deepInclude(tx.events.Transfer, { event: 'Transfer' }, 'No ERC20 Transfer event for minting'); + transferPresent = true; + } catch (err) { + console.warn(err.message); + } finally { + if (transferPresent) { + assert.deepInclude(tx.events.Transfer, { + returnValues: { + '0': '0x0000000000000000000000000000000000000000', + '1': this.test.ctx.mint.holder, + '2': this.test.ctx.web3.utils.toWei(this.test.ctx.mint.amount, 'ether'), + _from: this.test.ctx.mint.operator, + _to: this.test.ctx.mint.holder, + _value: this.test.ctx.web3.utils.toWei(this.test.ctx.mint.amount, 'ether'), + }, + }); + } + } + + const expectedBalance = [balanceBefore, this.test.ctx.mint.amount] + .map(val => parseInt(this.test.ctx.web3.utils.toWei(val, 'ether'))) + .reduce((acc, current) => acc + current, 0) + .toString(); + + assert.equal( + this.test.ctx.web3.utils.fromWei(expectedBalance, 'ether'), + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.mint.holder).call()) + ); + }); + + const inputs = [ + { parameters: { amount: '-3.2' }, reason: '(negative amount)' }, + { parameters: { amount: '0.007' }, reason: '(less than granularity)' }, + { parameters: { holder: '0x0000000000000000000000000000000000000000' }, reason: '(recipient is 0x0)' }, + ]; + + for (let input of inputs) { + it(`not mint ${input.reason}`, async function() { + const balanceBefore = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.mint.holder).call() + ); + + await this.test.ctx.mint.method.apply(this, [input.parameters]).should.be.rejectedWith('revert'); + + assert.equal( + balanceBefore, + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(this.test.ctx.mint.holder).call()) + ); + }); + } +}; diff --git a/test/suites/operatorBurn.js b/test/suites/operatorBurn.js new file mode 100644 index 0000000..2a09d85 --- /dev/null +++ b/test/suites/operatorBurn.js @@ -0,0 +1,190 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const chai = require('chai'); +const assert = chai.assert; +chai.use(require('chai-as-promised')).should(); + +module.exports = function suite() { + const sendInputs = [ + { + from: function() { return this.test.ctx.burn.operator; }, + amount: '3.6', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'own operator', + }, { + from: function() { return this.test.ctx.accounts[7]; }, + amount: '3.6', + data: null, + operatorData: null, + reason: 'without data, without operatorData', + }, { + from: function() { return this.test.ctx.accounts[7]; }, + amount: '3.6', + data: '0xcafe', + operatorData: null, + reason: 'with data, without operatorData', + }, { + from: function() { return this.test.ctx.accounts[7]; }, + amount: '3.6', + data: null, + operatorData: '0xbeef', + reason: 'without data, with operatorData', + }, { + from: function() { return this.test.ctx.accounts[7]; }, + amount: '3.6', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'with data, with operatorData', + }, { + from: function() { return this.test.ctx.accounts[7]; }, + amount: '0', + data: '0xcafe', + operatorData: '0xbeef', + reason: '0 tokens', + }, + ]; + + for (let input of sendInputs) { + it(`operatorBurn (${input.reason})`, async function() { + const from = input.from.apply(this); + + if (input.reason !== 'own operator') { + await this.test.ctx.token.contract.methods.authorizeOperator(this.test.ctx.burn.operator).send({ from: from }); + } + + const expectedBalanceFrom = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(from).call(), + 'ether' + ); + await this.test.ctx.mint.method.apply(this, [{ amount: input.amount, holder: from }]); + + const totalSupplyBefore = await this.test.ctx.token.contract.methods.totalSupply().call(); + + const tx = await this.test.ctx.token.contract.methods + .operatorBurn( + from, + this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + input.data === null ? '0x' : input.data, + input.operatorData === null ? '0x' : input.operatorData + ) + .send({ from: this.test.ctx.burn.operator }); + + const totalSupplyAfter = await this.test.ctx.token.contract.methods.totalSupply().call(); + const balanceFromAfter = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(from).call(), + 'ether' + ); + + assert.nestedProperty(tx, 'events.Burned'); + assert.deepInclude(tx.events.Burned, { event: 'Burned' }); + assert.deepInclude(tx.events.Burned, { + returnValues: { + '0': this.test.ctx.burn.operator, + '1': from, + '2': this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + '3': input.data, + '4': input.operatorData, + operator: this.test.ctx.burn.operator, + from: from, + amount: this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + data: input.data, + operatorData: input.operatorData, + }, + }); + + assert.equal(expectedBalanceFrom, balanceFromAfter); + + assert.equal( + '0', + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf('0x0000000000000000000000000000000000000000').call(), + 'ether' + ) + ); + + assert.equal( + this.test.ctx.web3.utils.fromWei((totalSupplyBefore - totalSupplyAfter).toString(), 'ether'), + input.amount + ); + }); + } + + xit('operatorBurn (ERC20)'); + const notSendInput = [ + { + from: 7, + mintAmount: '5', + amount: '-4.7', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'negative amount', + }, { + from: 7, + mintAmount: '5', + amount: '6.8', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'insufficient funds', + }, { + from: 7, + mintAmount: '5', + amount: '2.789', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'granularity', + }, { + from: 7, + mintAmount: '5', + amount: '2.7', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'unauthorized operator', + }, + ]; + + for (let input of notSendInput) { + it(`prevent operatorBurn (${input.reason})`, async function() { + input.from = this.test.ctx.accounts[input.from]; + await this.test.ctx.mint.method.apply(this, [{ amount: input.mintAmount, holder: input.from }]); + if (input.reason !== 'unauthorized operator') { + await this.test.ctx.token.contract.methods + .authorizeOperator(this.test.ctx.burn.operator).send({ from: input.from }); + } + + const totalSupplyBefore = await this.test.ctx.token.contract.methods.totalSupply().call(); + const balanceFromBefore = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.from).call(), 'ether' + ); + + await this.test.ctx.token.contract.methods + .operatorBurn( + input.from, + this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + input.data === null ? '0x' : input.data, + input.operatorData === null ? '0x' : input.operatorData + ) + .send({ from: this.test.ctx.burn.operator }) + .should.be.rejectedWith('revert'); + + const totalSupplyAfter = await this.test.ctx.token.contract.methods.totalSupply().call(); + const balanceFromAfter = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.from).call(), 'ether' + ); + + assert.equal(balanceFromBefore, balanceFromAfter); + + assert.equal( + '0', + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf('0x0000000000000000000000000000000000000000').call(), + 'ether' + ) + ); + + assert.equal(totalSupplyBefore, totalSupplyAfter); + }); + } +}; diff --git a/test/suites/operatorSend.js b/test/suites/operatorSend.js new file mode 100644 index 0000000..5f53884 --- /dev/null +++ b/test/suites/operatorSend.js @@ -0,0 +1,232 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const chai = require('chai'); +const assert = chai.assert; +chai.use(require('chai-as-promised')).should(); + +module.exports = function suite() { + const sendInputs = [ + { + operator: function() { return this.test.ctx.accounts[7]; }, + from: function() { return this.test.ctx.accounts[7]; }, + to: function() { return this.test.ctx.accounts[8]; }, + amount: '3.6', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'own operator', + }, { + operator: function() { return this.test.ctx.accounts[6]; }, + from: function() { return this.test.ctx.accounts[7]; }, + to: function() { return this.test.ctx.accounts[8]; }, + amount: '3.6', + data: null, + operatorData: null, + reason: 'without data, without operatorData', + }, { + operator: function() { return this.test.ctx.accounts[6]; }, + from: function() { return this.test.ctx.accounts[7]; }, + to: function() { return this.test.ctx.accounts[8]; }, + amount: '3.6', + data: '0xcafe', + operatorData: null, + reason: 'with data, without operatorData', + }, { + operator: function() { return this.test.ctx.accounts[6]; }, + from: function() { return this.test.ctx.accounts[7]; }, + to: function() { return this.test.ctx.accounts[8]; }, + amount: '3.6', + data: null, + operatorData: '0xbeef', + reason: 'without data, with operatorData', + }, { + operator: function() { return this.test.ctx.accounts[6]; }, + from: function() { return this.test.ctx.accounts[7]; }, + to: function() { return this.test.ctx.accounts[8]; }, + amount: '3.6', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'with data, with operatorData', + }, { + operator: function() { return this.test.ctx.token.defaultOperators[0]; }, + from: function() { return this.test.ctx.accounts[7]; }, + to: function() { return this.test.ctx.accounts[8]; }, + amount: '3.6', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'default operator', + }, { + operator: function() { return this.test.ctx.accounts[6]; }, + from: function() { return this.test.ctx.accounts[7]; }, + to: function() { return this.test.ctx.accounts[8]; }, + amount: '0', + data: '0xcafe', + operatorData: '0xbeef', + reason: '0 tokens', + }, + ]; + + for (let input of sendInputs) { + it(`operatorSend (${input.reason})`, async function() { + if (input.reason === 'default operator' && this.test.ctx.token.defaultOperators.length === 0) { + this.test.ctx.skipTest(); + } + + const operator = input.operator.apply(this); + const from = input.from.apply(this); + const to = input.to.apply(this); + + if (input.reason !== 'default operator' && input.reason !== 'own operator') { + await this.test.ctx.token.contract.methods.authorizeOperator(operator).send({ from: from }); + } + + const expectedBalanceFrom = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(from).call(), 'ether' + ); + await this.test.ctx.mint.method.apply(this, [{ amount: input.amount, holder: from }]); + + const balanceTo = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(to).call(), 'ether' + ); + + const tx = await this.test.ctx.token.contract.methods + .operatorSend( + from, + to, + this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + input.data === null ? '0x' : input.data, + input.operatorData === null ? '0x' : input.operatorData + ) + .send({ from: operator }); + + assert.nestedProperty(tx, 'events.Sent'); + assert.deepInclude(tx.events.Sent, { event: 'Sent' }); + assert.deepInclude(tx.events.Sent, { + returnValues: { + '0': operator, + '1': from, + '2': to, + '3': this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + '4': input.data, + '5': input.operatorData, + operator: operator, + from: from, + to: to, + amount: this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + data: input.data, + operatorData: input.operatorData, + }, + }); + + const expectedBalanceTo = [balanceTo, input.amount] + .map(val => parseInt(this.test.ctx.web3.utils.toWei(val, 'ether'))) + .reduce((acc, current) => acc + current, 0) + .toString(); + + assert.equal( + this.test.ctx.web3.utils.fromWei(expectedBalanceFrom, 'ether'), + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(from).call(), 'ether') + ); + + assert.equal( + this.test.ctx.web3.utils.fromWei(expectedBalanceTo, 'ether'), + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(to).call(), 'ether') + ); + }); + } + xit('operatorSend (ERC20)'); + + const notSendInput = [ + { + operator: 9, + from: 7, + to: 8, + mintAmount: '5', + amount: '-4.7', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'negative amount', + }, { + operator: 9, + from: 7, + to: 8, + mintAmount: '5', + amount: '6.8', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'insufficient funds', + }, { + operator: 9, + from: 7, + to: 8, + mintAmount: '5', + amount: '2.789', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'granularity', + }, { + operator: 9, + from: 7, + to: 8, + mintAmount: '5', + amount: '2.7', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'unauthorized operator', + }, { + operator: 9, + from: 7, + to: null, + mintAmount: '5', + amount: '2.4', + data: '0xcafe', + operatorData: '0xbeef', + reason: 'recipient is 0x0', + }, + ]; + + for (let input of notSendInput) { + it(`prevent send (${input.reason})`, async function() { + input.operator = this.test.ctx.accounts[input.operator]; + input.from = this.test.ctx.accounts[input.from]; + input.to = input.to === null ? '0x0000000000000000000000000000000000000000' : this.test.ctx.accounts[input.to]; + + await this.test.ctx.mint.method.apply(this, [{ amount: input.mintAmount, holder: input.from }]); + if (input.reason !== 'unauthorized operator') { + await this.test.ctx.token.contract.methods.authorizeOperator(input.operator).send({ from: input.from }); + } + + const balanceFrom = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.from).call(), 'ether' + ); + const balanceTo = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.to).call(), 'ether' + ); + + await this.test.ctx.token.contract.methods + .operatorSend( + input.from, + input.to, + this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + input.data === null ? '0x' : input.data, + input.operatorData === null ? '0x' : input.operatorData + ) + .send({ from: input.operator }) + .should.be.rejectedWith('revert'); + + assert.equal( + balanceFrom, + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.from).call(), 'ether') + ); + assert.equal( + balanceTo, + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.to).call(), 'ether') + ); + }); + } +}; diff --git a/test/suites/operators.js b/test/suites/operators.js new file mode 100644 index 0000000..9f59b33 --- /dev/null +++ b/test/suites/operators.js @@ -0,0 +1,176 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const chai = require('chai'); +const assert = chai.assert; +chai.use(require('chai-as-promised')).should(); + +module.exports = function suite() { + it('operator for itself', async function() { + assert.isTrue(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.accounts[4], this.test.ctx.accounts[4]).call() + ); + }); + + it('cannot revoke itself', async function() { + await this.test.ctx.token.contract.methods + .revokeOperator(this.test.ctx.accounts[4]) + .send({ from: this.test.ctx.accounts[4] }) + .should.be.rejectedWith('revert'); + }); + + it('authorize operator', async function() { + assert.isFalse(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.accounts[4], this.test.ctx.accounts[5]).call() + ); + + const tx = await this.test.ctx.token.contract.methods + .authorizeOperator(this.test.ctx.accounts[4]) + .send({ from: this.test.ctx.accounts[5] }); + + assert.nestedProperty(tx, 'events.AuthorizedOperator'); + assert.deepInclude(tx.events.AuthorizedOperator, { event: 'AuthorizedOperator' }); + assert.deepInclude(tx.events.AuthorizedOperator, { + returnValues: { + '0': this.test.ctx.accounts[4], + '1': this.test.ctx.accounts[5], + operator: this.test.ctx.accounts[4], + tokenHolder: this.test.ctx.accounts[5], + }, + }); + + assert.isTrue(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.accounts[4], this.test.ctx.accounts[5]).call() + ); + }); + + it('revoke operator', async function() { + await this.test.ctx.token.contract.methods + .authorizeOperator(this.test.ctx.accounts[4]) + .send({ from: this.test.ctx.accounts[5] }); + + assert.isTrue(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.accounts[4], this.test.ctx.accounts[5]).call() + ); + + const tx = await this.test.ctx.token.contract.methods + .revokeOperator(this.test.ctx.accounts[4]) + .send({ from: this.test.ctx.accounts[5] }); + + assert.nestedProperty(tx, 'events.RevokedOperator'); + assert.deepInclude(tx.events.RevokedOperator, { event: 'RevokedOperator' }); + assert.deepInclude(tx.events.RevokedOperator, { + returnValues: { + '0': this.test.ctx.accounts[4], + '1': this.test.ctx.accounts[5], + operator: this.test.ctx.accounts[4], + tokenHolder: this.test.ctx.accounts[5], + }, + }); + + assert.isFalse(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.accounts[4], this.test.ctx.accounts[5]).call() + ); + }); + + it('revoke a non-operator', async function() { + assert.isFalse(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.accounts[4], this.test.ctx.accounts[5]).call() + ); + + const tx = await this.test.ctx.token.contract.methods + .revokeOperator(this.test.ctx.accounts[4]) + .send({ from: this.test.ctx.accounts[5] }); + + assert.nestedProperty(tx, 'events.RevokedOperator'); + assert.deepInclude(tx.events.RevokedOperator, { event: 'RevokedOperator' }); + assert.deepInclude(tx.events.RevokedOperator, { + returnValues: { + '0': this.test.ctx.accounts[4], + '1': this.test.ctx.accounts[5], + operator: this.test.ctx.accounts[4], + tokenHolder: this.test.ctx.accounts[5], + }, + }); + + assert.isFalse(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.accounts[4], this.test.ctx.accounts[5]).call() + ); + }); + + it('default operators', async function() { + assert.sameMembers( + await this.test.ctx.token.contract.methods.defaultOperators().call(), + this.test.ctx.token.defaultOperators + ); + + for (let operator of this.test.ctx.token.defaultOperators) { + assert.isTrue(await this.test.ctx.token.contract.methods.isOperatorFor( + operator, this.test.ctx.accounts[9]).call() + ); + } + }); + + it('revoke default operator', async function() { + if (this.test.ctx.token.defaultOperators.length === 0) { this.test.ctx.skipTest(); } + + assert.isTrue(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.token.defaultOperators[0], this.test.ctx.accounts[5]).call() + ); + + const tx = await this.test.ctx.token.contract.methods + .revokeOperator(this.test.ctx.token.defaultOperators[0]) + .send({ from: this.test.ctx.accounts[5] }); + + assert.nestedProperty(tx, 'events.RevokedOperator'); + assert.deepInclude(tx.events.RevokedOperator, { event: 'RevokedOperator' }); + assert.deepInclude(tx.events.RevokedOperator, { + returnValues: { + '0': this.test.ctx.token.defaultOperators[0], + '1': this.test.ctx.accounts[5], + operator: this.test.ctx.token.defaultOperators[0], + tokenHolder: this.test.ctx.accounts[5], + }, + }); + + assert.isFalse(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.token.defaultOperators[0], this.test.ctx.accounts[5]).call() + ); + }); + + it('reinstate default operator', async function() { + if (this.test.ctx.token.defaultOperators.length === 0) { this.test.ctx.skipTest(); } + + assert.isTrue(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.token.defaultOperators[0], this.test.ctx.accounts[5]).call() + ); + + await this.test.ctx.token.contract.methods + .revokeOperator(this.test.ctx.token.defaultOperators[0]) + .send({ from: this.test.ctx.accounts[5] }); + + assert.isFalse(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.token.defaultOperators[0], this.test.ctx.accounts[5]).call() + ); + + const tx = await this.test.ctx.token.contract.methods + .authorizeOperator(this.test.ctx.token.defaultOperators[0]) + .send({ from: this.test.ctx.accounts[5] }); + + assert.nestedProperty(tx, 'events.AuthorizedOperator'); + assert.deepInclude(tx.events.AuthorizedOperator, { event: 'AuthorizedOperator' }); + assert.deepInclude(tx.events.AuthorizedOperator, { + returnValues: { + '0': this.test.ctx.token.defaultOperators[0], + '1': this.test.ctx.accounts[5], + operator: this.test.ctx.token.defaultOperators[0], + tokenHolder: this.test.ctx.accounts[5], + }, + }); + + assert.isTrue(await this.test.ctx.token.contract.methods.isOperatorFor( + this.test.ctx.token.defaultOperators[0], this.test.ctx.accounts[5]).call() + ); + }); +}; diff --git a/test/suites/send.js b/test/suites/send.js new file mode 100644 index 0000000..e151f05 --- /dev/null +++ b/test/suites/send.js @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const chai = require('chai'); +const assert = chai.assert; +chai.use(require('chai-as-promised')).should(); + +module.exports = function suite() { + const sendInputs = [ + { from: 7, to: 8, amount: '3.6', data: null, reason: 'without data' }, + { from: 7, to: 8, amount: '0', data: '0xcafe', reason: 'with data' }, + { from: 7, to: 8, amount: '0', data: null, reason: '0 tokens' }, + ]; + + for (let input of sendInputs) { + it(`send (${input.reason})`, async function() { + input.from = this.test.ctx.accounts[input.from]; + input.to = this.test.ctx.accounts[input.to]; + const expectedBalanceFrom = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.from).call(), 'ether' + ); + await this.test.ctx.mint.method.apply(this, [{ amount: input.amount, holder: input.from }]); + + const balanceTo = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.to).call(), 'ether' + ); + + const tx = await this.test.ctx.token.contract.methods + .send(input.to, this.test.ctx.web3.utils.toWei(input.amount, 'ether'), input.data === null ? '0x' : input.data) + .send({ from: input.from }); + + assert.nestedProperty(tx, 'events.Sent'); + assert.deepInclude(tx.events.Sent, { event: 'Sent' }); + assert.deepInclude(tx.events.Sent, { + returnValues: { + '0': input.from, + '1': input.from, + '2': input.to, + '3': this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + '4': input.data, + '5': null, + operator: input.from, + from: input.from, + to: input.to, + amount: this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + data: input.data, + operatorData: null, + }, + }); + + const expectedBalanceTo = [balanceTo, input.amount] + .map(val => parseInt(this.test.ctx.web3.utils.toWei(val, 'ether'))) + .reduce((acc, current) => acc + current, 0) + .toString(); + + assert.equal( + this.test.ctx.web3.utils.fromWei(expectedBalanceFrom, 'ether'), + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.from).call(), 'ether') + ); + + assert.equal( + this.test.ctx.web3.utils.fromWei(expectedBalanceTo, 'ether'), + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.to).call(), 'ether') + ); + }); + } + + const notSendInput = [ + { from: 7, to: 8, mintAmount: '5', amount: '-4.7', data: null, reason: 'negative amount' }, + { from: 7, to: 8, mintAmount: '5', amount: '6.8', data: null, reason: 'insufficient funds' }, + { from: 7, to: 8, mintAmount: '5', amount: '2.789', data: null, reason: 'granularity' }, + { from: 7, to: null, mintAmount: '5', amount: '2.4', data: null, reason: 'recipient is 0x0' }, + ]; + + for (let input of notSendInput) { + it(`prevent send (${input.reason})`, async function() { + input.from = this.test.ctx.accounts[input.from]; + input.to = input.to === null ? '0x0000000000000000000000000000000000000000' : this.test.ctx.accounts[input.to]; + + await this.test.ctx.mint.method.apply(this, [{ amount: input.mintAmount, holder: input.from }]); + + const balanceFrom = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.from).call(), 'ether' + ); + const balanceTo = this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.to).call(), 'ether' + ); + + await this.test.ctx.token.contract.methods + .send(input.to, this.test.ctx.web3.utils.toWei(input.amount, 'ether'), input.data === null ? '0x' : input.data) + .send({ from: input.from }) + .should.be.rejectedWith('revert'); + + assert.equal( + balanceFrom, + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.from).call(), 'ether') + ); + assert.equal( + balanceTo, + this.test.ctx.web3.utils.fromWei( + await this.test.ctx.token.contract.methods.balanceOf(input.to).call(), 'ether') + ); + }); + } + xit('send (ERC20)'); +}; diff --git a/test/suites/tokensReceived.js b/test/suites/tokensReceived.js new file mode 100644 index 0000000..9d443fa --- /dev/null +++ b/test/suites/tokensReceived.js @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +module.exports = function suite() { + xit('called on mint (self hook)'); + xit('called on mint (proxy hook)'); + xit('called on send (self hook)'); + xit('called on send (proxy hook)'); + xit('revert on mint (self hook)'); + xit('revert on mint (proxy hook)'); + xit('revert on send (self hook)'); + xit('revert on send (proxy hook)'); + xit('failure on send without hook (self hook)'); + xit('failure on send without hook (proxy hook)'); + xit('failure on mint without hook (self hook)'); + xit('failure on mint without hook (proxy hook)'); +}; diff --git a/test/suites/tokensToSend.js b/test/suites/tokensToSend.js new file mode 100644 index 0000000..16b3eb3 --- /dev/null +++ b/test/suites/tokensToSend.js @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const chai = require('chai'); +const assert = chai.assert; +chai.use(require('chai-as-promised')).should(); + +const artifacts = require('../../js/artifacts')(); + +module.exports = function suite() { + beforeEach(async function() { + this.hook = { + self: await artifacts.contracts.test.ContractAccount.ContractAccount.deploy( + this.web3, { from: this.accounts[5], arguments: [true, true] }), + balance: '5', + }; + await this.mint.method.apply(this, [{ amount: this.hook.balance, holder: this.hook.self.options.address }]); + }); + + afterEach(function() { delete this.hook; }); + + const inputs = [{ + label: 'called on send (self hook)', + event: 'NotifiedTokensToSend', + operator: function() { return this.test.ctx.hook.self.options.address; }, + from: function() { return this.test.ctx.hook.self.options.address; }, + to: function() { return this.test.ctx.accounts[4]; }, + amount: '3', + data: '0xcafe', + operatorData: null, + triggerHook: async function(origin, operator, from, to, amount, data, operatorData) { + return this.test.ctx.hook.self.methods + .send(this.test.ctx.token.contract.options.address, to, this.test.ctx.web3.utils.toWei(amount, 'ether'), data) + .send({ from: origin }); + }, + }, { + label: 'called on burn (self hook)', + event: 'NotifiedTokensToSend', + operator: function() { return this.test.ctx.hook.self.options.address; }, + from: function() { return this.test.ctx.hook.self.options.address; }, + to: function() { return '0x0000000000000000000000000000000000000000'; }, + amount: '3', + data: '0xcafe', + operatorData: null, + triggerHook: async function(origin, operator, from, to, amount, data, operatorData) { + await this.test.ctx.token.contract.methods.allowBurn(this.test.ctx.hook.self.options.address) + .send({ from: this.accounts[0] }); + return this.test.ctx.hook.self.methods + .burn(this.test.ctx.token.contract.options.address, this.test.ctx.web3.utils.toWei(amount, 'ether'), data) + .send({ from: origin }); + }, + }]; + + for (let input of inputs) { + it(input.label, async function() { + for (let prop of ['operator', 'from', 'to']) { input[prop] = input[prop].apply(this); } + + const balanceFrom = await this.test.ctx.token.contract.methods + .balanceOf(input.from).call(); + const balanceTo = await this.test.ctx.token.contract.methods.balanceOf(input.to).call(); + + const tx = await input.triggerHook.apply(this, [ + this.accounts[5], input.operator, input.from, input.to, input.amount, input.data, input.operatorData, + ]); + + assert.nestedProperty(tx, `events.${input.event}`); + assert.deepInclude(tx.events[input.event], { event: input.event }); + assert.deepInclude(tx.events[input.event], { + returnValues: { + '0': this.test.ctx.token.contract.options.address, + '1': input.operator, + '2': input.from, + '3': input.to, + '4': this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + '5': balanceFrom, + '6': balanceTo, + '7': input.data, + '8': input.operatorData, + token: this.test.ctx.token.contract.options.address, + operator: input.operator, + from: input.from, + to: input.to, + amount: this.test.ctx.web3.utils.toWei(input.amount, 'ether'), + balanceFrom: balanceFrom, + balanceTo: balanceTo, + data: input.data, + operatorData: input.operatorData, + }, + }); + }); + } + + xit('called on send (proxy hook)'); + xit('called on burn (proxy hook)'); + xit('revert on send (self hook)'); + xit('revert on burn (self hook)'); + xit('revert on send (proxy hook)'); + xit('revert on burn (proxy hook)'); +}; diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..f4dd727 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const ganache = require('ganache-cli'); +const Web3 = require('web3'); + +const initGanacheServer = (host, port) => { + const server = ganache.server({ + ws: true, + gasLimit: 5800000, + total_accounts: 10, // eslint-disable-line camelcase + }); + server.listen(port, host); + return server; +}; + +const initWeb3 = (host, port) => { + const web3 = new Web3(`ws://${host}:${port}`); + web3.extend({ + property: 'evm', + methods: [ + new web3.extend.Method({ name: 'snapshot', call: 'evm_snapshot', params: 0, outputFormatter: parseInt }), + new web3.extend.Method({ name: 'revert', call: 'evm_revert', params: 1, inputFormatter: [parseInt] }), + ], + }); + return web3; +}; + +const getAccounts = async (web3) => (await web3.eth.getAccounts()).map(web3.utils.toChecksumAddress); + +const getTotalSupply = async (web3, token) => ( + web3.utils.fromWei((await token.contract.methods.totalSupply().call()).toString())); + +const init = (host, port) => { + const ganacheServer = initGanacheServer(host, port); + const web3 = initWeb3(host, port); + return { ganacheServer, web3 }; +}; +module.exports = { initGanacheServer, initWeb3, init, getAccounts, getTotalSupply }; diff --git a/test/utils/attributes.js b/test/utils/attributes.js deleted file mode 100644 index 8de5499..0000000 --- a/test/utils/attributes.js +++ /dev/null @@ -1,51 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('attributes', function() { - let balances = {}; - for (let account of accounts) { - balances[account] = token.defaultBalance; - } - - it(`should have the name "${token.name}"`, async function() { - const name = await token.contract.methods.name().call(); - assert.strictEqual(name, token.name); - }); - - it(`should have the symbol "${token.symbol}"`, async function() { - const symbol = await token.contract.methods.symbol().call(); - assert.strictEqual(symbol, token.symbol); - }); - - it(`should have a granularity of ${token.granularity}`, - async function() { - const granularity = ( - await token.contract.methods.granularity().call()).toString(); - assert.strictEqual( - web3.utils.fromWei(granularity), - token.granularity - ); - } - ); - - it(`should have a total supply of ${token.initialSupply}`, - async function() { - await utils.assertTotalSupply(web3, token, token.initialSupply); - } - ); - - it(`should have a balance of ${token.defaultBalance} for all accounts`, - async function() { - for (let acc in balances) { - let balance = acc === accounts[0] - ? token.initialSupply + balances[acc] - : balances[acc]; - await utils.assertBalance(web3, token, acc, balance); - } - } - ); - }); -}; diff --git a/test/utils/burn.js b/test/utils/burn.js deleted file mode 100644 index fb7fdf9..0000000 --- a/test/utils/burn.js +++ /dev/null @@ -1,151 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('burn', function() { - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - }); - - it(`should let ${utils.formatAccount(accounts[0])} burn 3 ${token.symbol}`, - async function() { - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Burned', - data: { - operator: accounts[0], - from: accounts[0], - amount: web3.utils.toWei('3'), - data: '0xbeef', - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[0], - to: utils.zeroAddress, - amount: web3.utils.toWei('3'), - }, - }] - ); - - await token.contract.methods - .burn(web3.utils.toWei('3'), '0xbeef') - .send({ gas: 300000, from: accounts[0] }); - - await utils.getBlock(web3); - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 7); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply - 3); - await eventsCalled; - } - ); - - it(`should let ${utils.formatAccount(accounts[0])} burn 3 ${token.symbol}` + - ' (ERC20 Disabled)', async function() { - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - - await token.disableERC20(); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, - 'Burned', { - operator: accounts[0], - from: accounts[0], - amount: web3.utils.toWei('3'), - data: '0xcafe', - operatorData: null, - } - ); - - await token.contract.methods - .burn(web3.utils.toWei('3'), '0xcafe') - .send({ gas: 300000, from: accounts[0] }); - - await utils.getBlock(web3); - - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 7); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply - 3); - await eventCalled; - }); - - it(`should not let ${utils.formatAccount(accounts[0])} burn ` + - `${token.initialSupply + 10 + 1} ${token.symbol} ` + - '(not enough funds)', async function() { - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - - await token.contract.methods - .burn(web3.utils.toWei(`${token.initialSupply + 10 + 1}`), '0x') - .send({ gas: 300000, from: accounts[0] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - }); - - it(`should not let ${utils.formatAccount(accounts[0])} burn -3 ` + - `${token.symbol} (negative amount)`, async function() { - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - - await token.contract.methods - .burn(web3.utils.toWei('-3'), '0x') - .send({ gas: 300000, from: accounts[0] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - }); - - it(`should not let ${utils.formatAccount(accounts[0])} burn 0.007 ` + - `${token.symbol} (< granulairty)`, async function() { - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - - await token.contract.methods - .burn(web3.utils.toWei('0.007'), '0x') - .send({ gas: 300000, from: accounts[0] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - }); - - it(`should not let ${utils.formatAccount(accounts[1])} burn 3 ` + - `${token.symbol} (not owner)`, async function() { - await utils.assertBalance(web3, token, accounts[1], 10); - - await token.contract.methods - .burn(web3.utils.toWei('11'), '0x') - .send({ gas: 300000, from: accounts[1] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - }); - }); -}; diff --git a/test/utils/erc20Compatibility.js b/test/utils/erc20Compatibility.js deleted file mode 100644 index e223d8d..0000000 --- a/test/utils/erc20Compatibility.js +++ /dev/null @@ -1,191 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -const assert = chai.assert; -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('ERC20 Compatibility', function() { - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - }); - - it('should register the "ERC20Token" interface with ERC1820', - async function() { - let erc1820Registry = utils.getERC1820Registry(web3); - let erc20Hash = web3.utils.keccak256('ERC20Token'); - let erc20Addr = await erc1820Registry.methods - .getInterfaceImplementer(token.contract.options.address, erc20Hash) - .call(); - - assert.strictEqual(erc20Addr, token.contract.options.address); - } - ); - - it('should return 18 for decimals', async function() { - const decimals = await token.contract.methods.decimals().call(); - assert.strictEqual(decimals, '18'); - await utils.log(`decimals: ${decimals}`); - }); - - it(`should let ${utils.formatAccount(accounts[2])} ` + - `transfer 1.5 ${token.symbol} ` + - `to ${utils.formatAccount(accounts[1])}`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Sent', - data: { - operator: accounts[2], - from: accounts[2], - to: accounts[1], - amount: web3.utils.toWei('1.5'), - data: null, - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[2], - to: accounts[1], - amount: web3.utils.toWei('1.5'), - }, - }] - ); - - await token.contract.methods - .transfer(accounts[1], web3.utils.toWei('1.5')) - .send({ gas: 300000, from: accounts[2] }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 11.5); - await utils.assertBalance(web3, token, accounts[2], 8.5); - await eventsCalled; - }); - - it(`should approve ${utils.formatAccount(accounts[3])} ` + - `to transfer 3.5 ${token.symbol}` + - ` from ${utils.formatAccount(accounts[1])}`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, - 'Approval', { - owner: accounts[1], - spender: accounts[3], - amount: web3.utils.toWei('3.5'), - } - ); - - await token.contract.methods - .approve(accounts[3], web3.utils.toWei('3.5')) - .send({ gas: 300000, from: accounts[1] }); - - await utils.getBlock(web3); - - const allowance = await token.contract.methods - .allowance(accounts[1], accounts[3]) - .call(); - - assert.strictEqual(allowance, web3.utils.toWei('3.5')); - await utils.log(`allowance: ${allowance}`); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - await eventCalled; - }); - - it(`should let ${utils.formatAccount(accounts[3])} ` + - `transfer 3 ${token.symbol} ` + - `from ${utils.formatAccount(accounts[1])}`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Approval', - data: { - owner: accounts[1], - spender: accounts[3], - amount: web3.utils.toWei('3.5'), - }, - }, { - name: 'Sent', - data: { - operator: accounts[3], - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('3'), - data: null, - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('3'), - }, - }] - ); - - await token.contract.methods - .approve(accounts[3], web3.utils.toWei('3.5')) - .send({ gas: 300000, from: accounts[1] }); - - await token.contract.methods - .transferFrom(accounts[1], accounts[2], web3.utils.toWei('3')) - .send({ gas: 300000, from: accounts[3] }); - - await utils.getBlock(web3); - - const allowance = await token.contract.methods - .allowance(accounts[1], accounts[3]) - .call(); - - assert.strictEqual(web3.utils.fromWei(allowance), '0.5'); - await utils.log(`allowance: ${allowance}`); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 7); - await utils.assertBalance(web3, token, accounts[2], 13); - await eventsCalled; - }); - - it(`should not let ${utils.formatAccount(accounts[3])} transfer from ` + - `${utils.formatAccount(accounts[1])} (not approved)`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .transferFrom(accounts[1], accounts[2], web3.utils.toWei('1')) - .send({ gas: 300000, from: accounts[3] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - }); -}; diff --git a/test/utils/erc20Disabled.js b/test/utils/erc20Disabled.js deleted file mode 100644 index 6f3b4ed..0000000 --- a/test/utils/erc20Disabled.js +++ /dev/null @@ -1,103 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -const assert = chai.assert; -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('ERC20 Disabled', function() { - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - await token.disableERC20(); - }); - - it('should not return 18 for decimals', async function() { - await token.contract.methods - .decimals() - .call() - .should.be.rejectedWith('revert'); - }); - - it(`should not let ${utils.formatAccount(accounts[2])} ` + - `transfer 3 ${token.symbol} ` + - `to ${utils.formatAccount(accounts[1])}`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .transfer(accounts[1], web3.utils.toWei('3')) - .send({ gas: 300000, from: accounts[2] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - - it(`should not approve ${utils.formatAccount(accounts[3])} to transfer ` + - `from ${utils.formatAccount(accounts[1])}`, async function() { - await token.contract.methods - .approve(accounts[3], web3.utils.toWei('3.5')) - .send({ gas: 300000, from: accounts[1] }) - .should.be.rejectedWith('revert'); - - await token.contract.methods - .allowance(accounts[1], accounts[3]) - .call() - .should.be.rejectedWith('revert'); - }); - - it(`should not let ${utils.formatAccount(accounts[3])} ` + - `transfer 1 ${token.symbol} ` + - `from ${utils.formatAccount(accounts[1])}`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .approve(accounts[3], web3.utils.toWei('3.5')) - .send({ gas: 300000, from: accounts[1] }) - .should.be.rejectedWith('revert'); - - await token.contract.methods - .transferFrom(accounts[1], accounts[2], web3.utils.toWei('0.5')) - .send({ gas: 300000, from: accounts[3] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - - it('should enable ERC20 compatibility', async function() { - let erc820Registry = utils.getERC1820Registry(web3); - let erc20Hash = web3.utils.keccak256('ERC20Token'); - let erc20Addr = await erc820Registry.methods - .getInterfaceImplementer(token.contract.options.address, erc20Hash) - .call(); - - assert.strictEqual(erc20Addr, utils.zeroAddress); - - await token.contract.methods - .enableERC20() - .send({ gas: 300000, from: accounts[0] }); - - await utils.getBlock(web3); - erc20Addr = await erc820Registry.methods - .getInterfaceImplementer(token.contract.options.address, erc20Hash) - .call(); - - assert.strictEqual(erc20Addr, token.contract.options.address); - }); - }); -}; diff --git a/test/utils/index.js b/test/utils/index.js deleted file mode 100644 index eeeaf4b..0000000 --- a/test/utils/index.js +++ /dev/null @@ -1,138 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -const assert = chai.assert; -const ERC1820 = require('erc1820'); -const testAccounts = [ - '0x093d49D617a10F26915553255Ec3FEE532d2C12F', - '0x1dc728786E09F862E39Be1f39dD218EE37feB68D', - '0x2eeDf8a799B73BC02E4664183eB72422C377153B', - '0x3bF958Fa0626e898F548a8F95Cf9AB3A4Db65169', - '0x4bd1280852Cadb002734647305AFC1db7ddD6Acb', - '0x5cE162cFa6208d7c50A7cB3525AC126155e7bCe4', - '0x6b09D6433a379752157fD1a9E537c5CAe5fa3168', - '0x7dc0a40D64d72bb4590652B8f5C687bF7F26400c', - '0x8dF64de79608F0aE9e72ECAe3A400582AeD8101C', - '0x9a5279029e9A2D6E787c5A09CB068AB3D45e209d', -]; -const blocks = []; -let blockIdx = 0; - -const log = (msg) => process.env.MOCHA_VERBOSE && console.log(msg); -const zeroAddress = '0x0000000000000000000000000000000000000000'; - -function assertEventWillBeCalled(contract, name, data) { - return new Promise((resolve, reject) => { - contract.once(name, function(err, event) { - if (err) { reject(err); } - log(`${name} called with ${JSON.stringify(event.returnValues)}`); - assert.deepOwnInclude( - event.returnValues, data, `Event: ${name}: invalid data`); - resolve(); - }); - }); -} - -module.exports = { - zeroAddress, - log, - assertEventWillBeCalled, - assertEventsWillBeCalled(contract, events) { - return Promise.all(events - .map(event => assertEventWillBeCalled(contract, event.name, event.data))); - }, - - formatAccount(account) { - if (testAccounts.includes(account)) { return `${account.slice(0, 4)}...`; } - return account.slice(0, 8); - }, - - async getBlock(web3) { - blocks[blockIdx] = await web3.eth.getBlockNumber(); - this.log(`block ${blockIdx} -> ${blocks[blockIdx]}`); - blockIdx++; - }, - - async assertTotalSupply(web3, token, expected) { - const totalSupply = ( - await token.contract.methods.totalSupply().call()).toString(); - assert.equal(web3.utils.fromWei(totalSupply), expected); - this.log(`totalSupply: ${web3.utils.fromWei(totalSupply)}`); - }, - - async assertBalance(web3, token, account, expected) { - const balance = ( - await token.contract.methods.balanceOf(account).call()).toString(); - assert.equal(web3.utils.fromWei(balance), expected); - this.log(`balance[${account}]: ${web3.utils.fromWei(balance)}`); - }, - - getERC1820Registry(web3) { - return ERC1820.ERC1820Registry(web3); - }, - - async mintForAllAccounts(web3, accounts, token, operator, amount) { - let erc1820Registry = ERC1820.ERC1820Registry(web3); - let hook; - for (let account of accounts) { - hook = await erc1820Registry.methods.getInterfaceImplementer( - account, web3.utils.keccak256('ERC777TokensRecipient')).call(); - if (hook === zeroAddress) { hook = '0x0'; } - log(`mint ${amount} for ${account} by ${operator} (hook: ${hook})`); - await token.mintForAccount(account, amount, operator); - } - }, - - async assertHookCalled( - web3, - hook, - token, - operator, - from, - to, - data, - operatorData, - balanceFrom, - balanceTo, - ) { - assert.strictEqual( - await hook.methods.token(to).call(), - web3.utils.toChecksumAddress(token) - ); - assert.strictEqual( - await hook.methods.operator(to).call(), - web3.utils.toChecksumAddress(operator) - ); - assert.strictEqual( - await hook.methods.from(to).call(), - web3.utils.toChecksumAddress(from) - ); - assert.strictEqual( - await hook.methods.to(to).call(), - web3.utils.toChecksumAddress(to) - ); - assert.strictEqual(await hook.methods.data(to).call(), data); - assert.strictEqual( - await hook.methods.operatorData(to).call(), operatorData); - - assert.equal( - web3.utils.fromWei(await hook.methods.balanceOf(from).call()), - balanceFrom - ); - - assert.equal( - web3.utils.fromWei(await hook.methods.balanceOf(to).call()), - balanceTo - ); - }, - - async assertHookNotCalled(hook, to) { - assert.strictEqual(await hook.methods.token(to).call(), zeroAddress); - assert.strictEqual(await hook.methods.operator(to).call(), zeroAddress); - assert.strictEqual(await hook.methods.from(to).call(), zeroAddress); - assert.strictEqual(await hook.methods.to(to).call(), zeroAddress); - assert.strictEqual(await hook.methods.data(to).call(), null); - assert.strictEqual(await hook.methods.operatorData(to).call(), null); - }, -}; diff --git a/test/utils/mint.js b/test/utils/mint.js deleted file mode 100644 index 5f4714d..0000000 --- a/test/utils/mint.js +++ /dev/null @@ -1,103 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('minting', function() { - it(`should mint 10 ${token.symbol} for ${utils.formatAccount(accounts[1])}`, - async function() { - await utils.assertBalance(web3, token, accounts[1], 0); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Minted', - data: { - operator: web3.utils.toChecksumAddress(accounts[0]), - to: web3.utils.toChecksumAddress(accounts[1]), - amount: web3.utils.toWei('10'), - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: utils.zeroAddress, - to: web3.utils.toChecksumAddress(accounts[1]), - amount: web3.utils.toWei('10'), - }, - }] - ); - - await token.contract.methods - .mint(accounts[1], web3.utils.toWei('10'), '0x', '0x') - .send({ gas: 300000, from: accounts[0] }); - - await utils.getBlock(web3); - - await utils.assertTotalSupply(web3, token, token.initialSupply + 10); - await utils.assertBalance(web3, token, accounts[1], 10); - await eventsCalled; - } - ); - - it(`should mint 10 ${token.symbol} for ` + - `${utils.formatAccount(accounts[1])} ` + - '(ERC20 Disabled)', async function() { - await utils.assertBalance(web3, token, accounts[1], 0); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, 'Minted', { - operator: web3.utils.toChecksumAddress(accounts[0]), - to: web3.utils.toChecksumAddress(accounts[1]), - amount: web3.utils.toWei('10'), - operatorData: null, - } - ); - - await token.disableERC20(); - - await token.contract.methods - .mint(accounts[1], web3.utils.toWei('10'), '0x', '0x') - .send({ gas: 300000, from: accounts[0] }); - - await utils.getBlock(web3); - - await utils.assertTotalSupply(web3, token, token.initialSupply + 10); - await utils.assertBalance(web3, token, accounts[1], 10); - await eventCalled; - }); - - it(`should not mint -10 ${token.symbol} (negative amount)`, - async function() { - await utils.assertBalance(web3, token, accounts[1], 0); - - await token.contract.methods - .mint(accounts[1], web3.utils.toWei('-10'), '0x', '0x') - .send({ gas: 300000, from: accounts[0] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - - await utils.assertTotalSupply(web3, token, token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 0); - } - ); - - it(`should not mint 0.007 ${token.symbol} (< granulairty)`, - async function() { - await utils.assertBalance(web3, token, accounts[1], 0); - - await token.contract.methods - .mint(accounts[1], web3.utils.toWei('0.007'), '0x', '0x') - .send({ gas: 300000, from: accounts[0] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertBalance(web3, token, accounts[1], 0); - await utils.assertTotalSupply(web3, token, token.initialSupply); - } - ); - }); -}; diff --git a/test/utils/operator.js b/test/utils/operator.js deleted file mode 100644 index 9ec7911..0000000 --- a/test/utils/operator.js +++ /dev/null @@ -1,225 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -const assert = chai.assert; -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('operator', function() { - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - }); - - it('should list the default operators', async function() { - const defaultOperators = await token.contract.methods - .defaultOperators() - .call(); - - assert.deepEqual( - defaultOperators.map(web3.utils.toChecksumAddress), - token.defaultOperators.map(web3.utils.toChecksumAddress), - ); - }); - - for (let defaultOperator of token.defaultOperators) { - it(`should detect ${utils.formatAccount(defaultOperator)} is a default ` + - 'operator for all accounts', async function() { - for (let account of accounts) { - assert.isTrue( - await token.contract.methods - .isOperatorFor(defaultOperator, account) - .call() - ); - } - }); - } - - it(`should let ${utils.formatAccount(accounts[3])} revoke the default ` + - `operator ${utils.formatAccount(token.defaultOperators[1])}`, - async function() { - assert.isTrue( - await token.contract.methods - .isOperatorFor(token.defaultOperators[1], accounts[3]) - .call() - ); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, 'RevokedOperator', { - operator: web3.utils.toChecksumAddress(token.defaultOperators[1]), - tokenHolder: accounts[3], - } - ); - - await token.contract.methods - .revokeOperator(token.defaultOperators[1]) - .send({ from: accounts[3], gas: 300000 }); - - await utils.getBlock(web3); - assert.isFalse( - await token.contract.methods - .isOperatorFor(token.defaultOperators[1], accounts[3]) - .call() - ); - await eventCalled; - }); - - it(`should let ${utils.formatAccount(accounts[4])} reauthorize the ` + - 'previously revoked default operator ' + - `${utils.formatAccount(token.defaultOperators[0])}`, - async function() { - assert.isTrue( - await token.contract.methods - .isOperatorFor(token.defaultOperators[0], accounts[4]) - .call() - ); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'RevokedOperator', - data: { - operator: web3.utils.toChecksumAddress(token.defaultOperators[0]), - tokenHolder: accounts[4], - }, - }, { - name: 'AuthorizedOperator', - data: { - operator: web3.utils.toChecksumAddress(token.defaultOperators[0]), - tokenHolder: accounts[4], - }, - }] - ); - - await token.contract.methods - .revokeOperator(token.defaultOperators[0]) - .send({ from: accounts[4], gas: 300000 }); - - await utils.getBlock(web3); - assert.isFalse( - await token.contract.methods - .isOperatorFor(token.defaultOperators[0], accounts[4]) - .call() - ); - - await token.contract.methods - .authorizeOperator(token.defaultOperators[0]) - .send({ from: accounts[4], gas: 300000 }); - - await utils.getBlock(web3); - assert.isTrue( - await token.contract.methods - .isOperatorFor(token.defaultOperators[0], accounts[4]) - .call() - ); - await eventsCalled; - }); - - it(`should detect ${utils.formatAccount(accounts[3])} is not an operator ` + - `for ${utils.formatAccount(accounts[1])}`, async function() { - assert.isFalse( - await token.contract.methods - .isOperatorFor(accounts[3], accounts[1]) - .call() - ); - }); - - it(`should authorize ${utils.formatAccount(accounts[3])} as an operator ` + - `for ${utils.formatAccount(accounts[1])}`, async function() { - let eventCalled = utils.assertEventWillBeCalled( - token.contract, 'AuthorizedOperator', - { operator: accounts[3], tokenHolder: accounts[1] } - ); - - await token.contract.methods - .authorizeOperator(accounts[3]) - .send({ from: accounts[1], gas: 300000 }); - - assert.isTrue( - await token.contract.methods - .isOperatorFor(accounts[3], accounts[1]) - .call() - ); - await eventCalled; - }); - - it(`should revoke ${utils.formatAccount(accounts[3])} as an operator for ` + - `${utils.formatAccount(accounts[1])}`, async function() { - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'AuthorizedOperator', - data: { operator: accounts[3], tokenHolder: accounts[1] }, - }, { - name: 'RevokedOperator', - data: { operator: accounts[3], tokenHolder: accounts[1] }, - }] - ); - - await token.contract.methods - .authorizeOperator(accounts[3]) - .send({ from: accounts[1], gas: 300000 }); - - assert.isTrue( - await token.contract.methods - .isOperatorFor(accounts[3], accounts[1]) - .call() - ); - - await token.contract.methods - .revokeOperator(accounts[3]) - .send({ from: accounts[1], gas: 300000 }); - - await utils.getBlock(web3); - assert.isFalse( - await token.contract.methods - .isOperatorFor(accounts[3], accounts[1]) - .call() - ); - await eventsCalled; - }); - - it(`should not let ${utils.formatAccount(accounts[3])} authorize itself ` + - 'as one of his own operators', async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[3], 10); - - await token.contract.methods - .authorizeOperator(accounts[3]) - .send({ gas: 300000, from: accounts[3] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[3], 10); - }); - - it(`should make ${utils.formatAccount(accounts[3])} ` + - 'an operator for itself by default', async function() { - assert.isTrue( - await token.contract.methods - .isOperatorFor(accounts[3], accounts[3]) - .call() - ); - }); - - it(`should not let ${utils.formatAccount(accounts[3])} revoke itself ` + - 'as one of its own operators', async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[3], 10); - - await token.contract.methods - .revokeOperator(accounts[3]) - .send({ gas: 300000, from: accounts[3] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[3], 10); - }); - }); -}; diff --git a/test/utils/operatorBurn.js b/test/utils/operatorBurn.js deleted file mode 100644 index d62bd96..0000000 --- a/test/utils/operatorBurn.js +++ /dev/null @@ -1,154 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -chai.use(require('chai-as-promised')); -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('operatorBurn', function() { - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - }); - - it(`should let ${utils.formatAccount(token.burnOperator)} ` + - `burn 1.12 ${token.symbol} from ` + - `${utils.formatAccount(accounts[1])}`, async function() { - await token.contract.methods - .authorizeOperator(token.burnOperator) - .send({ from: accounts[1], gas: 300000 }); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Burned', - data: { - operator: token.burnOperator, - from: accounts[1], - amount: web3.utils.toWei('1.12'), - data: null, - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[1], - to: utils.zeroAddress, - amount: web3.utils.toWei('1.12'), - }, - }] - ); - - await token.contract.methods - .operatorBurn( - accounts[1], web3.utils.toWei('1.12'), '0x', '0x') - .send({ gas: 300000, from: token.burnOperator }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply - 1.12); - await utils.assertBalance(web3, token, accounts[1], 8.88); - await eventsCalled; - }); - - it(`should let ${utils.formatAccount(token.burnOperator)} ` + - `burn 1.12 ${token.symbol} from ` + - `${utils.formatAccount(accounts[1])}` + - '(ERC20 Disabled)', async function() { - await token.contract.methods - .authorizeOperator(token.burnOperator) - .send({ from: accounts[1], gas: 300000 }); - - await token.disableERC20(); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, - 'Burned', { - operator: token.burnOperator, - from: accounts[1], - amount: web3.utils.toWei('1.12'), - data: '0xcafe', - operatorData: '0xbeef', - } - ); - - await token.contract.methods - .operatorBurn( - accounts[1], web3.utils.toWei('1.12'), '0xcafe', '0xbeef') - .send({ gas: 300000, from: token.burnOperator }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply - 1.12); - await utils.assertBalance(web3, token, accounts[1], 8.88); - await eventCalled; - }); - - it(`should not let ${utils.formatAccount(token.burnOperator)} burn from ` + - `${utils.formatAccount(accounts[2])} (not operator)`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .operatorBurn( - accounts[2], web3.utils.toWei('3.72'), '0x', '0x') - .send({ gas: 300000, from: token.burnOperator }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - - it(`should not let ${utils.formatAccount(accounts[4])} burn from ` + - `${utils.formatAccount(accounts[2])} ` + - '(not burn operator)', async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .authorizeOperator(accounts[4]) - .send({ from: accounts[2], gas: 300000 }); - - await token.contract.methods - .operatorBurn( - accounts[2], web3.utils.toWei('3.72'), '0x', '0x') - .send({ gas: 300000, from: accounts[4] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - - it(`should let ${utils.formatAccount(token.burnOperator)} ` + - 'use operatorBurn on itself', async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, token.burnOperator, 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .operatorBurn( - token.burnOperator, web3.utils.toWei('3.72'), '0x', '0x') - .send({ gas: 300000, from: token.burnOperator }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply - 3.72); - await utils.assertBalance(web3, token, token.burnOperator, 6.28); - }); - }); -}; diff --git a/test/utils/operatorSend.js b/test/utils/operatorSend.js deleted file mode 100644 index 12011dd..0000000 --- a/test/utils/operatorSend.js +++ /dev/null @@ -1,157 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('operatorSend', function() { - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - }); - - it(`should let ${utils.formatAccount(accounts[3])} ` + - `send 1.12 ${token.symbol} from ${utils.formatAccount(accounts[1])} ` + - `to ${utils.formatAccount(accounts[2])}`, async function() { - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'AuthorizedOperator', - data: { operator: accounts[3], tokenHolder: accounts[1] }, - }, { - name: 'Sent', - data: { - operator: accounts[3], - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('1.12'), - data: null, - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('1.12'), - }, - }] - ); - - await token.contract.methods - .authorizeOperator(accounts[3]) - .send({ from: accounts[1], gas: 300000 }); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .operatorSend( - accounts[1], accounts[2], web3.utils.toWei('1.12'), '0x', '0x') - .send({ gas: 300000, from: accounts[3] }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 8.88); - await utils.assertBalance(web3, token, accounts[2], 11.12); - await eventsCalled; - }); - - it(`should not let ${utils.formatAccount(accounts[3])} send from ` + - `${utils.formatAccount(accounts[1])} (not operator)`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .operatorSend( - accounts[1], accounts[2], web3.utils.toWei('3.72'), '0x', '0x') - .send({ gas: 300000, from: accounts[3] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - - it(`should let ${utils.formatAccount(accounts[3])} ` + - 'use operatorSend on itself', async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[3], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Sent', - data: { - operator: accounts[3], - from: accounts[3], - to: accounts[2], - amount: web3.utils.toWei('3.72'), - data: null, - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[3], - to: accounts[2], - amount: web3.utils.toWei('3.72'), - }, - }] - ); - - await token.contract.methods - .operatorSend( - accounts[3], accounts[2], web3.utils.toWei('3.72'), '0x', '0x') - .send({ gas: 300000, from: accounts[3] }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[3], 6.28); - await utils.assertBalance(web3, token, accounts[2], 13.72); - await eventsCalled; - }); - - it(`should let ${utils.formatAccount(accounts[3])} ` + - 'use operatorSend on itself (ERC20 Disabled)', async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[3], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, - 'Sent', { - operator: accounts[3], - from: accounts[3], - to: accounts[2], - amount: web3.utils.toWei('3.72'), - data: null, - operatorData: null, - } - ); - - await token.contract.methods - .operatorSend( - accounts[3], accounts[2], web3.utils.toWei('3.72'), '0x', '0x') - .send({ gas: 300000, from: accounts[3] }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[3], 6.28); - await utils.assertBalance(web3, token, accounts[2], 13.72); - await eventCalled; - }); - }); -}; diff --git a/test/utils/send.js b/test/utils/send.js deleted file mode 100644 index 736731d..0000000 --- a/test/utils/send.js +++ /dev/null @@ -1,209 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); - -exports.test = function(web3, accounts, token) { - describe('send', function() { - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - }); - - it(`should let ${utils.formatAccount(accounts[1])} ` + - `send 3 ${token.symbol} with empty data ` + - `to ${utils.formatAccount(accounts[2])}`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Sent', - data: { - operator: accounts[1], - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('3'), - data: null, - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('3'), - }, - }] - ); - - await token.contract.methods - .send(accounts[2], web3.utils.toWei('3'), '0x') - .send({ gas: 300000, from: accounts[1] }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 7); - await utils.assertBalance(web3, token, accounts[2], 13); - await eventsCalled; - }); - - it(`should let ${utils.formatAccount(accounts[1])} ` + - `send 3 ${token.symbol} with data ` + - `to ${utils.formatAccount(accounts[2])}`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Sent', - data: { - operator: accounts[1], - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('3'), - data: '0xcafe', - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('3'), - }, - }] - ); - - await token.contract.methods - .send(accounts[2], web3.utils.toWei('3'), '0xcafe') - .send({ gas: 300000, from: accounts[1] }); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 7); - await utils.assertBalance(web3, token, accounts[2], 13); - await eventsCalled; - }); - - it(`should let ${utils.formatAccount(accounts[1])} ` + - `send 3 ${token.symbol} to ${utils.formatAccount(accounts[2])} ` + - '(ERC20 Disabled)', async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.disableERC20(); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, - 'Sent', { - operator: accounts[1], - from: accounts[1], - to: accounts[2], - amount: web3.utils.toWei('3'), - data: null, - operatorData: null, - } - ); - - await token.contract.methods - .send(accounts[2], web3.utils.toWei('3'), '0x') - .send({ gas: 300000, from: accounts[1] }); - - await utils.getBlock(web3); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 7); - await utils.assertBalance(web3, token, accounts[2], 13); - await eventCalled; - }); - - it(`should not let ${utils.formatAccount(accounts[1])} ` + - `send 11 ${token.symbol} (not enough funds)`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .send(accounts[2], web3.utils.toWei('11'), '0x') - .send({ gas: 300000, from: accounts[1] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - - it(`should not let ${utils.formatAccount(accounts[1])} ` + - `send -3 ${token.symbol} (negative amount)`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .send(accounts[2], web3.utils.toWei('-3'), '0x') - .send({ gas: 300000, from: accounts[1] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - - it(`should not let ${utils.formatAccount(accounts[1])} ` + - `send 0.007 ${token.symbol} (< granulairty)`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .send(accounts[2], web3.utils.toWei('0.007'), '0x') - .send({ gas: 300000, from: accounts[1] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - - it(`should not let ${utils.formatAccount(accounts[1])} ` + - `send ${token.symbol} to 0x0 (zero-account)`, async function() { - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - - await token.contract.methods - .send(utils.zeroAddress, - web3.utils.toWei('1'), '0x') - .send({ gas: 300000, from: accounts[1] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[1], 10); - await utils.assertBalance(web3, token, accounts[2], 10); - }); - }); -}; diff --git a/test/utils/tokensRecipient.js b/test/utils/tokensRecipient.js deleted file mode 100644 index 451609a..0000000 --- a/test/utils/tokensRecipient.js +++ /dev/null @@ -1,411 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -const assert = chai.assert; -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); -const OldExampleTokensRecipient = artifacts.require('ExampleTokensRecipient'); -const empty = '0x'; - -let deployTokensRecipient; -let erc1820Registry; - -exports.test = function(web3, accounts, token) { - describe('TokensRecipient', async function() { - before(function() { - let ExampleTokensRecipient = new web3.eth.Contract( - OldExampleTokensRecipient.abi, - { data: OldExampleTokensRecipient.bytecode } - ); - - erc1820Registry = utils.getERC1820Registry(web3); - - deployTokensRecipient = async function(setInterface, from) { - const deployRecipient = ExampleTokensRecipient - .deploy({ arguments: [setInterface] }); - const gas = await deployRecipient.estimateGas(); - const recipient = await deployRecipient - .send({ from: from, gas: gas }); - assert.ok(recipient.options.address); - return recipient; - }; - }); - - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - }); - - // truffle clean-room is not able to revert the ERC1820Registry - // manually unset any TokensRecipient that may have been set during testing. - afterEach(async function() { - for (let account of accounts) { - await erc1820Registry.methods - .setInterfaceImplementer( - account, - web3.utils.keccak256('ERC777TokensRecipient'), - utils.zeroAddress - ).send({ from: account }); - } - }); - - it('should notify the recipient upon receiving tokens', async function() { - const recipient = await deployTokensRecipient(true, accounts[4]); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - - await recipient.methods - .acceptTokens() - .send({ gas: 300000, from: accounts[4] }); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Sent', - data: { - operator: accounts[5], - from: accounts[5], - to: recipient.options.address, - amount: web3.utils.toWei('1.22'), - data: '0xcafe', - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[5], - to: recipient.options.address, - amount: web3.utils.toWei('1.22'), - }, - }] - ); - - const send = token.contract.methods - .send(recipient.options.address, web3.utils.toWei('1.22'), '0xcafe'); - - const sendGas = await send.estimateGas(); - await send.send({ gas: sendGas, from: accounts[5] }); - - await utils.getBlock(web3); - - await utils.assertHookCalled( - web3, - recipient, - token.contract.options.address, - accounts[5], - accounts[5], - recipient.options.address, - '0xcafe', - null, - 8.78, - 1.22 - ); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[5], 8.78); - await utils.assertBalance(web3, token, recipient.options.address, 1.22); - await eventsCalled; - }); - - it('should notify the recipient upon receiving tokens ' + - '(ERC20 Disabled)', async function() { - const recipient = await deployTokensRecipient(true, accounts[4]); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - - await recipient.methods - .acceptTokens() - .send({ gas: 300000, from: accounts[4] }); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, - 'Sent', { - operator: accounts[5], - from: accounts[5], - to: recipient.options.address, - amount: web3.utils.toWei('1.22'), - data: '0xcafe', - operatorData: null, - } - ); - - const send = token.contract.methods - .send(recipient.options.address, web3.utils.toWei('1.22'), '0xcafe'); - - const sendGas = await send.estimateGas(); - await send.send({ gas: sendGas, from: accounts[5] }); - - await utils.getBlock(web3); - - await utils.assertHookCalled( - web3, - recipient, - token.contract.options.address, - accounts[5], - accounts[5], - recipient.options.address, - '0xcafe', - null, - 8.78, - 1.22 - ); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[5], 8.78); - await utils.assertBalance(web3, token, recipient.options.address, 1.22); - await eventCalled; - }); - - it('should let the recipient reject the tokens', async function() { - const recipient = await deployTokensRecipient(true, accounts[4]); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - - await recipient.methods - .rejectTokens() - .send({ gas: 300000, from: accounts[4] }); - - await token.contract.methods - .send(recipient.options.address, web3.utils.toWei('1.22'), empty) - .send({ gas: 300000, from: accounts[5] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - - // revert will revert setting data in the hook - utils.assertHookNotCalled(recipient, recipient.options.address); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - }); - - it('should call "TokensRecipient" for ' + - `${utils.formatAccount(accounts[4])} on send`, async function() { - const recipient = await deployTokensRecipient(false, accounts[4]); - - await erc1820Registry.methods - .setInterfaceImplementer( - accounts[4], - web3.utils.keccak256('ERC777TokensRecipient'), - recipient.options.address - ).send({ from: accounts[4] }); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 10); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - - await recipient.methods - .acceptTokens() - .send({ gas: 300000, from: accounts[4] }); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Sent', - data: { - operator: accounts[5], - from: accounts[5], - to: accounts[4], - amount: web3.utils.toWei('4.24'), - data: '0xbeef', - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[5], - to: accounts[4], - amount: web3.utils.toWei('4.24'), - }, - }] - ); - - await token.contract.methods - .send(accounts[4], web3.utils.toWei('4.24'), '0xbeef') - .send({ gas: 300000, from: accounts[5] }); - - await utils.getBlock(web3); - - await utils.assertHookCalled( - web3, - recipient, - token.contract.options.address, - accounts[5], - accounts[5], - accounts[4], - '0xbeef', - null, - 5.76, - 14.24, - ); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 14.24); - await utils.assertBalance(web3, token, accounts[5], 5.76); - await utils.assertBalance(web3, token, recipient.options.address, 0); - await eventsCalled; - }); - - it('should call "TokensRecipient" for ' + - `${utils.formatAccount(accounts[4])} on send` + - '(ERC20 Disabled)', async function() { - const recipient = await deployTokensRecipient(false, accounts[4]); - - await erc1820Registry.methods - .setInterfaceImplementer( - accounts[4], - web3.utils.keccak256('ERC777TokensRecipient'), - recipient.options.address - ).send({ from: accounts[4] }); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 10); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - - await recipient.methods - .acceptTokens() - .send({ gas: 300000, from: accounts[4] }); - - await token.disableERC20(); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, - 'Sent', { - operator: accounts[5], - from: accounts[5], - to: accounts[4], - amount: web3.utils.toWei('1.22'), - data: '0xbeef', - operatorData: null, - } - ); - - await token.contract.methods - .send(accounts[4], web3.utils.toWei('1.22'), '0xbeef') - .send({ gas: 300000, from: accounts[5] }); - - await utils.getBlock(web3); - - await utils.assertHookCalled( - web3, - recipient, - token.contract.options.address, - accounts[5], - accounts[5], - accounts[4], - '0xbeef', - null, - 8.78, - 11.22, - ); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 11.22); - await utils.assertBalance(web3, token, accounts[5], 8.78); - await utils.assertBalance(web3, token, recipient.options.address, 0); - await eventCalled; - }); - - it('should not send tokens to a contract ' + - 'without TokensRecipient', async function() { - const recipient = await deployTokensRecipient(false, accounts[4]); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - - await token.contract.methods - .send(recipient.options.address, web3.utils.toWei('1.22'), empty) - .send({ gas: 300000, from: accounts[5] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - - // revert will revert setting data in the hook - utils.assertHookNotCalled(recipient, recipient.options.address); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - }); - - it('should call "TokensRecipient" for ' + - `${utils.formatAccount(accounts[4])} on mint`, async function() { - const recipient = await deployTokensRecipient(false, accounts[4]); - - await erc1820Registry.methods - .setInterfaceImplementer( - accounts[4], - web3.utils.keccak256('ERC777TokensRecipient'), - recipient.options.address - ).send({ from: accounts[4] }); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 10); - await utils.assertBalance(web3, token, recipient.options.address, 0); - - await recipient.methods - .acceptTokens() - .send({ gas: 300000, from: accounts[4] }); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Minted', - data: { - operator: accounts[0], - to: accounts[4], - amount: web3.utils.toWei('1.22'), - data: '0xcafe', - operatorData: '0xbeef', - }, - }, { - name: 'Transfer', - data: { - from: utils.zeroAddress, - to: accounts[4], - amount: web3.utils.toWei('1.22'), - }, - }] - ); - - await token.contract.methods - .mint(accounts[4], web3.utils.toWei('1.22'), '0xcafe', '0xbeef') - .send({ gas: 300000, from: accounts[0] }); - - await utils.getBlock(web3); - - await utils.assertHookCalled( - web3, - recipient, - token.contract.options.address, - accounts[0], - utils.zeroAddress, - accounts[4], - '0xcafe', - '0xbeef', - 0, - 11.22, - ); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply + 1.22); - await utils.assertBalance(web3, token, accounts[4], 11.22); - await utils.assertBalance(web3, token, recipient.options.address, 0); - await eventsCalled; - }); - }); -}; diff --git a/test/utils/tokensSender.js b/test/utils/tokensSender.js deleted file mode 100644 index 730e547..0000000 --- a/test/utils/tokensSender.js +++ /dev/null @@ -1,268 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const chai = require('chai'); -const assert = chai.assert; -chai.use(require('chai-as-promised')).should(); -const utils = require('./index'); -const OldExampleTokensSender = artifacts.require('ExampleTokensSender'); - -let deployTokensSender; -let erc1820Registry; - -exports.test = function(web3, accounts, token) { - describe('TokensSender', async function() { - before(async function() { - const ExampleTokensSender = new web3.eth.Contract( - OldExampleTokensSender.abi, - { data: OldExampleTokensSender.bytecode } - ); - - erc1820Registry = utils.getERC1820Registry(web3); - - deployTokensSender = async function(setInterface, from) { - const deploySender = ExampleTokensSender - .deploy({ arguments: [setInterface] }); - const deployGas = await deploySender.estimateGas(); - const sender = await deploySender - .send({ from: from, gas: deployGas }); - assert.ok(sender.options.address); - - await erc1820Registry.methods - .setInterfaceImplementer( - from, - web3.utils.keccak256('ERC777TokensSender'), - sender.options.address - ).send({ from: from }); - - return sender; - }; - }); - - beforeEach(async function() { - await utils - .mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000); - }); - - // truffle clean-room is not able to revert the ERC1820Registry - // manually unset any TokensSenders that may have been set during testing. - afterEach(async function() { - for (let account of accounts) { - await erc1820Registry.methods - .setInterfaceImplementer( - account, - web3.utils.keccak256('ERC777TokensSender'), - utils.zeroAddress - ).send({ from: account }); - } - }); - - it('should notify the token holder before sending ' + - 'tokens', async function() { - const sender = await deployTokensSender(true, accounts[4]); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 10); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, sender.options.address, 0); - utils.assertHookNotCalled(sender, sender.options.address); - - await sender.methods - .acceptTokensToSend() - .send({ gas: 300000, from: accounts[4] }); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Sent', - data: { - operator: accounts[4], - from: accounts[4], - to: accounts[5], - amount: web3.utils.toWei('1.22'), - data: null, - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[4], - to: accounts[5], - amount: web3.utils.toWei('1.22'), - }, - }] - ); - - await token.contract.methods - .send(accounts[5], web3.utils.toWei('1.22'), '0x') - .send({ gas: 300000, from: accounts[4] }); - - await utils.getBlock(web3); - - await utils.assertHookCalled( - web3, - sender, - token.contract.options.address, - accounts[4], - accounts[4], - accounts[5], - null, - null, - 10, - 10 - ); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 8.78); - await utils.assertBalance(web3, token, accounts[5], 11.22); - await utils.assertBalance(web3, token, sender.options.address, 0); - await eventsCalled; - }); - - it('should notify the token holder before sending tokens ' + - '(ERC20 Disabled)', async function() { - const sender = await deployTokensSender(true, accounts[4]); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 10); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, sender.options.address, 0); - utils.assertHookNotCalled(sender, sender.options.address); - - await sender.methods - .acceptTokensToSend() - .send({ gas: 300000, from: accounts[4] }); - - await token.disableERC20(); - - let eventCalled = utils.assertEventWillBeCalled( - token.contract, - 'Sent', { - operator: accounts[4], - from: accounts[4], - to: accounts[5], - amount: web3.utils.toWei('1.22'), - data: null, - operatorData: null, - } - ); - - await token.contract.methods - .send(accounts[5], web3.utils.toWei('1.22'), '0x') - .send({ gas: 300000, from: accounts[4] }); - - await utils.getBlock(web3); - - await utils.assertHookCalled( - web3, - sender, - token.contract.options.address, - accounts[4], - accounts[4], - accounts[5], - null, - null, - 10, - 10 - ); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 8.78); - await utils.assertBalance(web3, token, accounts[5], 11.22); - await utils.assertBalance(web3, token, sender.options.address, 0); - await eventCalled; - }); - - it('should block the sending of tokens for the token ' + - 'holder', async function() { - const sender = await deployTokensSender(true, accounts[4]); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 10); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, sender.options.address, 0); - - await sender.methods - .rejectTokensToSend() - .send({ gas: 300000, from: accounts[4] }); - - await token.contract.methods - .send(accounts[5], web3.utils.toWei('1.22'), '0x') - .send({ gas: 300000, from: accounts[4] }) - .should.be.rejectedWith('revert'); - - await utils.getBlock(web3); - - // revert will revert setting data in the hook - utils.assertHookNotCalled(sender, sender.options.address); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance(web3, token, accounts[4], 10); - await utils.assertBalance(web3, token, accounts[5], 10); - await utils.assertBalance(web3, token, sender.options.address, 0); - }); - - it('should notify the token holder before burning ' + - 'tokens', async function() { - const sender = await deployTokensSender(true, accounts[0]); - - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply); - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 10); - await utils.assertBalance(web3, token, sender.options.address, 0); - utils.assertHookNotCalled(sender, sender.options.address); - - await sender.methods - .acceptTokensToSend() - .send({ gas: 300000, from: accounts[0] }); - - let eventsCalled = utils.assertEventsWillBeCalled( - token.contract, [{ - name: 'Burned', - data: { - operator: accounts[0], - from: accounts[0], - amount: web3.utils.toWei('1.22'), - data: null, - operatorData: null, - }, - }, { - name: 'Transfer', - data: { - from: accounts[0], - to: utils.zeroAddress, - amount: web3.utils.toWei('1.22'), - }, - }] - ); - - await token.contract.methods - .burn(web3.utils.toWei('1.22'), '0x') - .send({ gas: 300000, from: accounts[0] }); - - await utils.getBlock(web3); - - await utils.assertHookCalled( - web3, - sender, - token.contract.options.address, - accounts[0], - accounts[0], - utils.zeroAddress, - null, - null, - token.initialSupply + 10, - 0 - ); - await utils.assertTotalSupply( - web3, token, 10 * accounts.length + token.initialSupply - 1.22); - await utils.assertBalance( - web3, token, accounts[0], token.initialSupply + 8.78); - await utils.assertBalance(web3, token, sender.options.address, 0); - await eventsCalled; - }); - }); -};