From eebec1d3ce1a5a6e37f86964b09747184bf2fcf3 Mon Sep 17 00:00:00 2001 From: Nicholas Juntilla Date: Tue, 7 Nov 2017 14:50:24 -0800 Subject: [PATCH] initial --- .gitignore | 25 +++ LICENSE | 19 ++ README.md | 34 ++++ contracts/HumanStandardToken.sol | 57 ++++++ contracts/HumanStandardTokenFactory.sol | 62 +++++++ contracts/Migrations.sol | 23 +++ contracts/SampleRecipientSuccess.sol | 23 +++ contracts/SampleRecipientThrow.sol | 8 + contracts/StandardToken.sol | 59 +++++++ contracts/Token.sol | 48 +++++ ethpm.json | 14 ++ migrations/1_initial_migration.js | 5 + migrations/2_deploy_tokens.js | 5 + migrations/3_deploy_factory.js | 6 + package.json | 50 ++++++ test/humanStandardToken.js | 225 ++++++++++++++++++++++++ test/humanStandardTokenFactory.js | 11 ++ test/utils.js | 14 ++ truffle.js | 40 +++++ 19 files changed, 728 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 contracts/HumanStandardToken.sol create mode 100644 contracts/HumanStandardTokenFactory.sol create mode 100644 contracts/Migrations.sol create mode 100644 contracts/SampleRecipientSuccess.sol create mode 100644 contracts/SampleRecipientThrow.sol create mode 100644 contracts/StandardToken.sol create mode 100644 contracts/Token.sol create mode 100644 ethpm.json create mode 100644 migrations/1_initial_migration.js create mode 100644 migrations/2_deploy_tokens.js create mode 100644 migrations/3_deploy_factory.js create mode 100644 package.json create mode 100644 test/humanStandardToken.js create mode 100644 test/humanStandardTokenFactory.js create mode 100644 test/utils.js create mode 100644 truffle.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2777000 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# System Cruft +# ================================================ +.DS_Store +*.swp +*.swo + +# secret stuff +# ================================================ +secrets.json + +# Generated stuff +# ================================================ +build +node_modules +contracts/Standard_Token/config/development/contracts.json +contracts/Standard_Token/config/test/contracts.json +contracts/Standard_Token_Factory/config/development/contracts.json +contracts/Standard_Token_Factory/config/test/contracts.json +Token_Contracts/environments/test/contracts/ +secrets.json + + +# Other +# ================================================ +bower_components \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ce1dbc --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Tokens +[ ![Codeship Status for ConsenSys/Tokens](https://app.codeship.com/projects/ccf33380-4dfa-0135-cfa1-72c4965f7f14/status?branch=master)](https://app.codeship.com/projects/233433) + +This repo contains Solidity smart contract code to issue simple, standards-compliant tokens on Ethereum. It can be used to create any form of asset, currency, coin, hours, usage tokens, vunk, etc. + +The default is [StandardToken.sol](https://github.com/ConsenSys/Tokens/blob/master/contracts/StandardToken.sol) which ONLY implements the core ERC20 standard functionality [#20](https://github.com/ethereum/EIPs/issues/20). + +[HumanStandardToken.sol](https://github.com/ConsenSys/Tokens/blob/master/contracts/HumanStandardToken.sol) is an example of a token that has optional extras fit for your issuing your own tokens, to be mainly used by other humans. It includes: + +1. Initial Finite Supply (upon creation one specifies how much is minted). +2. In the absence of a token registry: Optional Decimal, Symbol & Name. +3. Optional approveAndCall() functionality to notify a contract if an approval() has occurred. + +There is a set of tests written for the HumanStandardToken.sol using the Truffle framework to do so. + +Standards allows other contract developers to easily incorporate your token into their application (governance, exchanges, games, etc). It will be updated as often as possible. + +## Testing + +```npm install``` + +For getting truffle-hdwallet-provider. Solidity tests have to still be written. + +Uses Truffle 3.x. + +## ethpm + +This is published under tokens at ethpm. + +## Contributing + +**Pull requests are welcome! Please keep standards discussions to the EIP repos.** + +When submitting a pull request, please do so to the `staging` branch. diff --git a/contracts/HumanStandardToken.sol b/contracts/HumanStandardToken.sol new file mode 100644 index 0000000..af1ec0a --- /dev/null +++ b/contracts/HumanStandardToken.sol @@ -0,0 +1,57 @@ +/* +This Token Contract implements the standard token functionality (https://github.com/ethereum/EIPs/issues/20) as well as the following OPTIONAL extras intended for use by humans. + +In other words. This is intended for deployment in something like a Token Factory or Mist wallet, and then used by humans. +Imagine coins, currencies, shares, voting weight, etc. +Machine-based, rapid creation of many tokens would not necessarily need these extra features or will be minted in other manners. + +1) Initial Finite Supply (upon creation one specifies how much is minted). +2) In the absence of a token registry: Optional Decimal, Symbol & Name. +3) Optional approveAndCall() functionality to notify a contract if an approval() has occurred. + +.*/ + +import "./StandardToken.sol"; + +pragma solidity ^0.4.8; + +contract HumanStandardToken is StandardToken { + + /* Public variables of the token */ + + /* + NOTE: + The following variables are OPTIONAL vanities. One does not have to include them. + They allow one to customise the token contract & in no way influences the core functionality. + Some wallets/interfaces might not even bother to look at this information. + */ + string public name; //fancy name: eg Simon Bucks + uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether. + string public symbol; //An identifier: eg SBX + string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme. + + function HumanStandardToken( + uint256 _initialAmount, + string _tokenName, + uint8 _decimalUnits, + string _tokenSymbol + ) public { + balances[msg.sender] = _initialAmount; // Give the creator all initial tokens + totalSupply = _initialAmount; // Update total supply + name = _tokenName; // Set the name for display purposes + decimals = _decimalUnits; // Amount of decimals for display purposes + symbol = _tokenSymbol; // Set the symbol for display purposes + } + + /* Approves and then calls the receiving contract */ + function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + + //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. + //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) + //it is assumed when one does this that the call *should* succeed, otherwise one would use vanilla approve instead. + require(_spender.call(bytes4(bytes32(keccak256("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)); + return true; + } +} diff --git a/contracts/HumanStandardTokenFactory.sol b/contracts/HumanStandardTokenFactory.sol new file mode 100644 index 0000000..74a6038 --- /dev/null +++ b/contracts/HumanStandardTokenFactory.sol @@ -0,0 +1,62 @@ +import "./HumanStandardToken.sol"; + +pragma solidity ^0.4.8; + +contract HumanStandardTokenFactory { + + mapping(address => address[]) public created; + mapping(address => bool) public isHumanToken; //verify without having to do a bytecode check. + bytes public humanStandardByteCode; + + function HumanStandardTokenFactory() public { + //upon creation of the factory, deploy a HumanStandardToken (parameters are meaningless) and store the bytecode provably. + address verifiedToken = createHumanStandardToken(10000, "Verify Token", 3, "VTX"); + humanStandardByteCode = codeAt(verifiedToken); + } + + //verifies if a contract that has been deployed is a Human Standard Token. + //NOTE: This is a very expensive function, and should only be used in an eth_call. ~800k gas + function verifyHumanStandardToken(address _tokenContract) public constant returns (bool) { + bytes memory fetchedTokenByteCode = codeAt(_tokenContract); + + if (fetchedTokenByteCode.length != humanStandardByteCode.length) { + return false; //clear mismatch + } + + //starting iterating through it if lengths match + for (uint i = 0; i < fetchedTokenByteCode.length; i ++) { + if (fetchedTokenByteCode[i] != humanStandardByteCode[i]) { + return false; + } + } + + return true; + } + + //for now, keeping this internal. Ideally there should also be a live version of this that any contract can use, lib-style. + //retrieves the bytecode at a specific address. + function codeAt(address _addr) internal constant returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr) + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } + + function createHumanStandardToken(uint256 _initialAmount, string _name, uint8 _decimals, string _symbol) public returns (address) { + + HumanStandardToken newToken = (new HumanStandardToken(_initialAmount, _name, _decimals, _symbol)); + created[msg.sender].push(address(newToken)); + isHumanToken[address(newToken)] = true; + newToken.transfer(msg.sender, _initialAmount); //the factory will own the created tokens. You must transfer them. + return address(newToken); + } +} diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol new file mode 100644 index 0000000..03ea8c3 --- /dev/null +++ b/contracts/Migrations.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.4.4; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + modifier restricted() { + if (msg.sender == owner) _; + } + + function Migrations() public { + owner = msg.sender; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/contracts/SampleRecipientSuccess.sol b/contracts/SampleRecipientSuccess.sol new file mode 100644 index 0000000..b93ac67 --- /dev/null +++ b/contracts/SampleRecipientSuccess.sol @@ -0,0 +1,23 @@ +/* +This is an example contract that helps test the functionality of the approveAndCall() functionality of HumanStandardToken.sol. +This one assumes successful receival of approval. +*/ +pragma solidity ^0.4.8; + +contract SampleRecipientSuccess { + /* A Generic receiving function for contracts that accept tokens */ + address public from; + uint256 public value; + address public tokenContract; + bytes public extraData; + + event ReceivedApproval(uint256 _value); + + function receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) public { + from = _from; + value = _value; + tokenContract = _tokenContract; + extraData = _extraData; + ReceivedApproval(_value); + } +} diff --git a/contracts/SampleRecipientThrow.sol b/contracts/SampleRecipientThrow.sol new file mode 100644 index 0000000..4a1d986 --- /dev/null +++ b/contracts/SampleRecipientThrow.sol @@ -0,0 +1,8 @@ +/* +This is an example contract that helps test the functionality of the approveAndCall() functionality of HumanStandardToken.sol. +This one will throw and thus needs to propagate the error up. +*/ +pragma solidity ^0.4.8; + +contract SampleRecipientThrow { +} diff --git a/contracts/StandardToken.sol b/contracts/StandardToken.sol new file mode 100644 index 0000000..1f857c0 --- /dev/null +++ b/contracts/StandardToken.sol @@ -0,0 +1,59 @@ +/* +You should inherit from StandardToken or, for a token like you would want to +deploy in something like Mist, see HumanStandardToken.sol. +(This implements ONLY the standard functions and NOTHING else. +If you deploy this, you won't have anything useful.) + +Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20 +.*/ +pragma solidity ^0.4.8; + +import "./Token.sol"; + +contract StandardToken is Token { + + uint256 constant MAX_UINT256 = 2**256 - 1; + + function transfer(address _to, uint256 _value) public returns (bool success) { + //Default assumes totalSupply can't be over max (2^256 - 1). + //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. + //Replace the if with this one instead. + //require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]); + require(balances[msg.sender] >= _value); + balances[msg.sender] -= _value; + balances[_to] += _value; + Transfer(msg.sender, _to, _value); + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { + //same as above. Replace this line with the following if you want to protect against wrapping uints. + //require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]); + uint256 allowance = allowed[_from][msg.sender]; + require(balances[_from] >= _value && allowance >= _value); + balances[_to] += _value; + balances[_from] -= _value; + if (allowance < MAX_UINT256) { + allowed[_from][msg.sender] -= _value; + } + Transfer(_from, _to, _value); + return true; + } + + function balanceOf(address _owner) constant public returns (uint256 balance) { + return balances[_owner]; + } + + function approve(address _spender, uint256 _value) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + return true; + } + + function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; +} diff --git a/contracts/Token.sol b/contracts/Token.sol new file mode 100644 index 0000000..9cdf6b6 --- /dev/null +++ b/contracts/Token.sol @@ -0,0 +1,48 @@ +// Abstract contract for the full ERC 20 Token standard +// https://github.com/ethereum/EIPs/issues/20 +pragma solidity ^0.4.8; + +contract Token { + /* This is a slight change to the ERC20 base standard. + function totalSupply() constant returns (uint256 supply); + is replaced with: + uint256 public totalSupply; + This automatically creates a getter function for the totalSupply. + This is moved to the base contract since public getter functions are not + currently recognised as an implementation of the matching abstract + function by the compiler. + */ + /// total amount of tokens + uint256 public totalSupply; + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) public constant returns (uint256 balance); + + /// @notice send `_value` token to `_to` from `msg.sender` + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transfer(address _to, uint256 _value) public returns (bool success); + + /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); + + /// @notice `msg.sender` approves `_spender` to spend `_value` tokens + /// @param _spender The address of the account able to transfer the tokens + /// @param _value The amount of tokens to be approved for transfer + /// @return Whether the approval was successful or not + function approve(address _spender, uint256 _value) public returns (bool success); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address _owner, address _spender) public constant returns (uint256 remaining); + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} diff --git a/ethpm.json b/ethpm.json new file mode 100644 index 0000000..fcfe6a1 --- /dev/null +++ b/ethpm.json @@ -0,0 +1,14 @@ +{ + "package_name": "tokens", + "version": "0.0.3", + "description": "Ethereum Token Contracts", + "authors": [ + "Simon de la Rouviere ", + "Joseph Chow " + ], + "keywords": [ + "tokens", + "consensys" + ], + "license": "MIT" +} diff --git a/migrations/1_initial_migration.js b/migrations/1_initial_migration.js new file mode 100644 index 0000000..e627059 --- /dev/null +++ b/migrations/1_initial_migration.js @@ -0,0 +1,5 @@ +const Migrations = artifacts.require(`./Migrations.sol`) + +module.exports = (deployer) => { + deployer.deploy(Migrations) +} diff --git a/migrations/2_deploy_tokens.js b/migrations/2_deploy_tokens.js new file mode 100644 index 0000000..28aa164 --- /dev/null +++ b/migrations/2_deploy_tokens.js @@ -0,0 +1,5 @@ +const HumanStandardToken = artifacts.require(`./HumanStandardToken.sol`) + +module.exports = (deployer) => { + deployer.deploy(HumanStandardToken) +} diff --git a/migrations/3_deploy_factory.js b/migrations/3_deploy_factory.js new file mode 100644 index 0000000..056c742 --- /dev/null +++ b/migrations/3_deploy_factory.js @@ -0,0 +1,6 @@ +const HumanStandardTokenFactory = + artifacts.require(`./HumanStandardTokenFactory.sol`) + +module.exports = (deployer) => { + deployer.deploy(HumanStandardTokenFactory) +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f784b47 --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "erc20-tokens", + "version": "0.0.1", + "description": "Ethereum Token Contracts", + "main": "truffle.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "standard && truffle test", + "lint": "standard --fix" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ConsenSys/Tokens.git" + }, + "keywords": [ + "ethereum" + ], + "authors": [ + "Simon de la Rouviere ", + "Joseph Chow " + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/ConsenSys/Tokens/issues" + }, + "homepage": "https://github.com/ConsenSys/Tokens#readme", + "dependencies": { + "truffle-hdwallet-provider": "0.0.3" + }, + "devDependencies": { + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-node": "^5.1.0", + "eslint-plugin-react": "^7.1.0", + "eslint-plugin-standard": "^3.0.1", + "standard": "^10.0.2" + }, + "standard": { + "globals": [ + "assert", + "it", + "before", + "beforeEach", + "artifacts", + "contract", + "web3" + ] + } +} diff --git a/test/humanStandardToken.js b/test/humanStandardToken.js new file mode 100644 index 0000000..07b315b --- /dev/null +++ b/test/humanStandardToken.js @@ -0,0 +1,225 @@ +const expectThrow = require('./utils').expectThrow +const HumanStandardTokenAbstraction = artifacts.require('HumanStandardToken') +const SampleRecipientSuccess = artifacts.require('SampleRecipientSuccess') +const SampleRecipientThrow = artifacts.require('SampleRecipientThrow') +let HST + +contract('HumanStandardToken', function (accounts) { + beforeEach(async () => { + HST = await HumanStandardTokenAbstraction.new(10000, 'Simon Bucks', 1, 'SBX', {from: accounts[0]}) + }) + + it('creation: should create an initial balance of 10000 for the creator', async () => { + const balance = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balance.toNumber(), 10000) + }) + + it('creation: test correct setting of vanity information', async () => { + const name = await HST.name.call() + assert.strictEqual(name, 'Simon Bucks') + + const decimals = await HST.decimals.call() + assert.strictEqual(decimals.toNumber(), 1) + + const symbol = await HST.symbol.call() + assert.strictEqual(symbol, 'SBX') + }) + + it('creation: should succeed in creating over 2^256 - 1 (max) tokens', async () => { + // 2^256 - 1 + let HST2 = await HumanStandardTokenAbstraction.new('115792089237316195423570985008687907853269984665640564039457584007913129639935', 'Simon Bucks', 1, 'SBX', {from: accounts[0]}) + const totalSupply = await HST2.totalSupply() + const match = totalSupply.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77') + assert(match, 'result is not correct') + }) + + // TRANSERS + // normal transfers without approvals + it('transfers: ether transfer should be reversed.', async () => { + const balanceBefore = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balanceBefore.toNumber(), 10000) + + web3.eth.sendTransaction({from: accounts[0], to: HST.address, value: web3.toWei('10', 'Ether')}, async (err, res) => { + expectThrow(new Promise((resolve, reject) => { + if (err) reject(err) + resolve(res) + })) + + const balanceAfter = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balanceAfter.toNumber(), 10000) + }) + }) + + it('transfers: should transfer 10000 to accounts[1] with accounts[0] having 10000', async () => { + await HST.transfer(accounts[1], 10000, {from: accounts[0]}) + const balance = await HST.balanceOf.call(accounts[1]) + assert.strictEqual(balance.toNumber(), 10000) + }) + + it('transfers: should fail when trying to transfer 10001 to accounts[1] with accounts[0] having 10000', () => { + return expectThrow(HST.transfer.call(accounts[1], 10001, {from: accounts[0]})) + }) + + it('transfers: should handle zero-transfers normally', async () => { + assert(await HST.transfer.call(accounts[1], 0, {from: accounts[0]}), 'zero-transfer has failed') + }) + + // NOTE: testing uint256 wrapping is impossible in this standard token since you can't supply > 2^256 -1 + // todo: transfer max amounts + + // APPROVALS + it('approvals: msg.sender should approve 100 to accounts[1]', async () => { + await HST.approve(accounts[1], 100, {from: accounts[0]}) + const allowance = await HST.allowance.call(accounts[0], accounts[1]) + assert.strictEqual(allowance.toNumber(), 100) + }) + + it('approvals: msg.sender should approve 100 to SampleRecipient and then NOTIFY SampleRecipient. It should succeed.', async () => { + let SRS = await SampleRecipientSuccess.new({from: accounts[0]}) + await HST.approveAndCall(SRS.address, 100, '0x42', {from: accounts[0]}) + const allowance = await HST.allowance.call(accounts[0], SRS.address) + assert.strictEqual(allowance.toNumber(), 100) + + const value = await SRS.value.call() + assert.strictEqual(value.toNumber(), 100) + }) + + it('approvals: msg.sender should approve 100 to SampleRecipient and then NOTIFY SampleRecipient and throw.', async () => { + let SRS = await SampleRecipientThrow.new({from: accounts[0]}) + return expectThrow(HST.approveAndCall.call(SRS.address, 100, '0x42', {from: accounts[0]})) + }) + + // bit overkill. But is for testing a bug + it('approvals: msg.sender approves accounts[1] of 100 & withdraws 20 once.', async () => { + const balance0 = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balance0.toNumber(), 10000) + + await HST.approve(accounts[1], 100, {from: accounts[0]}) // 100 + const balance2 = await HST.balanceOf.call(accounts[2]) + assert.strictEqual(balance2.toNumber(), 0, 'balance2 not correct') + + HST.transferFrom.call(accounts[0], accounts[2], 20, {from: accounts[1]}) + await HST.allowance.call(accounts[0], accounts[1]) + await HST.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]}) // -20 + const allowance01 = await HST.allowance.call(accounts[0], accounts[1]) + assert.strictEqual(allowance01.toNumber(), 80) // =80 + + const balance22 = await HST.balanceOf.call(accounts[2]) + assert.strictEqual(balance22.toNumber(), 20) + + const balance02 = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balance02.toNumber(), 9980) + }) + + // should approve 100 of msg.sender & withdraw 50, twice. (should succeed) + it('approvals: msg.sender approves accounts[1] of 100 & withdraws 20 twice.', async () => { + await HST.approve(accounts[1], 100, {from: accounts[0]}) + const allowance01 = await HST.allowance.call(accounts[0], accounts[1]) + assert.strictEqual(allowance01.toNumber(), 100) + + await HST.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]}) + const allowance012 = await HST.allowance.call(accounts[0], accounts[1]) + assert.strictEqual(allowance012.toNumber(), 80) + + const balance2 = await HST.balanceOf.call(accounts[2]) + assert.strictEqual(balance2.toNumber(), 20) + + const balance0 = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balance0.toNumber(), 9980) + + // FIRST tx done. + // onto next. + await HST.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]}) + const allowance013 = await HST.allowance.call(accounts[0], accounts[1]) + assert.strictEqual(allowance013.toNumber(), 60) + + const balance22 = await HST.balanceOf.call(accounts[2]) + assert.strictEqual(balance22.toNumber(), 40) + + const balance02 = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balance02.toNumber(), 9960) + }) + + // should approve 100 of msg.sender & withdraw 50 & 60 (should fail). + it('approvals: msg.sender approves accounts[1] of 100 & withdraws 50 & 60 (2nd tx should fail)', async () => { + await HST.approve(accounts[1], 100, {from: accounts[0]}) + const allowance01 = await HST.allowance.call(accounts[0], accounts[1]) + assert.strictEqual(allowance01.toNumber(), 100) + + await HST.transferFrom(accounts[0], accounts[2], 50, {from: accounts[1]}) + const allowance012 = await HST.allowance.call(accounts[0], accounts[1]) + assert.strictEqual(allowance012.toNumber(), 50) + + const balance2 = await HST.balanceOf.call(accounts[2]) + assert.strictEqual(balance2.toNumber(), 50) + + const balance0 = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balance0.toNumber(), 9950) + + // FIRST tx done. + // onto next. + await expectThrow(HST.transferFrom.call(accounts[0], accounts[2], 60, {from: accounts[1]})) + }) + + it('approvals: attempt withdrawal from account with no allowance (should fail)', function () { + return expectThrow(HST.transferFrom.call(accounts[0], accounts[2], 60, {from: accounts[1]})) + }) + + it('approvals: allow accounts[1] 100 to withdraw from accounts[0]. Withdraw 60 and then approve 0 & attempt transfer.', async () => { + HST.approve(accounts[1], 100, {from: accounts[0]}) + HST.transferFrom(accounts[0], accounts[2], 60, {from: accounts[1]}) + HST.approve(accounts[1], 0, {from: accounts[0]}) + await expectThrow(HST.transferFrom.call(accounts[0], accounts[2], 10, {from: accounts[1]})) + }) + + it('approvals: approve max (2^256 - 1)', async () => { + await HST.approve(accounts[1], '115792089237316195423570985008687907853269984665640564039457584007913129639935', {from: accounts[0]}) + const allowance = await HST.allowance(accounts[0], accounts[1]) + assert(allowance.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77')) + }) + + // should approve max of msg.sender & withdraw 20 without changing allowance (should succeed). + it('approvals: msg.sender approves accounts[1] of max (2^256 - 1) & withdraws 20', async () => { + const balance0 = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balance0.toNumber(), 10000) + + const max = '1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77' + await HST.approve(accounts[1], max, {from: accounts[0]}) + const balance2 = await HST.balanceOf.call(accounts[2]) + assert.strictEqual(balance2.toNumber(), 0, 'balance2 not correct') + + await HST.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]}) + const allowance01 = await HST.allowance.call(accounts[0], accounts[1]) + assert(allowance01.equals(max)) + + const balance22 = await HST.balanceOf.call(accounts[2]) + assert.strictEqual(balance22.toNumber(), 20) + + const balance02 = await HST.balanceOf.call(accounts[0]) + assert.strictEqual(balance02.toNumber(), 9980) + }) + + it('events: should fire Transfer event properly', async () => { + const res = await HST.transfer(accounts[1], '2666', {from: accounts[0]}) + const transferLog = res.logs.find(element => element.event.match('Transfer')) + assert.strictEqual(transferLog.args._from, accounts[0]) + assert.strictEqual(transferLog.args._to, accounts[1]) + assert.strictEqual(transferLog.args._value.toString(), '2666') + }) + + it('events: should fire Transfer event normally on a zero transfer', async () => { + const res = await HST.transfer(accounts[1], '0', {from: accounts[0]}) + const transferLog = res.logs.find(element => element.event.match('Transfer')) + assert.strictEqual(transferLog.args._from, accounts[0]) + assert.strictEqual(transferLog.args._to, accounts[1]) + assert.strictEqual(transferLog.args._value.toString(), '0') + }) + + it('events: should fire Approval event properly', async () => { + const res = await HST.approve(accounts[1], '2666', {from: accounts[0]}) + const approvalLog = res.logs.find(element => element.event.match('Approval')) + assert.strictEqual(approvalLog.args._owner, accounts[0]) + assert.strictEqual(approvalLog.args._spender, accounts[1]) + assert.strictEqual(approvalLog.args._value.toString(), '2666') + }) +}) diff --git a/test/humanStandardTokenFactory.js b/test/humanStandardTokenFactory.js new file mode 100644 index 0000000..16c42c1 --- /dev/null +++ b/test/humanStandardTokenFactory.js @@ -0,0 +1,11 @@ +const HumanStandardTokenFactory = artifacts.require('HumanStandardTokenFactory') + +contract('HumanStandardTokenFactory', function (accounts) { + it('Verify a Human Standard Token once deployed using both verification functions.', async () => { + const factory = await HumanStandardTokenFactory.new() + const newTokenAddr = await factory.createHumanStandardToken.call(100000, 'Simon Bucks', 2, 'SBX', {from: accounts[0]}) + await factory.createHumanStandardToken(100000, 'Simon Bucks', 2, 'SBX', {from: accounts[0]}) + const res = await factory.verifyHumanStandardToken.call(newTokenAddr, {from: accounts[0]}) + assert(res, 'Could not verify the token.') + }) +}) diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..5638805 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,14 @@ +module.exports = { + expectThrow: async promise => { + try { + await promise + } catch (error) { + const invalidJump = error.message.search('invalid JUMP') >= 0 + const invalidOpcode = error.message.search('invalid opcode') >= 0 + const outOfGas = error.message.search('out of gas') >= 0 + assert(invalidJump || invalidOpcode || outOfGas, "Expected throw, got '" + error + "' instead") + return + } + assert.fail('Expected throw not received') + } +} diff --git a/truffle.js b/truffle.js new file mode 100644 index 0000000..aa5ca5d --- /dev/null +++ b/truffle.js @@ -0,0 +1,40 @@ +const HDWalletProvider = require('truffle-hdwallet-provider') +const fs = require('fs') + +// First read in the secrets.json to get our mnemonic +let secrets +let mnemonic +if (fs.existsSync('secrets.json')) { + secrets = JSON.parse(fs.readFileSync('secrets.json', 'utf8')) + mnemonic = secrets.mnemonic +} else { + console.log('No secrets.json found. If you are trying to publish EPM ' + + 'this will fail. Otherwise, you can ignore this message!') + mnemonic = '' +} + +module.exports = { + networks: { + live: { + network_id: 1 // Ethereum public network + // optional config values + // host - defaults to "localhost" + // port - defaults to 8545 + // gas + // gasPrice + // from - default address to use for any transaction Truffle makes during migrations + }, + ropsten: { + provider: new HDWalletProvider(mnemonic, 'https://ropsten.infura.io'), + network_id: '3' + }, + testrpc: { + network_id: 'default' + }, + development: { // truffle test hardcodes the "test" network. + host: 'localhost', + port: '8545', + network_id: 'default' + } + } +}