├── .gitignore ├── LICENSE ├── README.md ├── erc20.lll ├── erc20_benchmark ├── StandardToken.sol ├── benchmark.js ├── erc20.lll ├── erc20_abi.json ├── erc20_lll.hex ├── erc20_lll_opt.hex ├── erc20_solidity.hex └── erc20_solidity_opt.hex ├── erc20_test ├── 00_start_node ├── erc20.js ├── erc20_abi.json └── erc20_evm.dat └── images ├── ERC20_gas_comparison_chart.png └── ERC20_gas_comparison_table.png /.gitignore: -------------------------------------------------------------------------------- 1 | erc20_test/.node-* 2 | erc20_test/node_modules* 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ben Edgington 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ---- 2 | 3 | LLL Coin is now [live on Mainnet](https://etherscan.io/token/0xd25e81504e0aea1e1343d61581296bc3c460978b)! 4 | 5 | Send me a note (my email address is in the source code) with some comment about your interest in LLL and a Mainnet address, and I will send you a coin :-) 6 | 7 | ---- 8 | 9 | 10 | # LLL 11 | 12 | * [Introduction](#introduction) 13 | * [ERC20 LLL contract](#erc20lll-an-implementation-of-ethereum-erc20-tokens-in-lll) 14 | * [Benchmarking](#benchmarking-the-erc20-contract-against-solidity) 15 | 16 | 17 | ## Introduction 18 | 19 | According to the Ethereum [Homestead 20 | Documentation](http://www.ethdocs.org/en/latest/contracts-and-transactions/contracts.html#id4), 21 | 22 | > Lisp Like Language (LLL) is a low level language similar to Assembly. It is 23 | meant to be very simple and minimalistic; essentially just a tiny wrapper 24 | over coding in EVM directly. 25 | 26 | LLL is one of the three living languages for Ethereum contract creation, 27 | alongside Solidity and Serpent/Viper (which itself compiles to LLL). If you 28 | have the Solidity compiler, then you may well have LLL already. It's bundled 29 | with some of the `solc` releases as `lllc`. 30 | 31 | It's fair to say that LLL is lagging substantially behind Solidity in 32 | popularity for contract creation. But Daniel Ellison of ConsenSys is on a 33 | [mission](https://media.consensys.net/@zigguratt) to revive 34 | it. [Here](http://blog.syrinx.net/the-resurrection-of-lll-part-1/) as well. 35 | 36 | LLL is a low-level language, just one step above Ethereum Virtual Machine (EVM) 37 | bytecode. Why would we choose to go back to the 1970s in programming terms 38 | when we have all the object-oriented joys of Solidity at our disposal? 39 | 40 | Well, the EVM is a severely resource-constrained environment. Execution, memory 41 | and storage all have significant costs. For all its popularity, the Solidity 42 | compiler is not great at producing very efficient code. The bytecode generated 43 | by Solidity is full of redundancies, bloat, pointless jumps and other 44 | inefficiencies that cause steam come out of my ears, but, much more 45 | importantly, unnecessarily high gas usage. 46 | 47 | The low-level nature of LLL reminds you constantly that you are dealing with a 48 | resource-constrained environment. The LLL compiler doesn't auto-generate any of 49 | the junk you see in Solidity bytecode. This typically results in LLL bytecode 50 | being substantially more compact and efficient to run than Solidity bytecode. 51 | It is for this reason that the [deployed ENS 52 | registry](https://etherscan.io/address/0x314159265dd8dbb310642f98f50c066173c1259b#code) 53 | was [written in 54 | LLL](https://github.com/ethereum/ens/blob/master/contracts/ENS.lll). 55 | 56 | Of course, there are downsides. High-level languages exist for a reason. But 57 | it's not as bad as you might imagine. After only a week's spare time dabbling 58 | with LLL I was able to code up the `erc20.lll` example here. And I'm not any 59 | kind of developer by profession (which may be apparent from the code). 60 | 61 | 62 | ## *erc20.lll*: An implementation of Ethereum ERC20 tokens in LLL 63 | 64 | A fully functional implementation of the [ERC20 token 65 | standard](https://theethereum.wiki/w/index.php/ERC20_Token_Standard). 66 | 67 | I know it looks a bit long-winded, but of the (original) 349 lines, 84 are 68 | blank, 138 are comment, and only 127 are actual code. These Lisp-like languages 69 | lend themselves to sparse layout and lots of whitespace. I like this. It makes 70 | me feel calm. Actually, quite a lot of the preamble is re-usable and could be 71 | moved to an include file (yes, LLL [has a 72 | mechanism](http://lll-docs.readthedocs.io/en/latest/lll_reference.html#including-files-include) 73 | for this). 74 | 75 | It is possible to write LLL code [much more 76 | compactly](https://github.com/ethereum/cpp-ethereum/wiki/LLL-Examples-for-PoC-5/04fae9e627ac84d771faddcf60098ad09230ab58), 77 | but that makes my eyes hurt, and there's really no efficiency advantage in the 78 | compiled code. 79 | 80 | 81 | ## Benchmarking the ERC20 contract against Solidity 82 | 83 | If one of the premises is that LLL compiles to more efficient code than 84 | Solidity, I suppose we ought to do some benchmarking. 85 | 86 | Some things to bear in mind: 87 | 88 | * The ERC20 contract is really, really simple: there isn't that much 89 | opportunity for LLL to shine. 90 | 91 | * The gas costs for the main functions are dominated by the enormous gas usage 92 | of `SSTORE`, which are identical for both the LLL and the Solidity versions. 93 | 94 | * The Solidity version is based on the [StandardToken 95 | contract](https://github.com/ethereum/solidity/blob/develop/std/StandardToken.sol) 96 | distributed with the Solidity source code. I've improved and modified it to 97 | bring it up to the functionality of my LLL contract, but the input 98 | validation of the Solidity contract remains a little weaker than my LLL 99 | version. 100 | 101 | I've included both the optimised and unoptimised Solidity code in the 102 | benchmark; the LLL code generated is the same whether optimised or not 103 | (i.e. the optimiser can't improve it, which is noteworthy in and of itself). 104 | 105 | Benchmarking environment is as follows, 106 | 107 | ``` 108 | > /opt/node/bin/testrpc 109 | EthereumJS TestRPC v4.0.1 (ganache-core: 1.0.1) 110 | 111 | > solc --version 112 | solc, the solidity compiler commandline interface 113 | Version: 0.4.14-develop.2017.7.9+commit.027cad77.mod.Linux.g++ 114 | 115 | > lllc --version 116 | LLLC, the Lovely Little Language Compiler 117 | Version: 0.4.14-develop.2017.7.9+commit.027cad77.mod.Linux.g++ 118 | ``` 119 | 120 | ### Deployment costs 121 | 122 | This table shows the code sizes and deployment costs for each version. LLL 123 | scores a clear win here. 124 | 125 | | | Size (bytes) | Deployment Gas | 126 | |--------------|-------------:|---------------:| 127 | | Solidity | 2879 | 813908 | 128 | | Solidity Opt | 1730 | 515759 | 129 | | LLL | 855 | 291859 | 130 | 131 | 132 | ### Usage costs 133 | 134 | In the chart below I've subtracted the following high essential fixed costs for 135 | each function which are common to both contracts and are unavoidable: 136 | 137 | * The cost of the sendTransaction operation (21000). 138 | 139 | * The costs (and refunds) from the `SSTORE` operations that persist the data. 140 | 141 | * The cost of transferring the call data to the contract (identical for both). 142 | 143 | The point is to understand the overheads entailed by the choice of one or other 144 | of the languages, over and above the unavoidable costs that they have in 145 | common. We are not benchmarking the EVM, we are benchmarking the relative 146 | performance of the languages. 147 | 148 | ![Comparison of gas costs: bar chart](images/ERC20_gas_comparison_chart.png) 149 | 150 | Full details of the calculations are shown in the table below, and are also 151 | available on [Google 152 | Drive](https://docs.google.com/spreadsheets/d/1Kdwbw_0mIjakCPfk_rcd5PjIbFJ0ISWS14ZQ_oeUWnE/edit?usp=sharing) along with some explanatory notes. 153 | 154 | ![Comparison of gas costs: table](images/ERC20_gas_comparison_table.png) 155 | 156 | Of course, `name()`, `symbol()`, `decimals()`, `totalSupply()`, `balanceOf()` 157 | and `allowance()` are all constant functions, and are cost-less to evaluate 158 | off-blockchain. To make it more interesting, the chart is based on the cost of 159 | calling these functions from another contract (i.e. I invoked them with 160 | `web3.eth.sendTransaction` rather than `web3.eth.call`). 161 | 162 | ### Conclusion 163 | 164 | There are probably a couple of valid responses to all this. On the one hand, 165 | overall, using LLL saves about 1.5% of the gas used by a ``transfer()`` 166 | operation when compared to optimised Solidity. Big deal, right? 167 | 168 | On the other hand, given the simplicity of this contract and the unavoidable 169 | overheads of dealing with permanent storage, I think LLL acquits itself pretty 170 | well. For the parts that the programmer and compiler actually have control 171 | over, LLL performs up to four times more efficiently than optimised Solidity. 172 | It is cheaper and smaller across the board. As we implement more complex 173 | functions the gains can only be expected to be greater. 174 | 175 | Comparing the performance of the optimised and the unoptimised Solidity 176 | contracts, it's clear that changing the programming paradigm to LLL is vastly 177 | more effective than relying on the compiler to do the work for you. 178 | 179 | Where LLL really shines is in the code size and deployment costs. Given that a 180 | contract's code will be on the blockchain in perpetuity, stored and executed on 181 | thousands and thousands of individual nodes worldwide, minimising its footprint 182 | must be a good thing _per se_. 183 | 184 | #### Reflections on coding in LLL 185 | 186 | This is obviously pretty personal, but for me coding in LLL seems like "cutting 187 | with the grain" of the EVM. The transparency of the language makes it easy to 188 | reason about resource usage, security, efficiency and so on. 189 | 190 | I haven't done enough Solidity coding to form a firm view on it. While it 191 | certainly makes some things easier to code - by no means everything - that 192 | comes at a cost both in simple gas terms, but also in understanding of "what's 193 | going on under the hood". In the security critical environment of the 194 | blockchain, heavily resource constrained by the EVM, is that really a positive 195 | thing? 196 | -------------------------------------------------------------------------------- /erc20.lll: -------------------------------------------------------------------------------- 1 | ;;; --------------------------------------------------------------------------- 2 | ;;; Implementation of an ERC20 Token contract in LLL 3 | ;;; 4 | ;;; Ben Edgington - ben@benjaminion.xyz 5 | ;;; 6 | 7 | (seq 8 | 9 | ;; -------------------------------------------------------------------------- 10 | ;; CONSTANTS 11 | 12 | ;; Token parameters. 13 | ;; 0x40 is a "magic number" - the text of the string is placed here 14 | ;; when returning the string to the caller. See return-string below. 15 | (def 'token-name-string (lit 0x40 "LLL Coin - love to code in LLL.")) 16 | (def 'token-symbol-string (lit 0x40 "LLL")) 17 | (def 'token-decimals 0) 18 | (def 'token-supply 100) ; 100 total tokens 19 | 20 | ;; Booleans 21 | (def 'false 0) 22 | (def 'true 1) 23 | 24 | ;; Memory layout. 25 | (def 'mem-ret 0x00) ; Fixed due to compiler macro for return. 26 | (def 'mem-func 0x00) ; No conflict with mem-ret, so re-use. 27 | (def 'mem-keccak 0x00) ; No conflict with mem-func or mem-ret, so re-use. 28 | (def 'scratch0 0x20) 29 | (def 'scratch1 0x40) 30 | 31 | ;; Precomputed function IDs. 32 | (def 'get-name 0x06fdde03) ; name() 33 | (def 'get-symbol 0x95d89b41) ; symbol() 34 | (def 'get-decimals 0x313ce567) ; decimals() 35 | (def 'get-total-supply 0x18160ddd) ; totalSupply() 36 | (def 'get-balance-of 0x70a08231) ; balanceOf(address) 37 | (def 'transfer 0xa9059cbb) ; transfer(address,uint256) 38 | (def 'transfer-from 0x23b872dd) ; transferFrom(address,address,uint256) 39 | (def 'approve 0x095ea7b3) ; approve(address,uint256) 40 | (def 'get-allowance 0xdd62ed3e) ; allowance(address,address) 41 | 42 | ;; Event IDs 43 | (def 'transfer-event-id ; Transfer(address,address,uint256) 44 | 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) 45 | 46 | (def 'approval-event-id ; Approval(address,address,uint256) 47 | 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925) 48 | 49 | ;; -------------------------------------------------------------------------- 50 | ;; UTILITIES 51 | 52 | ;; -------------------------------------------------------------------------- 53 | ;; The following define the key data-structures: 54 | ;; - balance(addr) => value 55 | ;; - allowance(addr,addr) => value 56 | 57 | ;; Balances are stored at s[owner_addr]. 58 | (def 'balance (address) address) 59 | 60 | ;; Allowances are stored at s[owner_addr + keccak256(spender_addr)] 61 | ;; We use a crypto function here to avoid any situation where 62 | ;; approve(me, spender) can be abused to do approve(target, me). 63 | (def 'allowance (owner spender) 64 | (seq 65 | (mstore mem-keccak spender) 66 | (add owner (keccak256 mem-keccak 0x20)))) 67 | 68 | ;; -------------------------------------------------------------------------- 69 | ;; For convenience we have macros to refer to function arguments 70 | 71 | (def 'arg1 (calldataload 0x04)) 72 | (def 'arg2 (calldataload 0x24)) 73 | (def 'arg3 (calldataload 0x44)) 74 | 75 | ;; -------------------------------------------------------------------------- 76 | ;; Revert is a soft return that does not consume the remaining gas. 77 | ;; We use it when rejecting invalid user input. 78 | ;; 79 | ;; Note: The REVERT opcode will be implemented in Metropolis (EIP 140). 80 | ;; Meanwhile it just causes an invalid instruction exception (similar 81 | ;; to a "throw" in Solidity). When fully implemented, Revert could be 82 | ;; use to return error codes, or even messages. 83 | 84 | (def 'revert () (revert 0 0)) 85 | 86 | ;; -------------------------------------------------------------------------- 87 | ;; Macro for returning string names. 88 | ;; Compliant with the ABI format for strings. 89 | 90 | (def 'return-string (string-literal) 91 | (seq 92 | (mstore 0x00 0x20) ; Points to our string's memory location 93 | (mstore 0x20 string-literal) ; Length. String itself is copied to 0x40. 94 | (return 0x00 (& (+ (mload 0x20) 0x5f) (~ 0x1f))))) 95 | ; Round return up to 32 byte boundary 96 | 97 | ;; -------------------------------------------------------------------------- 98 | ;; Convenience macro for raising Events 99 | 100 | (def 'event3 (id addr1 addr2 value) 101 | (seq 102 | (mstore scratch0 value) 103 | (log3 scratch0 0x20 id addr1 addr2))) 104 | 105 | ;; -------------------------------------------------------------------------- 106 | ;; Determines whether the stored function ID matches a known 107 | ;; function hash and executes if so. 108 | ;; @param function-hash The four-byte hash of a known function signature. 109 | ;; @param code-body The code to run in the case of a match. 110 | 111 | (def 'function (function-hash code-body) 112 | (when (= (mload mem-func) function-hash) 113 | code-body)) 114 | 115 | ;; -------------------------------------------------------------------------- 116 | ;; Gets the function ID and stores it in memory for reference. 117 | ;; The function ID is in the leftmost four bytes of the call data. 118 | 119 | (def 'uses-functions 120 | (seq 121 | (mstore mem-func 0) 122 | (calldatacopy (+ mem-func 28) 0x00 4))) 123 | 124 | ;; -------------------------------------------------------------------------- 125 | ;; GUARDS 126 | 127 | ;; -------------------------------------------------------------------------- 128 | ;; Checks that ensure that each function is called with the right 129 | ;; number of arguments. For one thing this addresses the "ERC20 130 | ;; short address attack". For another, it stops me making 131 | ;; mistakes while testing. We use these only on the non-constant functions. 132 | 133 | (def 'has-one-arg (unless (= 0x24 (calldatasize)) (revert))) 134 | (def 'has-two-args (unless (= 0x44 (calldatasize)) (revert))) 135 | (def 'has-three-args (unless (= 0x64 (calldatasize)) (revert))) 136 | 137 | ;; -------------------------------------------------------------------------- 138 | ;; Check that addresses have only 160 bits and revert if not. 139 | ;; We use these input type-checks on the non-constant functions. 140 | 141 | (def 'is-address (addr) 142 | (when 143 | (shr addr 160) 144 | (revert))) 145 | 146 | ;; -------------------------------------------------------------------------- 147 | ;; Check that transfer values are smaller than total supply and 148 | ;; revert if not. This should effectively exclude negative values. 149 | 150 | (def 'is-value (value) 151 | (when (> value token-supply) (revert))) 152 | 153 | ;; -------------------------------------------------------------------------- 154 | ;; Will revert if sent any Ether. We use the macro immediately so as 155 | ;; to abort if sent any Ether during contract deployment. 156 | 157 | (def 'not-payable 158 | (when (callvalue) (revert))) 159 | 160 | not-payable 161 | 162 | ;; -------------------------------------------------------------------------- 163 | ;; INITIALISATION 164 | ;; 165 | ;; Assign all tokens initially to the owner of the contract. 166 | 167 | (sstore (balance (caller)) token-supply) 168 | 169 | ;; -------------------------------------------------------------------------- 170 | ;; CONTRACT CODE 171 | 172 | (returnlll 173 | (seq not-payable uses-functions 174 | 175 | ;; ---------------------------------------------------------------------- 176 | ;; Getter for the name of the token. 177 | ;; @abi name() constant returns (string) 178 | ;; @return The token name as a string. 179 | 180 | (function get-name 181 | (return-string token-name-string)) 182 | 183 | ;; ---------------------------------------------------------------------- 184 | ;; Getter for the symbol of the token. 185 | ;; @abi symbol() constant returns (string) 186 | ;; @return The token symbol as a string. 187 | 188 | (function get-symbol 189 | (return-string token-symbol-string)) 190 | 191 | ;; ---------------------------------------------------------------------- 192 | ;; Getter for the number of decimals assigned to the token. 193 | ;; @abi decimals() constant returns (uint256) 194 | ;; @return The token decimals. 195 | 196 | (function get-decimals 197 | (return token-decimals)) 198 | 199 | ;; ---------------------------------------------------------------------- 200 | ;; Getter for the total token supply. 201 | ;; @abi totalSupply() constant returns (uint256) 202 | ;; @return The token supply. 203 | 204 | (function get-total-supply 205 | (return token-supply)) 206 | 207 | ;; ---------------------------------------------------------------------- 208 | ;; Returns the account balance of another account. 209 | ;; @abi balanceOf(address) constant returns (uint256) 210 | ;; @param owner The address of the account's owner. 211 | ;; @return The account balance. 212 | 213 | (function get-balance-of 214 | (seq 215 | 216 | (def 'owner arg1) 217 | 218 | (return (sload (balance owner))))) 219 | 220 | ;; ---------------------------------------------------------------------- 221 | ;; Transfers _value amount of tokens to address _to. The command 222 | ;; should throw if the _from account balance has not enough 223 | ;; tokens to spend. 224 | ;; @abi transfer(address, uint256) returns (bool) 225 | ;; @param to The account to receive the tokens. 226 | ;; @param value The quantity of tokens to transfer. 227 | ;; @return Success (true). Other outcomes result in a Revert. 228 | 229 | (function transfer 230 | (seq has-two-args (is-address arg1) (is-value arg2) 231 | 232 | (def 'to arg1) 233 | (def 'value arg2) 234 | 235 | (when value ; value == 0 is a no-op 236 | (seq 237 | 238 | ;; The caller's balance. Save in memory for efficiency. 239 | (mstore scratch0 (sload (balance (caller)))) 240 | 241 | ;; Revert if the caller's balance is not sufficient. 242 | (when (> value (mload scratch0)) 243 | (revert)) 244 | 245 | ;; Make the transfer 246 | ;; It would be good to check invariants (sum of balances). 247 | (sstore (balance (caller)) (- (mload scratch0) value)) 248 | (sstore (balance to) (+ (sload (balance to)) value)) 249 | 250 | ;; Event - Transfer(address,address,uint256) 251 | (event3 transfer-event-id (caller) to value))) 252 | 253 | (return true))) 254 | 255 | ;; ---------------------------------------------------------------------- 256 | ;; Send _value amount of tokens from address _from to address _to 257 | ;; @abi transferFrom(address,address,uint256) returns (bool) 258 | ;; @param from The account to send the tokens from. 259 | ;; @param to The account to receive the tokens. 260 | ;; @param value The quantity of tokens to transfer. 261 | ;; @return Success (true). Other outcomes result in a Revert. 262 | 263 | (function transfer-from 264 | (seq has-three-args (is-address arg1) (is-address arg2) (is-value arg3) 265 | 266 | (def 'from arg1) 267 | (def 'to arg2) 268 | (def 'value arg3) 269 | 270 | (when value ; value == 0 is a no-op 271 | 272 | (seq 273 | 274 | ;; Save data to memory for efficiency. 275 | (mstore scratch0 (sload (balance from))) 276 | (mstore scratch1 (sload (allowance from (caller)))) 277 | 278 | ;; Revert if not enough funds, or not enough approved. 279 | (when 280 | (|| 281 | (> value (mload scratch0)) 282 | (> value (mload scratch1))) 283 | (revert)) 284 | 285 | ;; Make the transfer and update allowance. 286 | (sstore (balance from) (- (mload scratch0) value)) 287 | (sstore (balance to) (+ (sload (balance to)) value)) 288 | (sstore (allowance from (caller)) (- (mload scratch1) value)) 289 | 290 | ;; Event - Transfer(address,address,uint256) 291 | (event3 transfer-event-id from to value))) 292 | 293 | (return true))) 294 | 295 | ;; ---------------------------------------------------------------------- 296 | ;; Allows _spender to withdraw from your account multiple times, 297 | ;; up to the _value amount. If this function is called again it 298 | ;; overwrites the current allowance with _value. 299 | ;; @abi approve(address,uint256) returns (bool) 300 | ;; @param spender The withdrawing account having its limit set. 301 | ;; @param value The maximum allowed amount. 302 | ;; @return Success (true). Other outcomes result in a Revert. 303 | 304 | (function approve 305 | (seq has-two-args (is-address arg1) (is-value arg2) 306 | 307 | (def 'spender arg1) 308 | (def 'value arg2) 309 | 310 | ;; Force users set the allowance to 0 before setting it to 311 | ;; another value for the same spender. Prevents this attack: 312 | ;; https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM 313 | (when 314 | (&& value (sload (allowance (caller) spender))) 315 | (revert)) 316 | 317 | (sstore (allowance (caller) spender) value) 318 | 319 | ;; Event - Approval(address,address,uint256) 320 | (event3 approval-event-id (caller) spender value) 321 | 322 | (return true))) 323 | 324 | ;; ---------------------------------------------------------------------- 325 | ;; Returns the amount which _spender is still allowed to withdraw 326 | ;; from _owner. 327 | ;; @abi allowance(address,address) constant returns (uint256) 328 | ;; @param owner The owning account. 329 | ;; @param spender The withdrawing account. 330 | ;; @return The allowed amount remaining. 331 | 332 | (function get-allowance 333 | (seq 334 | 335 | (def 'owner arg1) 336 | (def 'spender arg2) 337 | 338 | (return (sload (allowance owner spender))))) 339 | 340 | ;; ---------------------------------------------------------------------- 341 | ;; Fallback: No functions matched the function ID provided. 342 | 343 | (revert))) 344 | ) 345 | -------------------------------------------------------------------------------- /erc20_benchmark/StandardToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | contract Token { 4 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 5 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 6 | 7 | function name() constant returns (string tokenName); 8 | function symbol() constant returns (string tokenSymbol); 9 | function decimals() constant returns (uint tokenDecimals); 10 | function totalSupply() constant returns (uint256 supply); 11 | function balanceOf(address _owner) constant returns (uint256 balance); 12 | function transfer(address _to, uint256 _value) returns (bool success); 13 | function transferFrom(address _from, address _to, uint256 _value) returns (bool success); 14 | function approve(address _spender, uint256 _value) returns (bool success); 15 | function allowance(address _owner, address _spender) constant returns (uint256 remaining); 16 | } 17 | 18 | contract StandardToken is Token { 19 | 20 | uint256 constant supply = 100000; 21 | uint256 constant tokenDecimals = 2; 22 | string constant tokenSymbol = "BEN"; 23 | string constant tokenName = "Ben Token"; 24 | 25 | mapping (address => uint256) balance; 26 | mapping (address => 27 | mapping (address => uint256)) m_allowance; 28 | 29 | modifier onlyGoodData() { 30 | require(msg.data.length % 32 == 4); 31 | _; 32 | } 33 | 34 | function StandardToken() { 35 | balance[msg.sender] = supply; 36 | } 37 | 38 | function balanceOf(address _account) constant returns (uint) { 39 | return balance[_account]; 40 | } 41 | 42 | function name() constant returns (string) { 43 | return tokenName; 44 | } 45 | 46 | function symbol() constant returns (string) { 47 | return tokenSymbol; 48 | } 49 | 50 | function decimals() constant returns (uint) { 51 | return tokenDecimals; 52 | } 53 | 54 | function totalSupply() constant returns (uint) { 55 | return supply; 56 | } 57 | 58 | function transfer(address _to, uint256 _value) onlyGoodData() returns (bool success) 59 | { 60 | return doTransfer(msg.sender, _to, _value); 61 | } 62 | 63 | function transferFrom(address _from, address _to, uint256 _value) onlyGoodData() returns (bool) 64 | { 65 | if (m_allowance[_from][msg.sender] >= _value) { 66 | if (doTransfer(_from, _to, _value)) { 67 | m_allowance[_from][msg.sender] -= _value; 68 | } 69 | return true; 70 | } else { 71 | revert(); 72 | } 73 | } 74 | 75 | function doTransfer(address _from, address _to, uint _value) internal returns (bool success) 76 | { 77 | if (balance[_from] >= _value && balance[_to] + _value >= balance[_to]) { 78 | if (_value > 0) { 79 | balance[_from] -= _value; 80 | balance[_to] += _value; 81 | Transfer(_from, _to, _value); 82 | } 83 | return true; 84 | } else { 85 | revert(); 86 | } 87 | } 88 | 89 | function approve(address _spender, uint256 _value) onlyGoodData() returns (bool success) 90 | { 91 | if (_value > supply) { 92 | revert(); 93 | } 94 | // Avoid "front-running" attack 95 | if (_value > 0 && m_allowance[msg.sender][_spender] > 0) { 96 | revert(); 97 | } 98 | m_allowance[msg.sender][_spender] = _value; 99 | Approval(msg.sender, _spender, _value); 100 | return true; 101 | } 102 | 103 | function allowance(address _owner, address _spender) constant returns (uint256) { 104 | return m_allowance[_owner][_spender]; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /erc20_benchmark/benchmark.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // 3 | // ERC20 benchmark suite 4 | // 5 | // Instructions: 6 | // 7 | // 1. Start testrpc (ideally in a different terminal) 8 | // > /opt/node/lib/node_modules/ethereumjs-testrpc/bin/testrpc -d 9 | // 2. Run the tests - the last arg is the bytecode file for the contract 10 | // > DEBUG=1 node benchmark.js erc20_solidity_opt.hex 11 | // 12 | // ============================================================================= 13 | 14 | // Input files 15 | const abi_file = 'erc20_abi.json'; 16 | const evm_file = process.argv[2]; 17 | 18 | // These addresses are generated as standard by testrpc -d 19 | const ADDR0 = "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1"; 20 | const ADDR1 = "0xffcf8fdee72ac11b5c542428b35eef5769c409f0"; 21 | const ADDR2 = "0x22d491bde2303f2f43325b2108d26f1eaba1e32b"; 22 | 23 | // Set up Web3. Need testrpc to be running. 24 | var Web3 = require('web3'); 25 | var web3 = new Web3(); 26 | web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')); 27 | 28 | const fs = require('fs'); 29 | 30 | // Read the Contract ABI [TODO - error handling] 31 | const ABI = fs.readFileSync(abi_file,'utf8'); 32 | const ERC20 = web3.eth.contract(JSON.parse(ABI)); 33 | 34 | // Read the EVM bytecode [TODO - error handling] 35 | const EVM = fs.readFileSync(evm_file,'utf8').trim(); 36 | //const GAS = web3.eth.estimateGas({data:EVM}); <-- doesn't work for Solidity 37 | const GAS = 4000000; 38 | 39 | // ============================================================================= 40 | // Control loop 41 | 42 | deployAndRun(); 43 | 44 | function runAll(erc20) 45 | { 46 | // Get token constants 47 | erc20.name.sendTransaction({from: ADDR0}, getGas); 48 | erc20.symbol.sendTransaction({from: ADDR0}, getGas); 49 | erc20.decimals.sendTransaction({from: ADDR0}, getGas); 50 | erc20.totalSupply.sendTransaction({from: ADDR0}, getGas); 51 | 52 | // First and second transfers to an account. The first should cost more 53 | erc20.transfer(ADDR1, 100, {from: ADDR0}, getGas); 54 | erc20.transfer(ADDR1, 100, {from: ADDR0}, getGas); 55 | 56 | // Balance 57 | erc20.balanceOf.sendTransaction(ADDR1, {from: ADDR0}, getGas); 58 | 59 | // Approval and first and second transferFroms 60 | erc20.approve(ADDR1, 100, {from: ADDR0}, getGas); 61 | erc20.transferFrom(ADDR0, ADDR2, 42, {from: ADDR1}, getGas); 62 | erc20.transferFrom(ADDR0, ADDR2, 42, {from: ADDR1}, getGas); 63 | 64 | // Reset approval amount to zero 65 | erc20.approve(ADDR1, 0, {from: ADDR0}, getGas); 66 | 67 | // Check approved allowance 68 | erc20.allowance.sendTransaction(ADDR1, ADDR2, {from: ADDR0}, getGas); 69 | } 70 | 71 | // ============================================================================= 72 | // Helper functions 73 | 74 | function deployAndRun() 75 | { 76 | ERC20.new( 77 | {from: ADDR0, data: EVM, gas: GAS}, 78 | function(err, myContract){ 79 | if(!err) { 80 | // NOTE: The callback will fire twice! 81 | // Once the contract has the transactionHash property set 82 | // and once its deployed on an address. 83 | if(myContract.address) { 84 | debug(2, '[deployAndRun] Contract deployed to ' + myContract.address); 85 | debug(1, '[deployAndRun] Starting benchmarks.'); 86 | var receipt = web3.eth.getTransactionReceipt(myContract.transactionHash); 87 | debug(1, receipt.cumulativeGasUsed); 88 | runAll(myContract); 89 | } 90 | } else { 91 | debug(1, '[deployAndRun] Error deployng contract.'); 92 | } 93 | }); 94 | } 95 | 96 | function getGas(err, transaction) 97 | { 98 | if(!err) { 99 | debug(2, '[getGas] ' + transaction); 100 | var receipt = web3.eth.getTransactionReceipt(transaction); 101 | debug(3, JSON.stringify(receipt)); 102 | debug(1, receipt.cumulativeGasUsed); 103 | } 104 | } 105 | 106 | // e.g. DEBUG=2 node erc20.js 107 | let debugLevel = parseInt(process.env.DEBUG); 108 | function debug(level, message) 109 | { 110 | if(debugLevel !== NaN && debugLevel >= level) { 111 | console.log('DEBUG[' + level + '] ' + message); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /erc20_benchmark/erc20.lll: -------------------------------------------------------------------------------- 1 | ;;; --------------------------------------------------------------------------- 2 | ;;; @title Implementation of an ERC20 Token contract in LLL 3 | ;;; @author Ben Edgington 4 | 5 | ;;; @dev Requires the "develop" branch of the ethereum/solidity to compile. 6 | 7 | (seq 8 | 9 | ;; -------------------------------------------------------------------------- 10 | ;; CONSTANTS 11 | 12 | ;; Token parameters. 13 | ;; @dev 0x40 is a "magic number" - the text of the string is placed here 14 | ;; when returning the string to the caller. See return-string below. 15 | (def 'token-name-string (lit 0x40 "Ben Token")) 16 | (def 'token-symbol-string (lit 0x40 "BEN")) 17 | (def 'token-decimals 2) 18 | (def 'token-supply 100000) ; 1000.00 total tokens 19 | 20 | ;; Booleans 21 | (def 'false 0) 22 | (def 'true 1) 23 | 24 | ;; Memory layout. 25 | (def 'mem-ret 0x00) ; Fixed due to compiler macro for return. 26 | (def 'mem-func 0x00) ; No conflict with mem-ret, so re-use. 27 | (def 'mem-keccak 0x00) ; No conflict with mem-func or mem-ret, so re-use. 28 | (def 'scratch0 0x20) 29 | (def 'scratch1 0x40) 30 | 31 | ;; Precomputed function IDs. 32 | (def 'get-name 0x06fdde03) ; name() 33 | (def 'get-symbol 0x95d89b41) ; symbol() 34 | (def 'get-decimals 0x313ce567) ; decimals() 35 | (def 'get-total-supply 0x18160ddd) ; totalSupply() 36 | (def 'get-balance-of 0x70a08231) ; balanceOf(address) 37 | (def 'transfer 0xa9059cbb) ; transfer(address,uint256) 38 | (def 'transfer-from 0x23b872dd) ; transferFrom(address,address,uint256) 39 | (def 'approve 0x095ea7b3) ; approve(address,uint256) 40 | (def 'get-allowance 0xdd62ed3e) ; allowance(address,address) 41 | 42 | ;; Event IDs 43 | (def 'transfer-event-id ; Transfer(address,address,uint256) 44 | 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) 45 | 46 | (def 'approval-event-id ; Approval(address,address,uint256) 47 | 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925) 48 | 49 | ;; -------------------------------------------------------------------------- 50 | ;; UTILITIES 51 | 52 | ;; -------------------------------------------------------------------------- 53 | ;; @notice The following define the key data-structures: 54 | ;; balance(addr) => value and allowance(addr,addr) => value 55 | 56 | ;; @dev Balances are stored at s[owner_addr]. 57 | (def 'balance (address) address) 58 | 59 | ;; @dev Allowances are stored at s[owner_addr + keccak256(spender_addr)] 60 | ;; @dev We use a crypto function here to avoid any situation where 61 | ;; approve(me, spender) can be abused to do approve(target, me). 62 | (def 'allowance (owner spender) 63 | (seq 64 | (mstore mem-keccak spender) 65 | (keccak256 mem-keccak 0x20))) 66 | 67 | ;; -------------------------------------------------------------------------- 68 | ;; @dev For convenience we have macros to refer to function arguments 69 | 70 | (def 'arg1 (calldataload 0x04)) 71 | (def 'arg2 (calldataload 0x24)) 72 | (def 'arg3 (calldataload 0x44)) 73 | 74 | ;; -------------------------------------------------------------------------- 75 | ;; @notice Revert is a soft return that does not consume the remaining gas. 76 | ;; We use it when rejecting invalid user input. 77 | ;; @dev The REVERT opcode will be implemented in Metropolis (EIP 140). 78 | ;; Meanwhile it just causes an invalid instruction exception (similar 79 | ;; to a "throw" in Solidity). 80 | ;; @dev When fully implemented, Revert could be use to return error codes, or 81 | ;; even messages. 82 | 83 | (def 'revert () (revert 0 0)) 84 | 85 | ;; -------------------------------------------------------------------------- 86 | ;; @notice For returning string names. 87 | ;; @dev Compliant with the ABI format for strings. 88 | 89 | (def 'return-string (string-literal) 90 | (seq 91 | (mstore 0x00 0x20) ; Points to our string's memory location 92 | (mstore 0x20 string-literal) ; Length. String itself is copied to 0x40. 93 | (return 0x00 (+ 0x40 (mload 0x20))))) 94 | 95 | ;; -------------------------------------------------------------------------- 96 | ;; @notice Convenience macro for raising Events 97 | 98 | (def 'event3 (id addr1 addr2 value) 99 | (seq 100 | (mstore scratch0 value) 101 | (log3 scratch0 0x20 id addr1 addr2))) 102 | 103 | ;; -------------------------------------------------------------------------- 104 | ;; @notice Determines whether the stored function ID matches a known 105 | ;; function hash and executes if so. 106 | ;; @param function-hash The four-byte hash of a known function signature. 107 | ;; @param code-body The code to run in the case of a match. 108 | 109 | (def 'function (function-hash code-body) 110 | (when (= (mload mem-func) function-hash) 111 | code-body)) 112 | 113 | ;; -------------------------------------------------------------------------- 114 | ;; @notice Gets the function ID and stores it in memory for reference. 115 | ;; @dev The function ID is in the leftmost four bytes of the call data. 116 | 117 | (def 'uses-functions 118 | (mstore 119 | mem-func 120 | (shr (calldataload 0x00) 224))) 121 | 122 | ;; -------------------------------------------------------------------------- 123 | ;; GUARDS 124 | 125 | ;; -------------------------------------------------------------------------- 126 | ;; @notice Checks that ensure that each function is called with the right 127 | ;; number of arguments. For one thing this addresses the "ERC20 128 | ;; short address attack". For another, it stops me making 129 | ;; mistakes while testing. 130 | ;; @dev We use these only on the non-constant functions. 131 | 132 | (def 'has-one-arg (unless (= 0x24 (calldatasize)) (revert))) 133 | (def 'has-two-args (unless (= 0x44 (calldatasize)) (revert))) 134 | (def 'has-three-args (unless (= 0x64 (calldatasize)) (revert))) 135 | 136 | ;; -------------------------------------------------------------------------- 137 | ;; @notice Check that addresses have only 160 bits and revert if not. 138 | ;; @dev We use these input type-checks on the non-constant functions. 139 | ;; @dev This is kind of expensive for now since it uses exp behind the 140 | ;; scenes until shr is implemented as an EVM opcode. The optimiser 141 | ;; is supposed to manage constant expressions, but it doesn't here 142 | ;; for some reason. 143 | 144 | (def 'is-address (addr) 145 | (when 146 | (shr addr 160) 147 | (revert))) 148 | 149 | ;; -------------------------------------------------------------------------- 150 | ;; @notice Check that transfer values are smaller than total supply and 151 | ;; revert if not. This should effectively exclude negative values. 152 | 153 | (def 'is-value (value) 154 | (when (> value token-supply) (revert))) 155 | 156 | ;; -------------------------------------------------------------------------- 157 | ;; @notice Will revert if sent any Ether. We use the macro immediately so as 158 | ;; to abort if sent any Ether during contract deployment. 159 | 160 | (def 'not-payable 161 | (when (callvalue) (revert))) 162 | 163 | not-payable 164 | 165 | ;; -------------------------------------------------------------------------- 166 | ;; INITIALISATION 167 | ;; 168 | ;; @notice Assign all tokens initially to the owner of the contract. 169 | 170 | (sstore (balance (caller)) token-supply) 171 | 172 | ;; -------------------------------------------------------------------------- 173 | ;; CONTRACT CODE 174 | 175 | (returnlll 176 | (seq not-payable uses-functions 177 | 178 | ;; ---------------------------------------------------------------------- 179 | ;; @notice Getter for the name of the token. 180 | ;; @abi name() constant returns (string) 181 | ;; @return The token name as a string. 182 | 183 | (function get-name 184 | (return-string token-name-string)) 185 | 186 | ;; ---------------------------------------------------------------------- 187 | ;; @notice Getter for the symbol of the token. 188 | ;; @abi symbol() constant returns (string) 189 | ;; @return The token symbol as a string. 190 | 191 | (function get-symbol 192 | (return-string token-symbol-string)) 193 | 194 | ;; ---------------------------------------------------------------------- 195 | ;; @notice Getter for the number of decimals assigned to the token. 196 | ;; @abi decimals() constant returns (uint256) 197 | ;; @return The token decimals. 198 | 199 | (function get-decimals 200 | (return token-decimals)) 201 | 202 | ;; ---------------------------------------------------------------------- 203 | ;; @notice Getter for the total token supply. 204 | ;; @abi totalSupply() constant returns (uint256) 205 | ;; @return The token supply. 206 | 207 | (function get-total-supply 208 | (return token-supply)) 209 | 210 | ;; ---------------------------------------------------------------------- 211 | ;; @notice Returns the account balance of another account. 212 | ;; @abi balanceOf(address) constant returns (uint256) 213 | ;; @param owner The address of the account's owner. 214 | ;; @return The account balance. 215 | 216 | (function get-balance-of 217 | (seq 218 | 219 | (def 'owner arg1) 220 | 221 | (return (sload (balance owner))))) 222 | 223 | ;; ---------------------------------------------------------------------- 224 | ;; @notice Transfers _value amount of tokens to address _to. The command 225 | ;; should throw if the _from account balance has not enough 226 | ;; tokens to spend. 227 | ;; @abi transfer(address, uint256) returns (bool) 228 | ;; @param to The account to receive the tokens. 229 | ;; @param value The quantity of tokens to transfer. 230 | ;; @return Success (true). Other outcomes result in a Revert. 231 | 232 | (function transfer 233 | (seq has-two-args (is-address arg1) (is-value arg2) 234 | 235 | (def 'to arg1) 236 | (def 'value arg2) 237 | 238 | (when value ; value == 0 is a no-op 239 | (seq 240 | 241 | ;; The caller's balance. Save in memory for efficiency. 242 | (mstore scratch0 (sload (balance (caller)))) 243 | 244 | ;; Revert if the caller's balance is not sufficient. 245 | (when (> value (mload scratch0)) 246 | (revert)) 247 | 248 | ;; Make the transfer 249 | ;; It would be good to check invariants (sum of balances). 250 | (sstore (balance (caller)) (- (mload scratch0) value)) 251 | (sstore (balance to) (+ (sload (balance to)) value)) 252 | 253 | ;; Event - Transfer(address,address,uint256) 254 | (event3 transfer-event-id (caller) to value))) 255 | 256 | (return true))) 257 | 258 | ;; ---------------------------------------------------------------------- 259 | ;; @notice Send _value amount of tokens from address _from to address _to 260 | ;; @abi transferFrom(address,address,uint256) returns (bool) 261 | ;; @param from The account to send the tokens from. 262 | ;; @param to The account to receive the tokens. 263 | ;; @param value The quantity of tokens to transfer. 264 | ;; @return Success (true). Other outcomes result in a Revert. 265 | 266 | (function transfer-from 267 | (seq has-three-args (is-address arg1) (is-address arg2) (is-value arg3) 268 | 269 | (def 'from arg1) 270 | (def 'to arg2) 271 | (def 'value arg3) 272 | 273 | (when value ; value == 0 is a no-op 274 | 275 | (seq 276 | 277 | ;; Save data to memory for efficiency. 278 | (mstore scratch0 (sload (balance from))) 279 | (mstore scratch1 (sload (allowance from (caller)))) 280 | 281 | ;; Revert if not enough funds, or not enough approved. 282 | (when 283 | (|| 284 | (> value (mload scratch0)) 285 | (> value (mload scratch1))) 286 | (revert)) 287 | 288 | ;; Make the transfer and update allowance. 289 | (sstore (balance from) (- (mload scratch0) value)) 290 | (sstore (balance to) (+ (sload (balance to)) value)) 291 | (sstore (allowance from (caller)) (- (mload scratch1) value)) 292 | 293 | ;; Event - Transfer(address,address,uint256) 294 | (event3 transfer-event-id from to value))) 295 | 296 | (return true))) 297 | 298 | ;; ---------------------------------------------------------------------- 299 | ;; @notice Allows _spender to withdraw from your account multiple times, 300 | ;; up to the _value amount. If this function is called again it 301 | ;; overwrites the current allowance with _value. 302 | ;; @abi approve(address,uint256) returns (bool) 303 | ;; @param spender The withdrawing account having its limit set. 304 | ;; @param value The maximum allowed amount. 305 | ;; @return Success (true). Other outcomes result in a Revert. 306 | 307 | (function approve 308 | (seq has-two-args (is-address arg1) (is-value arg2) 309 | 310 | (def 'spender arg1) 311 | (def 'value arg2) 312 | 313 | ;; Force users set the allowance to 0 before setting it to 314 | ;; another value for the same spender. Prevents this attack: 315 | ;; https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM 316 | (when 317 | (&& value (sload (allowance (caller) spender))) 318 | (revert)) 319 | 320 | (sstore (allowance (caller) spender) value) 321 | 322 | ;; Event - Approval(address,address,uint256) 323 | (event3 approval-event-id (caller) spender value) 324 | 325 | (return true))) 326 | 327 | ;; ---------------------------------------------------------------------- 328 | ;; @notice Returns the amount which _spender is still allowed to withdraw 329 | ;; from _owner. 330 | ;; @abi allowance(address,address) constant returns (uint256) 331 | ;; @param owner The owning account. 332 | ;; @param spender The withdrawing account. 333 | ;; @return The allowed amount remaining. 334 | 335 | (function get-allowance 336 | (seq 337 | 338 | (def 'owner arg1) 339 | (def 'spender arg2) 340 | 341 | (return (sload (allowance owner spender))))) 342 | 343 | ;; ---------------------------------------------------------------------- 344 | ;; @notice Fallback: No functions matched the function ID provided. 345 | 346 | (revert))) 347 | ) 348 | -------------------------------------------------------------------------------- /erc20_benchmark/erc20_abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant":true, 4 | "inputs":[], 5 | "name":"name", 6 | "outputs": 7 | [ 8 | { 9 | "name":"", 10 | "type":"string" 11 | } 12 | ], 13 | "type":"function" 14 | }, 15 | { 16 | "constant":false, 17 | "inputs": 18 | [ 19 | { 20 | "name":"_spender", 21 | "type":"address" 22 | }, 23 | { 24 | "name":"_value", 25 | "type":"uint256" 26 | } 27 | ], 28 | "name":"approve", 29 | "outputs": 30 | [ 31 | { 32 | "name":"success", 33 | "type":"bool" 34 | } 35 | ], 36 | "type":"function" 37 | }, 38 | { 39 | "constant":true, 40 | "inputs":[], 41 | "name":"totalSupply", 42 | "outputs": 43 | [ 44 | {"name":"","type":"uint256"} 45 | ], 46 | "type":"function" 47 | }, 48 | { 49 | "constant":false, 50 | "inputs": 51 | [ 52 | { 53 | "name":"_from", 54 | "type":"address" 55 | }, 56 | { 57 | "name":"_to", 58 | "type":"address" 59 | }, 60 | { 61 | "name":"_value", 62 | "type":"uint256" 63 | } 64 | ], 65 | "name":"transferFrom", 66 | "outputs": 67 | [ 68 | { 69 | "name":"success", 70 | "type":"bool" 71 | } 72 | ], 73 | "type":"function" 74 | }, 75 | { 76 | "constant":true, 77 | "inputs":[], 78 | "name":"decimals", 79 | "outputs": 80 | [ 81 | { 82 | "name":"", 83 | "type":"uint8"} 84 | ], 85 | "type":"function" 86 | }, 87 | { 88 | "constant":true, 89 | "inputs": 90 | [ 91 | { 92 | "name":"_owner", 93 | "type":"address" 94 | } 95 | ], 96 | "name":"balanceOf", 97 | "outputs": 98 | [ 99 | { 100 | "name":"balance", 101 | "type":"uint256" 102 | } 103 | ], 104 | "type":"function" 105 | }, 106 | { 107 | "constant":true, 108 | "inputs":[], 109 | "name":"symbol", 110 | "outputs": 111 | [ 112 | { 113 | "name":"", 114 | "type":"string" 115 | } 116 | ], 117 | "type":"function" 118 | }, 119 | { 120 | "constant":false, 121 | "inputs": 122 | [ 123 | { 124 | "name":"_to", 125 | "type":"address" 126 | }, 127 | { 128 | "name":"_value", 129 | "type":"uint256" 130 | } 131 | ], 132 | "name":"transfer", 133 | "outputs": 134 | [ 135 | { 136 | "name":"success", 137 | "type":"bool" 138 | } 139 | ], 140 | "type":"function" 141 | }, 142 | { 143 | "constant":true, 144 | "inputs": 145 | [ 146 | { 147 | "name":"_owner", 148 | "type":"address" 149 | }, 150 | { 151 | "name":"_spender", 152 | "type":"address" 153 | } 154 | ], 155 | "name":"allowance", 156 | "outputs": 157 | [ 158 | { 159 | "name":"remaining", 160 | "type":"uint256" 161 | } 162 | ], 163 | "type":"function" 164 | }, 165 | { 166 | "anonymous":false, 167 | "inputs": 168 | [ 169 | { 170 | "indexed":true, 171 | "name":"_from", 172 | "type":"address" 173 | }, 174 | { 175 | "indexed":true, 176 | "name":"_to", 177 | "type":"address" 178 | }, 179 | { 180 | "indexed":false, 181 | "name":"_value", 182 | "type":"uint256" 183 | } 184 | ], 185 | "name":"Transfer", 186 | "type":"event" 187 | }, 188 | { 189 | "anonymous":false, 190 | "inputs": 191 | [ 192 | { 193 | "indexed":true, 194 | "name":"_owner", 195 | "type":"address" 196 | }, 197 | { 198 | "indexed":true, 199 | "name":"_spender", 200 | "type":"address" 201 | }, 202 | { 203 | "indexed":false, 204 | "name":"_value", 205 | "type":"uint256" 206 | } 207 | ], 208 | "name":"Approval", 209 | "type":"event" 210 | } 211 | ] 212 | -------------------------------------------------------------------------------- /erc20_benchmark/erc20_lll.hex: -------------------------------------------------------------------------------- 1 | 3415600957600080fd5b620186a033556103398061001e6000396000f300341561000a57600080fd5b60e060020a600035046000526306fdde03600051141561003f57602060005260098061032d6040396020526020516040016000f35b6395d89b4160005114156100685760206000526003806103366040396020526020516040016000f35b63313ce567600051141561008157600260005260206000f35b6318160ddd600051141561009c57620186a060005260206000f35b6370a0823160005114156100b7576004355460005260206000f35b63a9059cbb600051141561016557366044146100d257600080fd5b60a060020a60043504156100e557600080fd5b620186a060243511156100f757600080fd5b6024351561015a573354602052602051602435111561011557600080fd5b602435602051033355602435600435540160043555602435602052600435337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a35b600160005260206000f35b6323b872dd6000511415610259573660641461018057600080fd5b60a060020a600435041561019357600080fd5b60a060020a60243504156101a657600080fd5b620186a060443511156101b857600080fd5b6044351561024e5760043554602052336000526020600020546040526001602051604435116101ea5750604051604435115b156101f457600080fd5b604435602051036004355560443560243554016024355560443560405103336000526020600020556044356020526024356004357fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a35b600160005260206000f35b63095ea7b36000511415610304573660441461027457600080fd5b60a060020a600435041561028757600080fd5b620186a0602435111561029957600080fd5b6000602435156102b157506004356000526020600020545b156102bb57600080fd5b602435600435600052602060002055602435602052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925602080a3600160005260206000f35b63dd62ed3e60005114156103275760243560005260206000205460005260206000f35b600080fd0042656e20546f6b656e42454e -------------------------------------------------------------------------------- /erc20_benchmark/erc20_lll_opt.hex: -------------------------------------------------------------------------------- 1 | 3415600957600080fd5b620186a033556103398061001e6000396000f300341561000a57600080fd5b60e060020a600035046000526306fdde03600051141561003f57602060005260098061032d6040396020526020516040016000f35b6395d89b4160005114156100685760206000526003806103366040396020526020516040016000f35b63313ce567600051141561008157600260005260206000f35b6318160ddd600051141561009c57620186a060005260206000f35b6370a0823160005114156100b7576004355460005260206000f35b63a9059cbb600051141561016557366044146100d257600080fd5b60a060020a60043504156100e557600080fd5b620186a060243511156100f757600080fd5b6024351561015a573354602052602051602435111561011557600080fd5b602435602051033355602435600435540160043555602435602052600435337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a35b600160005260206000f35b6323b872dd6000511415610259573660641461018057600080fd5b60a060020a600435041561019357600080fd5b60a060020a60243504156101a657600080fd5b620186a060443511156101b857600080fd5b6044351561015a5760043554602052336000526020600020546040526001602051604435116101ea5750604051604435115b156101f457600080fd5b604435602051036004355560443560243554016024355560443560405103336000526020600020556044356020526024356004357fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602080a35b600160005260206000f35b63095ea7b36000511415610304573660441461027457600080fd5b60a060020a600435041561028757600080fd5b620186a0602435111561029957600080fd5b6000602435156102b157506004356000526020600020545b156102bb57600080fd5b602435600435600052602060002055602435602052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925602080a3600160005260206000f35b63dd62ed3e60005114156103275760243560005260206000205460005260206000f35b600080fd0042656e20546f6b656e42454e -------------------------------------------------------------------------------- /erc20_benchmark/erc20_solidity.hex: -------------------------------------------------------------------------------- 1 | 6060604052341561000f57600080fd5b5b620186a06000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5b610ad8806100676000396000f30060606040523615610097576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde031461009c578063095ea7b31461012b57806318160ddd1461018557806323b872dd146101ae578063313ce5671461022757806370a082311461025057806395d89b411461029d578063a9059cbb1461032c578063dd62ed3e14610386575b600080fd5b34156100a757600080fd5b6100af6103f2565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f05780820151818401525b6020810190506100d4565b50505050905090810190601f16801561011d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561013657600080fd5b61016b600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610436565b604051808215151515815260200191505060405180910390f35b341561019057600080fd5b6101986105ef565b6040518082815260200191505060405180910390f35b34156101b957600080fd5b61020d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506105fb565b604051808215151515815260200191505060405180910390f35b341561023257600080fd5b61023a610753565b6040518082815260200191505060405180910390f35b341561025b57600080fd5b610287600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061075d565b6040518082815260200191505060405180910390f35b34156102a857600080fd5b6102b06107a6565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102f15780820151818401525b6020810190506102d5565b50505050905090810190601f16801561031e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561033757600080fd5b61036c600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506107ea565b604051808215151515815260200191505060405180910390f35b341561039157600080fd5b6103dc600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610820565b6040518082815260200191505060405180910390f35b6103fa610a98565b6040805190810160405280600981526020017f42656e20546f6b656e000000000000000000000000000000000000000000000081525090505b90565b600060046020600036905081151561044a57fe5b0614151561045757600080fd5b620186a082111561046757600080fd5b6000821180156104f357506000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054115b156104fd57600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a3600190505b5b92915050565b6000620186a090505b90565b600060046020600036905081151561060f57fe5b0614151561061c57600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515610745576106ac8484846108a8565b1561073c5781600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b6001905061074b565b600080fd5b5b5b9392505050565b6000600290505b90565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b6107ae610a98565b6040805190810160405280600381526020017f42454e000000000000000000000000000000000000000000000000000000000081525090505b90565b60006004602060003690508115156107fe57fe5b0614151561080b57600080fd5b6108163384846108a8565b90505b5b92915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b92915050565b6000816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015801561097657506000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054826000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020540110155b15610a8b576000821115610a8257816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a35b60019050610a91565b600080fd5b5b9392505050565b6020604051908101604052806000815250905600a165627a7a7230582035ed27274e0d0842a7540f6842fc08ef91bfb9abb00d9611231e1e303b037e210029 -------------------------------------------------------------------------------- /erc20_benchmark/erc20_solidity_opt.hex: -------------------------------------------------------------------------------- 1 | 6060604052341561000f57600080fd5b5b600160a060020a0333166000908152602081905260409020620186a090555b5b6106838061003f6000396000f300606060405236156100965763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde03811461009b578063095ea7b31461012657806318160ddd1461015c57806323b872dd14610181578063313ce567146101bd57806370a08231146101e257806395d89b4114610213578063a9059cbb1461029e578063dd62ed3e146102d4575b600080fd5b34156100a657600080fd5b6100ae61030b565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100eb5780820151818401525b6020016100d2565b50505050905090810190601f1680156101185780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561013157600080fd5b610148600160a060020a036004351660243561034d565b604051901515815260200160405180910390f35b341561016757600080fd5b61016f61041c565b60405190815260200160405180910390f35b341561018c57600080fd5b610148600160a060020a0360043581169060243516604435610424565b604051901515815260200160405180910390f35b34156101c857600080fd5b61016f6104bb565b60405190815260200160405180910390f35b34156101ed57600080fd5b61016f600160a060020a03600435166104c1565b60405190815260200160405180910390f35b341561021e57600080fd5b6100ae6104e0565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100eb5780820151818401525b6020016100d2565b50505050905090810190601f1680156101185780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156102a957600080fd5b610148600160a060020a0360043516602435610522565b604051901515815260200160405180910390f35b34156102df57600080fd5b61016f600160a060020a036004358116906024351661054c565b60405190815260200160405180910390f35b610313610645565b60408051908101604052600981527f42656e20546f6b656e0000000000000000000000000000000000000000000000602082015290505b90565b60006020365b06600414151561036257600080fd5b620186a082111561037257600080fd5b6000821180156103a75750600160a060020a033381166000908152600160209081526040808320938716835292905290812054115b156103b157600080fd5b600160a060020a03338116600081815260016020908152604080832094881680845294909152908190208590557f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259085905190815260200160405180910390a35060015b5b92915050565b620186a05b90565b60006020365b06600414151561043957600080fd5b600160a060020a038085166000908152600160209081526040808320339094168352929052205482901061009657610472848484610579565b156104a557600160a060020a03808516600090815260016020908152604080832033909416835292905220805483900390555b5060016104b2565b600080fd5b5b5b9392505050565b60025b90565b600160a060020a0381166000908152602081905260409020545b919050565b6104e8610645565b60408051908101604052600381527f42454e0000000000000000000000000000000000000000000000000000000000602082015290505b90565b60006020365b06600414151561053757600080fd5b610542338484610579565b90505b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b600160a060020a0383166000908152602081905260408120548290108015906105bc5750600160a060020a03831660009081526020819052604090205482810110155b156100965760008211156104a557600160a060020a038085166000818152602081905260408082208054879003905592861680825290839020805486019055917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9085905190815260200160405180910390a35b5060016104b2565b600080fd5b5b9392505050565b602060405190810160405260008152905600a165627a7a72305820d7cf6213db6274d11481d2f04c45d5f706bdcdc3608dd0b786eb2e06218061eb0029 -------------------------------------------------------------------------------- /erc20_test/00_start_node: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /opt/node/bin/testrpc -d 4 | 5 | -------------------------------------------------------------------------------- /erc20_test/erc20.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // 3 | // ERC20 test suite 4 | // 5 | // *** Needs web3.js version at least 1.0.0 *** 6 | // 7 | // Instructions: 8 | // 9 | // 1. Compile the bytecode, e.g. 10 | // > lllc ../erc20.lll > erc20_evm.dat 11 | // 2. Start testrpc (ideally in a different terminal) with -d flag 12 | // > /opt/node/lib/node_modules/ethereumjs-testrpc/bin/testrpc -d 13 | // 3a. Run all the tests: 14 | // > node erc20.js 15 | // 3b. Run the subset of tests matching 'regex' 16 | // > node erc20.js 'regex' 17 | // 3c. Run with debugging info (n = 0,1,2 or 3) 18 | // > DEBUG=n node erc20.js 'regex' 19 | // 20 | // ============================================================================= 21 | 22 | // TODO - Try testing against cpp-ethereum 23 | 24 | // Test selection expression 25 | const testSelector = process.argv[2] || ''; 26 | 27 | // Input files 28 | const abi_file = 'erc20_abi.json'; 29 | const evm_file = 'erc20_evm.dat'; 30 | 31 | // Token paramaters 32 | const TOTALSUPPLY = 100; 33 | const DECIMALS = 0; 34 | const SYMBOL = 'LLL'; 35 | const NAME = 'LLL Coin - love to code in LLL.'; 36 | 37 | // Set up Web3. Need testrpc to be running. 38 | const Web3 = require('web3'); 39 | const web3 = new Web3('http://localhost:8545'); 40 | 41 | // These addresses are generated as standard by testrpc -d 42 | const ADDR0 = web3.utils.toChecksumAddress("0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1"); 43 | const ADDR1 = web3.utils.toChecksumAddress("0xffcf8fdee72ac11b5c542428b35eef5769c409f0"); 44 | const ADDR2 = web3.utils.toChecksumAddress("0x22d491bde2303f2f43325b2108d26f1eaba1e32b"); 45 | 46 | // Set up the Contract object 47 | const fs = require('fs'); 48 | const ABI = fs.readFileSync(abi_file,'utf8'); 49 | const EVM = fs.readFileSync(evm_file,'utf8').trim(); 50 | const ERC20 = new web3.eth.Contract(JSON.parse(ABI)); 51 | ERC20.options.from = ADDR0; 52 | ERC20.options.data = EVM; 53 | ERC20.options.gas = 4000000 54 | 55 | // ============================================================================= 56 | // The Tests 57 | 58 | const theTests = { 59 | 60 | // ------------------------------------------------------------------------- 61 | // Constant functions 62 | 63 | // There's a nasty race condition in latest web3.js (1.0.0-beta.18) 64 | // that prodcuces garbage when running the string tests and the constant 65 | // tests concurrently, so they are now separated. 66 | t1_constant_strings:function(testName, erc20) 67 | { 68 | Promise.all([ 69 | erc20.methods.symbol().call() 70 | .then(checkResult(testName, 'string', SYMBOL)) 71 | .catch(checkResult(testName, 'isError', false)), 72 | erc20.methods.name().call() 73 | .then(checkResult(testName, 'string', NAME)) 74 | .catch(checkResult(testName, 'isError', false)) 75 | ]).then(finalResult(testName)); 76 | }, 77 | 78 | t1_constants:function(testName, erc20) 79 | { 80 | Promise.all([ 81 | erc20.methods.totalSupply().call() 82 | .then(checkResult(testName, 'uint256', TOTALSUPPLY)) 83 | .catch(checkResult(testName, 'isError', false)), 84 | erc20.methods.decimals().call() 85 | .then(checkResult(testName, 'uint256', DECIMALS)) 86 | .catch(checkResult(testName, 'isError', false)) 87 | ]).then(finalResult(testName)); 88 | }, 89 | 90 | t1_balances:function(testName, erc20) 91 | { 92 | Promise.all([ 93 | erc20.methods.balanceOf(ADDR0).call() 94 | .then(checkResult(testName, 'uint256', TOTALSUPPLY)) 95 | .catch(checkResult(testName, 'isError', false)), 96 | erc20.methods.balanceOf(ADDR1).call() 97 | .then(checkResult(testName, 'uint256', 0)) 98 | .catch(checkResult(testName, 'isError', false)), 99 | erc20.methods.balanceOf(ADDR2).call() 100 | .then(checkResult(testName, 'uint256', 0)) 101 | .catch(checkResult(testName, 'isError', false)) 102 | ]).then(finalResult(testName)); 103 | }, 104 | 105 | 106 | t1_allowances:function(testName, erc20) 107 | { 108 | erc20.methods.allowance(ADDR1,ADDR2).call() 109 | .then(checkResult(testName, 'uint256', 0)) 110 | .catch(checkResult(testName, 'isError', false)) 111 | .then(finalResult(testName)); 112 | }, 113 | 114 | // ------------------------------------------------------------------------- 115 | // Input validation tests 116 | 117 | t2_call_invalid_function:function(testName, erc20) 118 | { 119 | // Test sending correct and non-existent function selectors 120 | Promise.all([ 121 | 122 | // Good function call - totalSupply() 123 | web3.eth.call({to: erc20.options.address, data: '0x18160ddd'}) 124 | .then(checkResult(testName, 'bytes32', decToBytes32(TOTALSUPPLY))) 125 | .catch(checkResult(testName, 'isError', false)), 126 | 127 | // Call to non-existent function 128 | web3.eth.call({to: erc20.options.address, data: '0x12345678'}) 129 | .then(checkResult(testName, 'isError', true)) 130 | .catch(checkResult(testName, 'isError', true)) 131 | 132 | ]).then(finalResult(testName)); 133 | }, 134 | 135 | t2_send_ether_to_transfer:function(testName, erc20) 136 | { 137 | // Any attempt to send Ether to the contract should fail. 138 | web3.eth.sendTransaction({from: ADDR0, to: erc20.options.address, data: '0xa9059cbb' + hexToBytes32(ADDR1) + decToBytes32(1), value: 1}) 139 | .then(checkResult(testName, 'isError', true)) 140 | .catch(checkResult(testName, 'isError', true)) 141 | .then(finalResult(testName)); 142 | }, 143 | 144 | t2_low_level_transfer:function(testName, erc20) 145 | { 146 | // Check this works as expected as it is the template for the following 147 | // tests. Transfer 1 Token from ADDR0 to ADDR1. 148 | web3.eth.sendTransaction({from: ADDR0, to: erc20.options.address, data: '0xa9059cbb' + hexToBytes32(ADDR1) + decToBytes32(1)}) 149 | .then(checkResult(testName, 'isReceipt', true)) 150 | .catch(checkResult(testName, 'isError', false)) 151 | .then(finalResult(testName)); 152 | }, 153 | 154 | t2_too_little_call_data:function(testName, erc20) 155 | { 156 | // Test with transfer() function, providing truncated call data. 157 | web3.eth.sendTransaction({from: ADDR0, to: erc20.options.address, data: '0xa9059cbb' + hexToBytes32(ADDR1) + decToBytes32(1).substr(2)}) 158 | .then(checkResult(testName, 'isError', true)) 159 | .catch(checkResult(testName, 'isError', true)) 160 | .then(finalResult(testName)); 161 | }, 162 | 163 | t2_too_much_call_data:function(testName, erc20) 164 | { 165 | // Test with transfer() function, providing extended call data. 166 | web3.eth.sendTransaction({from: ADDR0, to: erc20.options.address, data: '0xa9059cbb' + hexToBytes32(ADDR1) + decToBytes32(1) + '00'}) 167 | .then(checkResult(testName, 'isError', true)) 168 | .catch(checkResult(testName, 'isError', true)) 169 | .then(finalResult(testName)); 170 | }, 171 | 172 | t2_invalid_address:function(testName, erc20) 173 | { 174 | // Test with transfer() function, providing a malformed address. 175 | web3.eth.sendTransaction({from: ADDR0, to: erc20.options.address, data: '0xa9059cbb' + '01' + hexToBytes32(ADDR1).substr(2) + decToBytes32(1)}) 176 | .then(checkResult(testName, 'isError', true)) 177 | .catch(checkResult(testName, 'isError', true)) 178 | .then(finalResult(testName)); 179 | }, 180 | 181 | // ------------------------------------------------------------------------- 182 | // Smoke tests on transfer(), approve(), transferFrom() 183 | 184 | t3_transfer:function(testName, erc20) 185 | { 186 | // Transfer 10 tokens from ADDR0 to ADDR1 187 | erc20.methods.transfer(ADDR1, 10).send({from: ADDR0}) 188 | .then(checkResult(testName, 'isReceipt', true)) 189 | .catch(checkResult(testName, 'isError', false)) 190 | .then(finalResult(testName)); 191 | }, 192 | 193 | t3_transfer_too_much:function(testName, erc20) 194 | { 195 | // Try to send more than totalSupply 196 | erc20.methods.transfer(ADDR1, 1 + TOTALSUPPLY).send({from: ADDR0}) 197 | .then(checkResult(testName, 'isError', true)) 198 | .catch(checkResult(testName, 'isError', true)) 199 | .then(finalResult(testName)); 200 | }, 201 | 202 | t3_approve:function(testName, erc20) 203 | { 204 | // Approve ADDR1 to transfer 10 tokens from ADDR0 205 | erc20.methods.approve(ADDR1, 10).send({from: ADDR0}) 206 | .then(checkResult(testName, 'isReceipt', true)) 207 | .catch(checkResult(testName, 'isError', false)) 208 | .then(finalResult(testName)); 209 | }, 210 | 211 | t3_approve_too_much:function(testName, erc20) 212 | { 213 | // Approve ADDR1 to transfer more than totalSupply tokens from ADDR0 214 | erc20.methods.approve(ADDR1, 1 + TOTALSUPPLY).send({from: ADDR0}) 215 | .then(checkResult(testName, 'isError', true)) 216 | .catch(checkResult(testName, 'isError', true)) 217 | .then(finalResult(testName)); 218 | }, 219 | 220 | t3_transfer_from_no_approval:function(testName, erc20) 221 | { 222 | // ADDR1 tries transfer from ADDR0 to ADDR2 without having approval 223 | erc20.methods.transferFrom(ADDR0, ADDR2, 10).send({from: ADDR1}) 224 | .then(checkResult(testName, 'isError', true)) 225 | .catch(checkResult(testName, 'isError', true)) 226 | .then(finalResult(testName)); 227 | }, 228 | 229 | t3_transfer_from_no_approval_zero:function(testName, erc20) 230 | { 231 | // ADDR1 tries transfer from ADDR0 to ADDR2 without having approval 232 | // But zero value, so should not be an error. 233 | erc20.methods.transferFrom(ADDR0, ADDR2, 0).send({from: ADDR1}) 234 | .then(checkResult(testName, 'isReceipt', true)) 235 | .catch(checkResult(testName, 'isError', false)) 236 | .then(finalResult(testName)); 237 | }, 238 | 239 | // ------------------------------------------------------------------------- 240 | // End-to-End Tests 241 | 242 | t4_valid_transfer:function(testName, erc20) 243 | { 244 | let amount = 10; 245 | let addr0_startTokens; 246 | let addr1_startTokens; 247 | 248 | serialExec([ 249 | 250 | // Save initial balances 251 | next => {erc20.methods.balanceOf(ADDR0).call() 252 | .then(bal => {addr0_startTokens = parseInt(bal)}) 253 | .catch(checkResult(testName, 'isError', false)) 254 | .then(next)}, 255 | next => {erc20.methods.balanceOf(ADDR1).call() 256 | .then(bal => {addr1_startTokens = parseInt(bal)}) 257 | .catch(checkResult(testName, 'isError', false)) 258 | .then(next)}, 259 | 260 | // Successful transfer 261 | next => {erc20.methods.transfer(ADDR1, amount).send({from: ADDR0}) 262 | .catch(checkResult(testName, 'isError', false)) 263 | .then(next)}, 264 | 265 | // Check balances 266 | next => {erc20.methods.balanceOf(ADDR0).call() 267 | .then(checkResult(testName, 'uint256', addr0_startTokens - amount)) 268 | .catch(checkResult(testName, 'isError', false)) 269 | .then(next)}, 270 | next => {erc20.methods.balanceOf(ADDR1).call() 271 | .then(checkResult(testName, 'uint256', addr1_startTokens + amount)) 272 | .catch(checkResult(testName, 'isError', false)) 273 | .then(finalResult(testName))}, 274 | ]); 275 | }, 276 | 277 | t4_invalid_transfer:function(testName, erc20) 278 | { 279 | let addr0_startTokens; 280 | let addr1_startTokens; 281 | 282 | serialExec([ 283 | 284 | // Save initial balances 285 | next => {erc20.methods.balanceOf(ADDR0).call() 286 | .then(bal => {addr0_startTokens = parseInt(bal)}) 287 | .catch(checkResult(testName, 'isError', false)) 288 | .then(next)}, 289 | next => {erc20.methods.balanceOf(ADDR1).call() 290 | .then(bal => {addr1_startTokens = parseInt(bal)}) 291 | .catch(checkResult(testName, 'isError', false)) 292 | .then(next)}, 293 | 294 | // Unsuccessful transfer 295 | next => {erc20.methods.transfer(ADDR1, addr0_startTokens + 1).send({from: ADDR0}) 296 | .then(checkResult(testName, 'isError', true)) 297 | .catch(checkResult(testName, 'isError', true)) 298 | .then(next)}, 299 | 300 | // Check balances 301 | next => {erc20.methods.balanceOf(ADDR0).call() 302 | .then(checkResult(testName, 'uint256', addr0_startTokens)) 303 | .catch(checkResult(testName, 'isError', false)) 304 | .then(next)}, 305 | next => {erc20.methods.balanceOf(ADDR1).call() 306 | .then(checkResult(testName, 'uint256', addr1_startTokens)) 307 | .catch(checkResult(testName, 'isError', false)) 308 | .then(finalResult(testName))} 309 | ]); 310 | }, 311 | 312 | t4_multiple_transfers:function(testName, erc20) 313 | { 314 | let amount; 315 | let addr0_startTokens; 316 | let addr1_startTokens; 317 | 318 | serialExec([ 319 | 320 | // Save initial balances and calculate transfer amount 321 | next => {erc20.methods.balanceOf(ADDR0).call() 322 | .then(bal => { 323 | addr0_startTokens = parseInt(bal); 324 | amount = 1 + Math.floor(addr0_startTokens / 3);}) 325 | .catch(checkResult(testName, 'isError', false)) 326 | .then(next)}, 327 | next => {erc20.methods.balanceOf(ADDR1).call() 328 | .then(bal => {addr1_startTokens = parseInt(bal)}) 329 | .catch(checkResult(testName, 'isError', false)) 330 | .then(next)}, 331 | 332 | // Two successful transfers and an unsuccessful 333 | next => {erc20.methods.transfer(ADDR1, amount).send({from: ADDR0}) 334 | .then(checkResult(testName, 'isReceipt', true)) 335 | .catch(checkResult(testName, 'isError', false)) 336 | .then(next)}, 337 | next => {erc20.methods.transfer(ADDR1, amount).send({from: ADDR0}) 338 | .then(checkResult(testName, 'isReceipt', true)) 339 | .catch(checkResult(testName, 'isError', false)) 340 | .then(next)}, 341 | next => {erc20.methods.transfer(ADDR1, amount).send({from: ADDR0}) 342 | .then(checkResult(testName, 'isError', true)) 343 | .catch(checkResult(testName, 'isError', true)) 344 | .then(next)}, 345 | 346 | // Check balances 347 | next => {erc20.methods.balanceOf(ADDR0).call() 348 | .then(checkResult(testName, 'uint256', addr0_startTokens - 2 * amount)) 349 | .catch(checkResult(testName, 'isError', false)) 350 | .then(next)}, 351 | next => {erc20.methods.balanceOf(ADDR1).call() 352 | .then(checkResult(testName, 'uint256', addr1_startTokens + 2 * amount)) 353 | .catch(checkResult(testName, 'isError', false)) 354 | .then(finalResult(testName))}, 355 | ]); 356 | }, 357 | 358 | t4_multiple_approve:function(testName, erc20) 359 | { 360 | serialExec([ 361 | 362 | // Check ADDR0 => ADDR1 allowance is 0 363 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 364 | .then(checkResult(testName, 'uint256', 0)) 365 | .catch(checkResult(testName, 'isError', false)) 366 | .then(next)}, 367 | 368 | // Set allowance ADDR0 => ADDR1 to 10 369 | next => {erc20.methods.approve(ADDR1, 10).send({from: ADDR0}) 370 | .then(checkResult(testName, 'isReceipt', true)) 371 | .catch(checkResult(testName, 'isError', false)) 372 | .then(next)}, 373 | 374 | // Check ADDR0 => ADDR1 allowance is 10 375 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 376 | .then(checkResult(testName, 'uint256', 10)) 377 | .catch(checkResult(testName, 'isError', false)) 378 | .then(next)}, 379 | 380 | // Try to set allowance ADDR0 => ADDR1 to 5 (should fail) 381 | next => {erc20.methods.approve(ADDR1, 5).send({from: ADDR0}) 382 | .then(checkResult(testName, 'isError', true)) 383 | .catch(checkResult(testName, 'isError', true)) 384 | .then(next)}, 385 | 386 | // Check ADDR0 => ADDR1 allowance is still 10 387 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 388 | .then(checkResult(testName, 'uint256', 10)) 389 | .catch(checkResult(testName, 'isError', false)) 390 | .then(next)}, 391 | 392 | // Set allowance ADDR0 => ADDR1 to 0 393 | next => {erc20.methods.approve(ADDR1, 0).send({from: ADDR0}) 394 | .then(checkResult(testName, 'isReceipt', true)) 395 | .catch(checkResult(testName, 'isError', false)) 396 | .then(next)}, 397 | 398 | // Check ADDR0 => ADDR1 allowance is now 0 399 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 400 | .then(checkResult(testName, 'uint256', 0)) 401 | .catch(checkResult(testName, 'isError', false)) 402 | .then(next)}, 403 | 404 | // Try to set allowance ADDR0 => ADDR1 to 5 (should succeed) 405 | next => {erc20.methods.approve(ADDR1, 5).send({from: ADDR0}) 406 | .then(checkResult(testName, 'isReceipt', true)) 407 | .catch(checkResult(testName, 'isError', false)) 408 | .then(next)}, 409 | 410 | // Check ADDR0 => ADDR1 allowance is now 5 411 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 412 | .then(checkResult(testName, 'uint256', 5)) 413 | .catch(checkResult(testName, 'isError', false)) 414 | .then(finalResult(testName))} 415 | ]); 416 | }, 417 | 418 | t4_transfer_from:function(testName, erc20) 419 | { 420 | let addr0_startTokens; 421 | let addr2_startTokens; 422 | 423 | serialExec([ 424 | 425 | // Save initial balances 426 | next => {erc20.methods.balanceOf(ADDR0).call() 427 | .then(bal => {addr0_startTokens = parseInt(bal)}) 428 | .catch(checkResult(testName, 'isError', false)) 429 | .then(next)}, 430 | next => {erc20.methods.balanceOf(ADDR2).call() 431 | .then(bal => {addr2_startTokens = parseInt(bal)}) 432 | .catch(checkResult(testName, 'isError', false)) 433 | .then(next)}, 434 | 435 | // Check ADDR0 => ADDR1 allowance is 0 436 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 437 | .then(checkResult(testName, 'uint256', 0)) 438 | .catch(checkResult(testName, 'isError', false)) 439 | .then(next)}, 440 | 441 | // ADDR1 tries transfer from ADDR0 to ADDR2 with no allowance 442 | next => {erc20.methods.transferFrom(ADDR0, ADDR2, 42).send({from: ADDR1}) 443 | .then(checkResult(testName, 'isError', true)) 444 | .catch(checkResult(testName, 'isError', true)) 445 | .then(next)}, 446 | 447 | // Set allowance ADDR0 => ADDR1 to 83 448 | next => {erc20.methods.approve(ADDR1, 83).send({from: ADDR0}) 449 | .then(checkResult(testName, 'isReceipt', true)) 450 | .catch(checkResult(testName, 'isError', false)) 451 | .then(next)}, 452 | 453 | // Check ADDR0 => ADDR1 allowance is now 83 454 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 455 | .then(checkResult(testName, 'uint256', 83)) 456 | .catch(checkResult(testName, 'isError', false)) 457 | .then(next)}, 458 | 459 | // ADDR1 transfers 42 from ADDR0 to ADDR2 460 | next => {erc20.methods.transferFrom(ADDR0, ADDR2, 42).send({from: ADDR1}) 461 | .then(checkResult(testName, 'isReceipt', true)) 462 | .catch(checkResult(testName, 'isError', false)) 463 | .then(next)}, 464 | 465 | // Check balance of ADDR0 is start-42, ADDR2 is start+42 466 | next => {erc20.methods.balanceOf(ADDR0).call() 467 | .then(checkResult(testName, 'uint256', addr0_startTokens - 42)) 468 | .catch(checkResult(testName, 'isError', false)) 469 | .then(next)}, 470 | next => {erc20.methods.balanceOf(ADDR2).call() 471 | .then(checkResult(testName, 'uint256', addr2_startTokens + 42)) 472 | .catch(checkResult(testName, 'isError', false)) 473 | .then(next)}, 474 | 475 | // Check ADDR0 => ADDR1 allowance is now 41 476 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 477 | .then(checkResult(testName, 'uint256', 41)) 478 | .catch(checkResult(testName, 'isError', false)) 479 | .then(next)}, 480 | 481 | // ADDR1 tries to transfer 42 from ADDR0 to ADDR2 (should fail) 482 | next => {erc20.methods.transferFrom(ADDR0, ADDR2, 42).send({from: ADDR1}) 483 | .then(checkResult(testName, 'isError', true)) 484 | .catch(checkResult(testName, 'isError', true)) 485 | .then(next)}, 486 | 487 | // Check balance of ADDR0 remains start-42, ADDR2 +42 488 | next => {erc20.methods.balanceOf(ADDR0).call() 489 | .then(checkResult(testName, 'uint256', addr0_startTokens - 42)) 490 | .catch(checkResult(testName, 'isError', false)) 491 | .then(next)}, 492 | next => {erc20.methods.balanceOf(ADDR2).call() 493 | .then(checkResult(testName, 'uint256', addr2_startTokens + 42)) 494 | .catch(checkResult(testName, 'isError', false)) 495 | .then(finalResult(testName))} 496 | ]); 497 | }, 498 | 499 | t4_multiple_allowances_1:function(testName, erc20) 500 | { 501 | serialExec([ 502 | 503 | // Check that multiple approvals from different owners for 504 | // the same spender are correctly handled. 505 | 506 | // Check ADDR0 => ADDR2 allowance is 0 507 | next => {erc20.methods.allowance(ADDR0, ADDR2).call() 508 | .then(checkResult(testName, 'uint256', 0)) 509 | .catch(checkResult(testName, 'isError', false)) 510 | .then(next)}, 511 | 512 | // Check ADDR1 => ADDR2 allowance is 0 513 | next => {erc20.methods.allowance(ADDR1, ADDR2).call() 514 | .then(checkResult(testName, 'uint256', 0)) 515 | .catch(checkResult(testName, 'isError', false)) 516 | .then(next)}, 517 | 518 | // Set allowance ADDR0 => ADDR2 to 10 519 | next => {erc20.methods.approve(ADDR2, 10).send({from: ADDR0}) 520 | .then(checkResult(testName, 'isReceipt', true)) 521 | .catch(checkResult(testName, 'isError', false)) 522 | .then(next)}, 523 | 524 | // Set allowance ADDR1 => ADDR2 to 20 525 | next => {erc20.methods.approve(ADDR2, 20).send({from: ADDR1}) 526 | .then(checkResult(testName, 'isReceipt', true)) 527 | .catch(checkResult(testName, 'isError', false)) 528 | .then(next)}, 529 | 530 | // Check ADDR0 => ADDR2 allowance is 10 531 | next => {erc20.methods.allowance(ADDR0, ADDR2).call() 532 | .then(checkResult(testName, 'uint256', 10)) 533 | .catch(checkResult(testName, 'isError', false)) 534 | .then(next)}, 535 | 536 | // Check ADDR1 => ADDR2 allowance is 20 537 | next => {erc20.methods.allowance(ADDR1, ADDR2).call() 538 | .then(checkResult(testName, 'uint256', 20)) 539 | .catch(checkResult(testName, 'isError', false)) 540 | .then(finalResult(testName))} 541 | ]); 542 | }, 543 | 544 | t4_multiple_allowances_2:function(testName, erc20) 545 | { 546 | serialExec([ 547 | 548 | // Check that multiple approvals from the same owner for 549 | // different spenders are correctly handled. 550 | 551 | // Check ADDR0 => ADDR1 allowance is 0 552 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 553 | .then(checkResult(testName, 'uint256', 0)) 554 | .catch(checkResult(testName, 'isError', false)) 555 | .then(next)}, 556 | 557 | // Check ADDR0 => ADDR2 allowance is 0 558 | next => {erc20.methods.allowance(ADDR0, ADDR2).call() 559 | .then(checkResult(testName, 'uint256', 0)) 560 | .catch(checkResult(testName, 'isError', false)) 561 | .then(next)}, 562 | 563 | // Set allowance ADDR0 => ADDR1 to 10 564 | next => {erc20.methods.approve(ADDR1, 10).send({from: ADDR0}) 565 | .then(checkResult(testName, 'isReceipt', true)) 566 | .catch(checkResult(testName, 'isError', false)) 567 | .then(next)}, 568 | 569 | // Set allowance ADDR0 => ADDR2 to 20 570 | next => {erc20.methods.approve(ADDR2, 20).send({from: ADDR0}) 571 | .then(checkResult(testName, 'isReceipt', true)) 572 | .catch(checkResult(testName, 'isError', false)) 573 | .then(next)}, 574 | 575 | // Check ADDR0 => ADDR1 allowance is 10 576 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 577 | .then(checkResult(testName, 'uint256', 10)) 578 | .catch(checkResult(testName, 'isError', false)) 579 | .then(next)}, 580 | 581 | // Check ADDR0 => ADDR2 allowance is 20 582 | next => {erc20.methods.allowance(ADDR0, ADDR2).call() 583 | .then(checkResult(testName, 'uint256', 20)) 584 | .catch(checkResult(testName, 'isError', false)) 585 | .then(finalResult(testName))} 586 | ]); 587 | }, 588 | 589 | t4_symmetric_allowances:function(testName, erc20) 590 | { 591 | serialExec([ 592 | 593 | // Check that setting allowance for ADDR0 -> ADDR1 594 | // doesn't set allowance for ADDR1 -> ADDR0 595 | 596 | // Check ADDR0 => ADDR1 allowance is 0 597 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 598 | .then(checkResult(testName, 'uint256', 0)) 599 | .catch(checkResult(testName, 'isError', false)) 600 | .then(next)}, 601 | 602 | // Check ADDR1 => ADDR0 allowance is 0 603 | next => {erc20.methods.allowance(ADDR1, ADDR0).call() 604 | .then(checkResult(testName, 'uint256', 0)) 605 | .catch(checkResult(testName, 'isError', false)) 606 | .then(next)}, 607 | 608 | // Set allowance ADDR0 => ADDR1 to 10 609 | next => {erc20.methods.approve(ADDR1, 10).send({from: ADDR0}) 610 | .then(checkResult(testName, 'isReceipt', true)) 611 | .catch(checkResult(testName, 'isError', false)) 612 | .then(next)}, 613 | 614 | // Check ADDR0 => ADDR1 allowance is 10 615 | next => {erc20.methods.allowance(ADDR0, ADDR1).call() 616 | .then(checkResult(testName, 'uint256', 10)) 617 | .catch(checkResult(testName, 'isError', false)) 618 | .then(next)}, 619 | 620 | // Check ADDR1 => ADDR0 allowance remains 0 621 | next => {erc20.methods.allowance(ADDR0, ADDR2).call() 622 | .then(checkResult(testName, 'uint256', 0)) 623 | .catch(checkResult(testName, 'isError', false)) 624 | .then(finalResult(testName))} 625 | ]); 626 | }, 627 | 628 | // ------------------------------------------------------------------------- 629 | // Events 630 | 631 | t5_transfer_event:function(testName, erc20) 632 | { 633 | erc20.methods.transfer(ADDR1, 42).send({from: ADDR0}) 634 | .then(checkResult(testName, 'event', {Transfer: {_value: '42', _from: ADDR0, _to: ADDR1}})) 635 | .catch(checkResult(testName, 'isError', false)) 636 | .then(finalResult(testName)); 637 | }, 638 | 639 | t5_zero_transfer_no_event:function(testName, erc20) 640 | { 641 | 642 | erc20.methods.transfer(ADDR1, 0).send({from: ADDR0}) 643 | .then(checkResult(testName, 'hasEvent', false)) 644 | .catch(checkResult(testName, 'isError', false)) 645 | .then(finalResult(testName)); 646 | }, 647 | 648 | t5_approve_event:function(testName, erc20) 649 | { 650 | erc20.methods.approve(ADDR1, 42).send({from: ADDR0}) 651 | .then(checkResult(testName, 'event', {Approval: {_value: '42', _owner: ADDR0, _spender: ADDR1}})) 652 | .catch(checkResult(testName, 'isError', false)) 653 | .then(finalResult(testName)); 654 | }, 655 | 656 | t5_transfer_from_event:function(testName, erc20) 657 | { 658 | serialExec([ 659 | next => {erc20.methods.approve(ADDR1, 50).send({from: ADDR0}) 660 | .catch(checkResult(testName, 'isError', false)) 661 | .then(next)}, 662 | 663 | next => {erc20.methods.transferFrom(ADDR0, ADDR2, 42).send({from: ADDR1}) 664 | .then(checkResult(testName, 'event', {Transfer: {_value: '42', _from: ADDR0, _to: ADDR2}})) 665 | .catch(checkResult(testName, 'isError', false)) 666 | .then(finalResult(testName))} 667 | ]) 668 | }, 669 | 670 | t5_zero_transfer_from_no_event:function(testName, erc20) 671 | { 672 | serialExec([ 673 | 674 | next => {erc20.methods.approve(ADDR1, 50).send({from: ADDR0}) 675 | .catch(checkResult(testName, 'isError', false)) 676 | .then(next)}, 677 | 678 | next => {erc20.methods.transferFrom(ADDR0, ADDR2, 0).send({from: ADDR1}) 679 | .then(checkResult(testName, 'hasEvent', false)) 680 | .catch(checkResult(testName, 'isError', false)) 681 | .then(finalResult(testName))} 682 | ]); 683 | }, 684 | 685 | } 686 | 687 | // ============================================================================= 688 | // Control Loop 689 | // 690 | // List the tests and execute the ones that match the selector Regex 691 | // 692 | 693 | var tests = getAllMethods(theTests); 694 | var testsToRun = tests.filter(function(name){return name.match(testSelector)}); 695 | var numTestsToRun = testsToRun.length; 696 | var allTestResults = {}; 697 | var testsPassed = 0, testsFailed = 0; 698 | 699 | console.log('Running ' + numTestsToRun + ' test' 700 | + (numTestsToRun == 1 ? '.' : 's.')); 701 | 702 | // We now run each test sequentially. This is is much kinder to the node than 703 | // blatting them all out asynchronously, and results in far fewer crashes. 704 | // runNextTest() will be called again via processResult(). 705 | runNextTest(); 706 | 707 | // ============================================================================= 708 | // Helper functions 709 | 710 | // Used to list the names of the available tests 711 | function getAllMethods(object) 712 | { 713 | return Object.getOwnPropertyNames(object).filter(function(property) { 714 | return typeof object[property] == 'function'; 715 | }); 716 | } 717 | 718 | // Re-deploy the contract and run the `test` function with name `testName` 719 | function newTest(testName, test) 720 | { 721 | ERC20.deploy().send().then( 722 | function(myContract){ 723 | if(myContract.options.address) { 724 | debug(2, '[newTest] Contract deployed to ' + myContract.options.address); 725 | debug(1, '[newTest] Starting ' + testName); 726 | return test(testName, myContract); 727 | } else { 728 | // TODO - error handling. 729 | } 730 | }); 731 | } 732 | 733 | // Checks the global list of remaining tests and runs the next if exists. 734 | function runNextTest() 735 | { 736 | let nextTestName = testsToRun.shift(); 737 | if (nextTestName) { 738 | allTestResults[nextTestName] = true; 739 | newTest(nextTestName, theTests[nextTestName]); 740 | } 741 | } 742 | 743 | // Sometimes it's useful to serialise the execution of the asynchronous calls 744 | function serialExec(callbacks) 745 | { 746 | function next() 747 | { 748 | let callback = callbacks.shift(); 749 | if(callback) { 750 | callback(function() {next();}); 751 | } 752 | } 753 | next(); 754 | } 755 | 756 | // Aggregate the results of each individual subtest making up a larger test 757 | function logResult(testName, pass) 758 | { 759 | allTestResults[testName] &= pass; 760 | debug(0, '[logResult] ' + testName + (pass ? ' PASSED' : ' FAILED')); 761 | } 762 | 763 | // Output the final result of a test when all promises have resolved 764 | function processResult(testName) 765 | { 766 | console.log(testName + ': ' 767 | + (allTestResults[testName] ? 'PASSED' : 'FAILED')); 768 | if (allTestResults[testName]) { 769 | testsPassed++; 770 | } else { 771 | testsFailed++; 772 | } 773 | console.log(' Tests passed: ' + testsPassed + '. Tests failed: ' + testsFailed + '.' + "\n"); 774 | 775 | runNextTest(); 776 | } 777 | 778 | // ----------------------------------------------------------------------------- 779 | // Callback functions, curried for ease of parameter handling. 780 | 781 | // Callback to check that the actual result of a test 782 | // matches the expected result. 783 | function checkResult(testName, type, expected) 784 | { 785 | return function(result) 786 | { 787 | testAssert(testName, result, type, expected); 788 | } 789 | } 790 | 791 | // Callback to output the final result of a test once all promises resolve 792 | function finalResult(testName) 793 | { 794 | return function(v) 795 | { 796 | processResult(testName); 797 | } 798 | } 799 | 800 | // ----------------------------------------------------------------------------- 801 | // For checking test results 802 | 803 | function testAssert(testName, result, type, expected) 804 | { 805 | debug(2, '[testAssert] result = ' + result + ', expected = ' + expected + ', type = ' + type); 806 | let pass = compare(type, result, expected); 807 | if (!pass) { 808 | debug(1, '*** Failure in test ' + testName); 809 | debug(1, ' Expected: ' + expected); 810 | debug(1, ' Got: ' + result); 811 | debug(1, ' Return: ' + pass); 812 | } 813 | logResult(testName, pass); 814 | } 815 | 816 | function compare(type, result, expected) 817 | { 818 | debug(3, '[compare] typeof result = ' + typeof result + ', typeof expected = ' + typeof expected); 819 | debug(3, '[compare] result = [' + result + '], expected = [' + expected + ']'); 820 | debug(2, '[compare] Looking for ' + type); 821 | switch(type) { 822 | case 'string': 823 | return(result === expected); 824 | break; 825 | case 'uint256': 826 | return(result === expected.toString()); 827 | break; 828 | case 'bytes32': 829 | return(result === '0x' + expected); 830 | break; 831 | case 'isReceipt': 832 | return((result.transactionHash !== undefined) === expected); 833 | break; 834 | case 'isError': 835 | return((result instanceof Error) === expected); 836 | break; 837 | case 'hasEvent': 838 | debug(3, '[compare] result.events: ' + JSON.stringify(result, null, 1)); 839 | return ((result.events !== undefined && Object.keys(result.events).length !== 0) === expected); 840 | break; 841 | case 'event': 842 | let events = result.events; 843 | if (events === undefined) { 844 | debug(3, '[compare] result does not have an events key:'); 845 | debug(3, '[compare] ' + JSON.stringify(result, null , 1)); 846 | return false; 847 | } 848 | let eventName = Object.keys(expected)[0]; 849 | if (events[eventName] === undefined) { 850 | debug(3, '[compare] event ' + eventName + ' not found.'); 851 | debug(3, '[compare] ' + JSON.stringify(events, null, 1)); 852 | return false; 853 | } 854 | let expKeys = Object.keys(expected[eventName]); 855 | for (let i=0; i= level) { 892 | console.log('DEBUG[' + level + '] ' + message); 893 | } 894 | } 895 | -------------------------------------------------------------------------------- /erc20_test/erc20_abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type":"constructor", 4 | "inputs":[], 5 | "outputs":[] 6 | }, 7 | { 8 | "constant":true, 9 | "inputs":[], 10 | "name":"name", 11 | "outputs": 12 | [ 13 | { 14 | "name":"", 15 | "type":"string" 16 | } 17 | ], 18 | "type":"function" 19 | }, 20 | { 21 | "constant":false, 22 | "inputs": 23 | [ 24 | { 25 | "name":"_spender", 26 | "type":"address" 27 | }, 28 | { 29 | "name":"_value", 30 | "type":"uint256" 31 | } 32 | ], 33 | "name":"approve", 34 | "outputs": 35 | [ 36 | { 37 | "name":"success", 38 | "type":"bool" 39 | } 40 | ], 41 | "type":"function" 42 | }, 43 | { 44 | "constant":true, 45 | "inputs":[], 46 | "name":"totalSupply", 47 | "outputs": 48 | [ 49 | {"name":"","type":"uint256"} 50 | ], 51 | "type":"function" 52 | }, 53 | { 54 | "constant":false, 55 | "inputs": 56 | [ 57 | { 58 | "name":"_from", 59 | "type":"address" 60 | }, 61 | { 62 | "name":"_to", 63 | "type":"address" 64 | }, 65 | { 66 | "name":"_value", 67 | "type":"uint256" 68 | } 69 | ], 70 | "name":"transferFrom", 71 | "outputs": 72 | [ 73 | { 74 | "name":"success", 75 | "type":"bool" 76 | } 77 | ], 78 | "type":"function" 79 | }, 80 | { 81 | "constant":true, 82 | "inputs":[], 83 | "name":"decimals", 84 | "outputs": 85 | [ 86 | { 87 | "name":"", 88 | "type":"uint8"} 89 | ], 90 | "type":"function" 91 | }, 92 | { 93 | "constant":true, 94 | "inputs": 95 | [ 96 | { 97 | "name":"_owner", 98 | "type":"address" 99 | } 100 | ], 101 | "name":"balanceOf", 102 | "outputs": 103 | [ 104 | { 105 | "name":"balance", 106 | "type":"uint256" 107 | } 108 | ], 109 | "type":"function" 110 | }, 111 | { 112 | "constant":true, 113 | "inputs":[], 114 | "name":"symbol", 115 | "outputs": 116 | [ 117 | { 118 | "name":"", 119 | "type":"string" 120 | } 121 | ], 122 | "type":"function" 123 | }, 124 | { 125 | "constant":false, 126 | "inputs": 127 | [ 128 | { 129 | "name":"_to", 130 | "type":"address" 131 | }, 132 | { 133 | "name":"_value", 134 | "type":"uint256" 135 | } 136 | ], 137 | "name":"transfer", 138 | "outputs": 139 | [ 140 | { 141 | "name":"success", 142 | "type":"bool" 143 | } 144 | ], 145 | "type":"function" 146 | }, 147 | { 148 | "constant":true, 149 | "inputs": 150 | [ 151 | { 152 | "name":"_owner", 153 | "type":"address" 154 | }, 155 | { 156 | "name":"_spender", 157 | "type":"address" 158 | } 159 | ], 160 | "name":"allowance", 161 | "outputs": 162 | [ 163 | { 164 | "name":"remaining", 165 | "type":"uint256" 166 | } 167 | ], 168 | "type":"function" 169 | }, 170 | { 171 | "anonymous":false, 172 | "inputs": 173 | [ 174 | { 175 | "indexed":true, 176 | "name":"_from", 177 | "type":"address" 178 | }, 179 | { 180 | "indexed":true, 181 | "name":"_to", 182 | "type":"address" 183 | }, 184 | { 185 | "indexed":false, 186 | "name":"_value", 187 | "type":"uint256" 188 | } 189 | ], 190 | "name":"Transfer", 191 | "type":"event" 192 | }, 193 | { 194 | "anonymous":false, 195 | "inputs": 196 | [ 197 | { 198 | "indexed":true, 199 | "name":"_owner", 200 | "type":"address" 201 | }, 202 | { 203 | "indexed":true, 204 | "name":"_spender", 205 | "type":"address" 206 | }, 207 | { 208 | "indexed":false, 209 | "name":"_value", 210 | "type":"uint256" 211 | } 212 | ], 213 | "name":"Approval", 214 | "type":"event" 215 | } 216 | ] 217 | -------------------------------------------------------------------------------- /erc20_test/erc20_evm.dat: -------------------------------------------------------------------------------- 1 | 3415600a5760006000fd5b606433556103748061001d6000396000f300341561000b5760006000fd5b600060005260046000601c600001376306fdde036000511415610047576020600052601f80610355604039602052601f19605f60205101166000f35b6395d89b416000511415610074576020600052600380610352604039602052601f19605f60205101166000f35b63313ce567600051141561008d57600060005260206000f35b6318160ddd60005114156100a657606460005260206000f35b6370a0823160005114156100c1576004355460005260206000f35b63a9059cbb600051141561017257366044146100dd5760006000fd5b60a060020a60043504156100f15760006000fd5b606460243511156101025760006000fd5b6024351561016757335460205260205160243511156101215760006000fd5b602435602051033355602435600435540160043555602435602052600435337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206020a35b600160005260206000f35b6323b872dd6000511415610272573660641461018e5760006000fd5b60a060020a60043504156101a25760006000fd5b60a060020a60243504156101b65760006000fd5b606460443511156101c75760006000fd5b60443515610267576004355460205233600052602060002060043501546040526001602051604435116101fd5750604051604435115b156102085760006000fd5b60443560205103600435556044356024355401602435556044356040510333600052602060002060043501556044356020526024356004357fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206020a35b600160005260206000f35b63095ea7b36000511415610324573660441461028e5760006000fd5b60a060020a60043504156102a25760006000fd5b606460243511156102b35760006000fd5b6000602435156102cd575060043560005260206000203301545b156102d85760006000fd5b6024356004356000526020600020330155602435602052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560206020a3600160005260206000f35b63dd62ed3e600051141561034b576024356000526020600020600435015460005260206000f35b60006000fd004c4c4c4c4c4c20436f696e202d206c6f766520746f20636f646520696e204c4c4c2e 2 | -------------------------------------------------------------------------------- /images/ERC20_gas_comparison_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminion/LLL_erc20/18cfbd32a08ec525668ee57533779903e2717b12/images/ERC20_gas_comparison_chart.png -------------------------------------------------------------------------------- /images/ERC20_gas_comparison_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminion/LLL_erc20/18cfbd32a08ec525668ee57533779903e2717b12/images/ERC20_gas_comparison_table.png --------------------------------------------------------------------------------