├── .gitattributes ├── .gitignore ├── README.md ├── contract ├── GST.abi ├── GST1.asm ├── GST1.sol ├── GST2_ETC.asm ├── GST2_ETC.sol ├── GST2_ETH.asm ├── GST2_ETH.sol ├── gst2_free_example.sol ├── rlp.sol └── test_helper.sol ├── miner ├── README.md ├── example_constant_price.py └── example_dynamic_price.py ├── requirements.txt ├── security_notes └── test ├── __init__.py ├── compare_schemes.py ├── generic_ERC20_token.py ├── generic_gas_token.py ├── test_GST1.py ├── test_GST2.py └── test_rlp.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,vim,linux,emacs,python,virtualenv,sublimetext 3 | 4 | ### Emacs ### 5 | # -*- mode: gitignore; -*- 6 | *~ 7 | \#*\# 8 | /.emacs.desktop 9 | /.emacs.desktop.lock 10 | *.elc 11 | auto-save-list 12 | tramp 13 | .\#* 14 | 15 | # Org-mode 16 | .org-id-locations 17 | *_archive 18 | 19 | # flymake-mode 20 | *_flymake.* 21 | 22 | # eshell files 23 | /eshell/history 24 | /eshell/lastdir 25 | 26 | # elpa packages 27 | /elpa/ 28 | 29 | # reftex files 30 | *.rel 31 | 32 | # AUCTeX auto folder 33 | /auto/ 34 | 35 | # cask packages 36 | .cask/ 37 | dist/ 38 | 39 | # Flycheck 40 | flycheck_*.el 41 | 42 | # server auth directory 43 | /server/ 44 | 45 | # projectiles files 46 | .projectile 47 | projectile-bookmarks.eld 48 | 49 | # directory configuration 50 | .dir-locals.el 51 | 52 | # saveplace 53 | places 54 | 55 | # url cache 56 | url/cache/ 57 | 58 | # cedet 59 | ede-projects.el 60 | 61 | # smex 62 | smex-items 63 | 64 | # company-statistics 65 | company-statistics-cache.el 66 | 67 | # anaconda-mode 68 | anaconda-mode/ 69 | 70 | ### Linux ### 71 | 72 | # temporary files which can be created if a process still has a handle open of a deleted file 73 | .fuse_hidden* 74 | 75 | # KDE directory preferences 76 | .directory 77 | 78 | # Linux trash folder which might appear on any partition or disk 79 | .Trash-* 80 | 81 | # .nfs files are created when an open file is removed but is still being accessed 82 | .nfs* 83 | 84 | ### OSX ### 85 | *.DS_Store 86 | .AppleDouble 87 | .LSOverride 88 | 89 | # Icon must end with two \r 90 | Icon 91 | 92 | # Thumbnails 93 | ._* 94 | 95 | # Files that might appear in the root of a volume 96 | .DocumentRevisions-V100 97 | .fseventsd 98 | .Spotlight-V100 99 | .TemporaryItems 100 | .Trashes 101 | .VolumeIcon.icns 102 | .com.apple.timemachine.donotpresent 103 | 104 | # Directories potentially created on remote AFP share 105 | .AppleDB 106 | .AppleDesktop 107 | Network Trash Folder 108 | Temporary Items 109 | .apdisk 110 | 111 | ### Python ### 112 | # Byte-compiled / optimized / DLL files 113 | __pycache__/ 114 | *.py[cod] 115 | *$py.class 116 | 117 | # C extensions 118 | *.so 119 | 120 | # Distribution / packaging 121 | .Python 122 | build/ 123 | develop-eggs/ 124 | downloads/ 125 | eggs/ 126 | .eggs/ 127 | lib/ 128 | lib64/ 129 | parts/ 130 | sdist/ 131 | var/ 132 | wheels/ 133 | *.egg-info/ 134 | .installed.cfg 135 | *.egg 136 | 137 | # PyInstaller 138 | # Usually these files are written by a python script from a template 139 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 140 | *.manifest 141 | *.spec 142 | 143 | # Installer logs 144 | pip-log.txt 145 | pip-delete-this-directory.txt 146 | 147 | # Unit test / coverage reports 148 | htmlcov/ 149 | .tox/ 150 | .coverage 151 | .coverage.* 152 | .cache 153 | .pytest_cache/ 154 | nosetests.xml 155 | coverage.xml 156 | *.cover 157 | .hypothesis/ 158 | 159 | # Translations 160 | *.mo 161 | *.pot 162 | 163 | # Flask stuff: 164 | instance/ 165 | .webassets-cache 166 | 167 | # Scrapy stuff: 168 | .scrapy 169 | 170 | # Sphinx documentation 171 | docs/_build/ 172 | 173 | # PyBuilder 174 | target/ 175 | 176 | # Jupyter Notebook 177 | .ipynb_checkpoints 178 | 179 | # pyenv 180 | .python-version 181 | 182 | # celery beat schedule file 183 | celerybeat-schedule.* 184 | 185 | # SageMath parsed files 186 | *.sage.py 187 | 188 | # Environments 189 | .env 190 | .venv 191 | env/ 192 | venv/ 193 | ENV/ 194 | env.bak/ 195 | venv.bak/ 196 | 197 | # Spyder project settings 198 | .spyderproject 199 | .spyproject 200 | 201 | # Rope project settings 202 | .ropeproject 203 | 204 | # mkdocs documentation 205 | /site 206 | 207 | # mypy 208 | .mypy_cache/ 209 | 210 | ### SublimeText ### 211 | # cache files for sublime text 212 | *.tmlanguage.cache 213 | *.tmPreferences.cache 214 | *.stTheme.cache 215 | 216 | # workspace files are user-specific 217 | *.sublime-workspace 218 | 219 | # project files should be checked into the repository, unless a significant 220 | # proportion of contributors will probably not be using SublimeText 221 | # *.sublime-project 222 | 223 | # sftp configuration file 224 | sftp-config.json 225 | 226 | # Package control specific files 227 | Package Control.last-run 228 | Package Control.ca-list 229 | Package Control.ca-bundle 230 | Package Control.system-ca-bundle 231 | Package Control.cache/ 232 | Package Control.ca-certs/ 233 | Package Control.merged-ca-bundle 234 | Package Control.user-ca-bundle 235 | oscrypto-ca-bundle.crt 236 | bh_unicode_properties.cache 237 | 238 | # Sublime-github package stores a github token in this file 239 | # https://packagecontrol.io/packages/sublime-github 240 | GitHub.sublime-settings 241 | 242 | ### Vim ### 243 | # swap 244 | .sw[a-p] 245 | .*.sw[a-p] 246 | # session 247 | Session.vim 248 | # temporary 249 | .netrwhist 250 | # auto-generated tag files 251 | tags 252 | 253 | ### VirtualEnv ### 254 | # Virtualenv 255 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 256 | [Bb]in 257 | [Ii]nclude 258 | [Ll]ib 259 | [Ll]ib64 260 | [Ll]ocal 261 | [Mm]an 262 | [Ss]cripts 263 | [Tt]cl 264 | pyvenv.cfg 265 | pip-selfcheck.json 266 | 267 | 268 | # End of https://www.gitignore.io/api/osx,vim,linux,emacs,python,virtualenv,sublimetext 269 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GasToken () 2 | 3 | GasToken is an Ethereum ERC20 smart contract that lets users tokenize gas, an internal resource used in Ethereum to pay for transactions. 4 | The idea is simple: store gas when gas prices are low, release it when gas prices are high and experience lower transaction fees. GasTokens can be traded like any other ERC20 token. 5 | 6 | The details on how GasToken works can be found here: 7 | 8 | This repository contains the Solidity code for two GasToken variants (GST1 and GST2) and exhaustive test suites of those contracts' functionalities. The `contract/` folder contains our smart contracts, the `test/` folder contains our pyethereum.tester based test suite. 9 | 10 | ## Deployments 11 | 12 | The contracts are deployed on Ethereum, Kovan (testnet), Rinkeby (testnet), Ropsten Revived (testnet), and Ethereum Classic. 13 | 14 | **Ethereum** 15 | + GST1: [`0x88d60255F917e3eb94eaE199d827DAd837fac4cB`](https://etherscan.io/address/0x88d60255f917e3eb94eae199d827dad837fac4cb) 16 | + GST2: [`0x0000000000b3F879cb30FE243b4Dfee438691c04`](https://etherscan.io/address/0x0000000000b3f879cb30fe243b4dfee438691c04) 17 | 18 | **Ropsten** 19 | + GST1: [`0x88d60255F917e3eb94eaE199d827DAd837fac4cB`](https://ropsten.etherscan.io/address/0x88d60255f917e3eb94eae199d827dad837fac4cb) 20 | + GST2: [`0x0000000000b3F879cb30FE243b4Dfee438691c04`](https://ropsten.etherscan.io/address/0x0000000000b3f879cb30fe243b4dfee438691c04) 21 | 22 | **Rinkeby** 23 | + GST1: [`0x88d60255F917e3eb94eaE199d827DAd837fac4cB`](https://rinkeby.etherscan.io/address/0x88d60255f917e3eb94eae199d827dad837fac4cb) 24 | + GST2: [`0x0000000000b3F879cb30FE243b4Dfee438691c04`](https://rinkeby.etherscan.io/address/0x0000000000b3f879cb30fe243b4dfee438691c04) 25 | 26 | **Kovan** 27 | + GST1: [`0x88d60255F917e3eb94eaE199d827DAd837fac4cB`](https://kovan.etherscan.io/address/0x88d60255f917e3eb94eae199d827dad837fac4cb) 28 | + GST2: [`0x0000000000170CcC93903185bE5A2094C870Df62`](https://kovan.etherscan.io/address/0x0000000000170ccc93903185be5a2094c870df62) 29 | 30 | **Ethereum Classic** 31 | + GST1: [`0x88d60255F917e3eb94eaE199d827DAd837fac4cB`](http://gastracker.io/contract/0x88d60255F917e3eb94eaE199d827DAd837fac4cB) 32 | + GST2: [`0x0000000000b3F879cb30FE243b4Dfee438691c04`](http://gastracker.io/contract/0x0000000000b3F879cb30FE243b4Dfee438691c04) 33 | 34 | The GST2 code deployed on Ethereum Classic slightly differs from that on Ethereum and the testnets. For details, check out `contract/GST2_ETC.sol` 35 | 36 | ## Dependencies 37 | 38 | The code was tested using python 3.6.2 and Solidity Version: 0.4.18+commit.9cf6e910.Darwin.appleclang 39 | 40 | Run 41 | 42 | ```sh 43 | $ python -m pip install -r requirements.txt 44 | ``` 45 | to install the required dependencies. 46 | 47 | ## Run Tests 48 | 49 | To run the tests for the Storage-based GasToken (GST1), run 50 | 51 | ```sh 52 | $ python -m test.test_GST1 53 | ``` 54 | 55 | To run the tests for the Contract-based GasToken (GST2), run 56 | 57 | ```sh 58 | $ python -m test.test_GST2 59 | ``` 60 | 61 | To run tests for the RLP encoding used in GST2, run 62 | 63 | ```sh 64 | $ python -m test.test_rlp 65 | ``` 66 | 67 | ## Authors 68 | 69 | We are a team of blockchain researchers from around the world: 70 | 71 | 76 | 77 | with advice, review, and support from Ari Juels (Cornell Tech, IC3, The Jacobs Institute). 78 | 79 | We offer absolutely no support, guarantees, advice, or other help with GasToken. If you like it, use it. 80 | -------------------------------------------------------------------------------- /contract/GST.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"value","type":"uint256"}],"name":"freeFromUpTo","outputs":[{"name":"freed","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"supply","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"value","type":"uint256"}],"name":"freeFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"freeUpTo","outputs":[{"name":"freed","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"free","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}] 2 | -------------------------------------------------------------------------------- /contract/GST1.asm: -------------------------------------------------------------------------------- 1 | 6060604052600436106100d0576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100d5578063079d229f14610163578063095ea7b3146101b957806318160ddd1461021357806323b872dd1461023c578063313ce567146102b55780635f2e2b45146102e45780636366b9361461033e57806370a082311461037557806395d89b41146103c2578063a0712d6814610450578063a9059cbb14610473578063d8ccd0f3146104cd578063dd62ed3e14610508575b600080fd5b34156100e057600080fd5b6100e8610574565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561012857808201518184015260208101905061010d565b50505050905090810190601f1680156101555780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561016e57600080fd5b6101a3600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506105ad565b6040518082815260200191505060405180910390f35b34156101c457600080fd5b6101f9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610733565b604051808215151515815260200191505060405180910390f35b341561021e57600080fd5b6102266108c7565b6040518082815260200191505060405180910390f35b341561024757600080fd5b61029b600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506108d9565b604051808215151515815260200191505060405180910390f35b34156102c057600080fd5b6102c8610a17565b604051808260ff1660ff16815260200191505060405180910390f35b34156102ef57600080fd5b610324600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610a1c565b604051808215151515815260200191505060405180910390f35b341561034957600080fd5b61035f6004808035906020019091905050610bae565b6040518082815260200191505060405180910390f35b341561038057600080fd5b6103ac600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610c55565b6040518082815260200191505060405180910390f35b34156103cd57600080fd5b6103d5610c9d565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104155780820151818401526020810190506103fa565b50505050905090810190601f1680156104425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561045b57600080fd5b6104716004808035906020019091905050610cd6565b005b341561047e57600080fd5b6104b3600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610d8a565b604051808215151515815260200191505060405180910390f35b34156104d857600080fd5b6104ee6004808035906020019091905050610da4565b604051808215151515815260200191505060405180910390f35b341561051357600080fd5b61055e600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610e52565b6040518082815260200191505060405180910390f35b6040805190810160405280600b81526020017f476173746f6b656e2e696f00000000000000000000000000000000000000000081525081565b60008060008060003393506000808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054925082861115610605578295505b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002091508160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080861115610693578095505b61069c86610ed9565b8583036000808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508581038260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508594505050505092915050565b600080339050600083141580156107c757506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156107d557600091506108c0565b82600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a3600191505b5092915050565b60008063deadbeef9050805491505090565b600080339050600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205483111580156109725750610971858585610f26565b5b15610a0a5782600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555060019150610a0f565b600091505b509392505050565b600281565b60008060008060003393506000808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054925082861115610a795760009450610ba4565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002091508160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080861115610b0c5760009450610ba4565b610b1586610ed9565b8583036000808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508581038260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600194505b5050505092915050565b6000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080831115610bfe578092505b610c0783610ed9565b8281036000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555082915050919050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6040805190810160405280600481526020017f475354310000000000000000000000000000000000000000000000000000000081525081565b600080600080600063deadbeef94506000861415610cf357610d82565b84549350600184860101925085848601019150828210151515610d1257fe5b8290505b8181111515610d3057600181558080600101915050610d16565b8584018555856000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505b505050505050565b600080339050610d9b818585610f26565b91505092915050565b6000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080831115610df95760009150610e4c565b610e0283610ed9565b8281036000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600191505b50919050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600080600080600063deadbeef9450845493506001868587010301925083850191508290505b8181111515610f1957600081558080600101915050610eff565b8584038555505050505050565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151561107557816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905061107a565b600090505b93925050505600a165627a7a72305820814e4e76e31471bb35c564b5fa7168f3094feb8b51738e7895e9b727701d27650029 -------------------------------------------------------------------------------- /contract/GST1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | contract GasToken1 { 4 | ////////////////////////////////////////////////////////////////////////// 5 | // Generic ERC20 6 | ////////////////////////////////////////////////////////////////////////// 7 | 8 | // owner -> amount 9 | mapping(address => uint256) s_balances; 10 | // owner -> spender -> max amount 11 | mapping(address => mapping(address => uint256)) s_allowances; 12 | 13 | event Transfer(address indexed from, address indexed to, uint256 value); 14 | 15 | event Approval(address indexed owner, address indexed spender, uint256 value); 16 | 17 | // Spec: Get the account balance of another account with address `owner` 18 | function balanceOf(address owner) public constant returns (uint256 balance) { 19 | return s_balances[owner]; 20 | } 21 | 22 | function internalTransfer(address from, address to, uint256 value) internal returns (bool success) { 23 | if (value <= s_balances[from]) { 24 | s_balances[from] -= value; 25 | s_balances[to] += value; 26 | Transfer(from, to, value); 27 | return true; 28 | } else { 29 | return false; 30 | } 31 | } 32 | 33 | // Spec: Send `value` amount of tokens to address `to` 34 | function transfer(address to, uint256 value) public returns (bool success) { 35 | address from = msg.sender; 36 | return internalTransfer(from, to, value); 37 | } 38 | 39 | // Spec: Send `value` amount of tokens from address `from` to address `to` 40 | function transferFrom(address from, address to, uint256 value) public returns (bool success) { 41 | address spender = msg.sender; 42 | if(value <= s_allowances[from][spender] && internalTransfer(from, to, value)) { 43 | s_allowances[from][spender] -= value; 44 | return true; 45 | } else { 46 | return false; 47 | } 48 | } 49 | 50 | // Spec: Allow `spender` to withdraw from your account, multiple times, up 51 | // to the `value` amount. If this function is called again it overwrites the 52 | // current allowance with `value`. 53 | function approve(address spender, uint256 value) public returns (bool success) { 54 | address owner = msg.sender; 55 | if (value != 0 && s_allowances[owner][spender] != 0) { 56 | return false; 57 | } 58 | s_allowances[owner][spender] = value; 59 | Approval(owner, spender, value); 60 | return true; 61 | } 62 | 63 | // Spec: Returns the `amount` which `spender` is still allowed to withdraw 64 | // from `owner`. 65 | // What if the allowance is higher than the balance of the `owner`? 66 | // Callers should be careful to use min(allowance, balanceOf) to make sure 67 | // that the allowance is actually present in the account! 68 | function allowance(address owner, address spender) public constant returns (uint256 remaining) { 69 | return s_allowances[owner][spender]; 70 | } 71 | 72 | ////////////////////////////////////////////////////////////////////////// 73 | // GasToken specifics 74 | ////////////////////////////////////////////////////////////////////////// 75 | 76 | uint8 constant public decimals = 2; 77 | string constant public name = "Gastoken.io"; 78 | string constant public symbol = "GST1"; 79 | 80 | // We start our storage at this location. The EVM word at this location 81 | // contains the number of stored words. The stored words follow at 82 | // locations (STORAGE_LOCATION_ARRAY+1), (STORAGE_LOCATION_ARRAY+2), ... 83 | uint256 constant STORAGE_LOCATION_ARRAY = 0xDEADBEEF; 84 | 85 | 86 | // totalSupply is the number of words we have in storage 87 | function totalSupply() public constant returns (uint256 supply) { 88 | uint256 storage_location_array = STORAGE_LOCATION_ARRAY; 89 | assembly { 90 | supply := sload(storage_location_array) 91 | } 92 | } 93 | 94 | // Mints `value` new sub-tokens (e.g. cents, pennies, ...) by filling up 95 | // `value` words of EVM storage. The minted tokens are owned by the 96 | // caller of this function. 97 | function mint(uint256 value) public { 98 | uint256 storage_location_array = STORAGE_LOCATION_ARRAY; // can't use constants inside assembly 99 | 100 | if (value == 0) { 101 | return; 102 | } 103 | 104 | // Read supply 105 | uint256 supply; 106 | assembly { 107 | supply := sload(storage_location_array) 108 | } 109 | 110 | // Set memory locations in interval [l, r] 111 | uint256 l = storage_location_array + supply + 1; 112 | uint256 r = storage_location_array + supply + value; 113 | assert(r >= l); 114 | 115 | for (uint256 i = l; i <= r; i++) { 116 | assembly { 117 | sstore(i, 1) 118 | } 119 | } 120 | 121 | // Write updated supply & balance 122 | assembly { 123 | sstore(storage_location_array, add(supply, value)) 124 | } 125 | s_balances[msg.sender] += value; 126 | } 127 | 128 | function freeStorage(uint256 value) internal { 129 | uint256 storage_location_array = STORAGE_LOCATION_ARRAY; // can't use constants inside assembly 130 | 131 | // Read supply 132 | uint256 supply; 133 | assembly { 134 | supply := sload(storage_location_array) 135 | } 136 | 137 | // Clear memory locations in interval [l, r] 138 | uint256 l = storage_location_array + supply - value + 1; 139 | uint256 r = storage_location_array + supply; 140 | for (uint256 i = l; i <= r; i++) { 141 | assembly { 142 | sstore(i, 0) 143 | } 144 | } 145 | 146 | // Write updated supply 147 | assembly { 148 | sstore(storage_location_array, sub(supply, value)) 149 | } 150 | } 151 | 152 | // Frees `value` sub-tokens (e.g. cents, pennies, ...) belonging to the 153 | // caller of this function by clearing value words of EVM storage, which 154 | // will trigger a partial gas refund. 155 | function free(uint256 value) public returns (bool success) { 156 | uint256 from_balance = s_balances[msg.sender]; 157 | if (value > from_balance) { 158 | return false; 159 | } 160 | 161 | freeStorage(value); 162 | 163 | s_balances[msg.sender] = from_balance - value; 164 | 165 | return true; 166 | } 167 | 168 | // Frees up to `value` sub-tokens. Returns how many tokens were freed. 169 | // Otherwise, identical to free. 170 | function freeUpTo(uint256 value) public returns (uint256 freed) { 171 | uint256 from_balance = s_balances[msg.sender]; 172 | if (value > from_balance) { 173 | value = from_balance; 174 | } 175 | 176 | freeStorage(value); 177 | 178 | s_balances[msg.sender] = from_balance - value; 179 | 180 | return value; 181 | } 182 | 183 | // Frees `value` sub-tokens owned by address `from`. Requires that `msg.sender` 184 | // has been approved by `from`. 185 | function freeFrom(address from, uint256 value) public returns (bool success) { 186 | address spender = msg.sender; 187 | uint256 from_balance = s_balances[from]; 188 | if (value > from_balance) { 189 | return false; 190 | } 191 | 192 | mapping(address => uint256) from_allowances = s_allowances[from]; 193 | uint256 spender_allowance = from_allowances[spender]; 194 | if (value > spender_allowance) { 195 | return false; 196 | } 197 | 198 | freeStorage(value); 199 | 200 | s_balances[from] = from_balance - value; 201 | from_allowances[spender] = spender_allowance - value; 202 | 203 | return true; 204 | } 205 | 206 | // Frees up to `value` sub-tokens owned by address `from`. Returns how many tokens were freed. 207 | // Otherwise, identical to `freeFrom`. 208 | function freeFromUpTo(address from, uint256 value) public returns (uint256 freed) { 209 | address spender = msg.sender; 210 | uint256 from_balance = s_balances[from]; 211 | if (value > from_balance) { 212 | value = from_balance; 213 | } 214 | 215 | mapping(address => uint256) from_allowances = s_allowances[from]; 216 | uint256 spender_allowance = from_allowances[spender]; 217 | if (value > spender_allowance) { 218 | value = spender_allowance; 219 | } 220 | 221 | freeStorage(value); 222 | 223 | s_balances[from] = from_balance - value; 224 | from_allowances[spender] = spender_allowance - value; 225 | 226 | return value; 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /contract/GST2_ETC.asm: -------------------------------------------------------------------------------- 1 | 6060604052600436106100d0576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100d5578063079d229f14610163578063095ea7b3146101b957806318160ddd1461021357806323b872dd1461023c578063313ce567146102b55780635f2e2b45146102e45780636366b9361461033e57806370a082311461037557806395d89b41146103c2578063a0712d6814610450578063a9059cbb14610473578063d8ccd0f3146104cd578063dd62ed3e14610508575b600080fd5b34156100e057600080fd5b6100e8610574565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561012857808201518184015260208101905061010d565b50505050905090810190601f1680156101555780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561016e57600080fd5b6101a3600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506105ad565b6040518082815260200191505060405180910390f35b34156101c457600080fd5b6101f9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610733565b604051808215151515815260200191505060405180910390f35b341561021e57600080fd5b6102266108c7565b6040518082815260200191505060405180910390f35b341561024757600080fd5b61029b600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506108d5565b604051808215151515815260200191505060405180910390f35b34156102c057600080fd5b6102c8610a13565b604051808260ff1660ff16815260200191505060405180910390f35b34156102ef57600080fd5b610324600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610a18565b604051808215151515815260200191505060405180910390f35b341561034957600080fd5b61035f6004808035906020019091905050610baa565b6040518082815260200191505060405180910390f35b341561038057600080fd5b6103ac600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610c51565b6040518082815260200191505060405180910390f35b34156103cd57600080fd5b6103d5610c99565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104155780820151818401526020810190506103fa565b50505050905090810190601f1680156104425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561045b57600080fd5b6104716004808035906020019091905050610cd2565b005b341561047e57600080fd5b6104b3600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610d7a565b604051808215151515815260200191505060405180910390f35b34156104d857600080fd5b6104ee6004808035906020019091905050610d94565b604051808215151515815260200191505060405180910390f35b341561051357600080fd5b61055e600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610e42565b6040518082815260200191505060405180910390f35b6040805190810160405280600b81526020017f476173746f6b656e2e696f00000000000000000000000000000000000000000081525081565b60008060008060003393506000808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054925082861115610605578295505b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002091508160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080861115610693578095505b61069c86610ec9565b8583036000808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508581038260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508594505050505092915050565b600080339050600083141580156107c757506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156107d557600091506108c0565b82600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a3600191505b5092915050565b600060035460025403905090565b600080339050600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054831115801561096e575061096d858585610f3f565b5b15610a065782600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555060019150610a0b565b600091505b509392505050565b600281565b60008060008060003393506000808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054925082861115610a755760009450610ba0565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002091508160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080861115610b085760009450610ba0565b610b1186610ec9565b8583036000808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508581038260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600194505b5050505092915050565b6000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080831115610bfa578092505b610c0383610ec9565b8281036000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555082915050919050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6040805190810160405280600481526020017f475354320000000000000000000000000000000000000000000000000000000081525081565b60008090505b81811015610d1a576000610cea61109a565b73ffffffffffffffffffffffffffffffffffffffff1614151515610d0d57600080fd5b8080600101915050610cd8565b81600260008282540192505081905550816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505050565b600080339050610d8b818585610f3f565b91505092915050565b6000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080831115610de95760009150610e3c565b610df283610ec9565b8281036000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600191505b50919050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60008060035491506001820190505b82820181111515610f3157610eed30826110d0565b73ffffffffffffffffffffffffffffffffffffffff165a60405160006040518083038160008787f1925050501515610f2457600080fd5b8080600101915050610ed8565b828201600381905550505050565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151561108e57816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050611093565b600090505b9392505050565b60006040517e756eb3f879cb30fe243b4dfee438691c043318585733ff6000526016600af38152601f600182016000f091505090565b60008060008060008068ffffffffffffffffff87111515156110f157600080fd5b8660001080156111015750608087105b15611113576001945060019350611125565b61111c8761120d565b94506001850193505b8360146001010192506a01000000000000000000008873ffffffffffffffffffffffffffffffffffffffff16027e010000000000000000000000000000000000000000000000000000000000006014608001027f01000000000000000000000000000000000000000000000000000000000000008560c00102010191508660001080156111b25750608087105b156111cd5769010000000000000000008702820191506111f0565b6901000000000000000000856080010282019150846009036101000a8702820191505b604051828152600184018120915050809550505050505092915050565b6000806000809150600190505b8084101515611235576001820191506101008102905061121a565b81925050509190505600a165627a7a7230582042113ef75de85d02868edf18a500cdeffe3b36ab06f3fae0036276f263d200250029 -------------------------------------------------------------------------------- /contract/GST2_ETC.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | import "rlp.sol"; 4 | 5 | contract GasToken2 is Rlp { 6 | ////////////////////////////////////////////////////////////////////////// 7 | // Generic ERC20 8 | ////////////////////////////////////////////////////////////////////////// 9 | 10 | // owner -> amount 11 | mapping(address => uint256) s_balances; 12 | // owner -> spender -> max amount 13 | mapping(address => mapping(address => uint256)) s_allowances; 14 | 15 | event Transfer(address indexed from, address indexed to, uint256 value); 16 | 17 | event Approval(address indexed owner, address indexed spender, uint256 value); 18 | 19 | // Spec: Get the account balance of another account with address `owner` 20 | function balanceOf(address owner) public constant returns (uint256 balance) { 21 | return s_balances[owner]; 22 | } 23 | 24 | function internalTransfer(address from, address to, uint256 value) internal returns (bool success) { 25 | if (value <= s_balances[from]) { 26 | s_balances[from] -= value; 27 | s_balances[to] += value; 28 | Transfer(from, to, value); 29 | return true; 30 | } else { 31 | return false; 32 | } 33 | } 34 | 35 | // Spec: Send `value` amount of tokens to address `to` 36 | function transfer(address to, uint256 value) public returns (bool success) { 37 | address from = msg.sender; 38 | return internalTransfer(from, to, value); 39 | } 40 | 41 | // Spec: Send `value` amount of tokens from address `from` to address `to` 42 | function transferFrom(address from, address to, uint256 value) public returns (bool success) { 43 | address spender = msg.sender; 44 | if(value <= s_allowances[from][spender] && internalTransfer(from, to, value)) { 45 | s_allowances[from][spender] -= value; 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } 51 | 52 | // Spec: Allow `spender` to withdraw from your account, multiple times, up 53 | // to the `value` amount. If this function is called again it overwrites the 54 | // current allowance with `value`. 55 | function approve(address spender, uint256 value) public returns (bool success) { 56 | address owner = msg.sender; 57 | if (value != 0 && s_allowances[owner][spender] != 0) { 58 | return false; 59 | } 60 | s_allowances[owner][spender] = value; 61 | Approval(owner, spender, value); 62 | return true; 63 | } 64 | 65 | // Spec: Returns the `amount` which `spender` is still allowed to withdraw 66 | // from `owner`. 67 | // What if the allowance is higher than the balance of the `owner`? 68 | // Callers should be careful to use min(allowance, balanceOf) to make sure 69 | // that the allowance is actually present in the account! 70 | function allowance(address owner, address spender) public constant returns (uint256 remaining) { 71 | return s_allowances[owner][spender]; 72 | } 73 | 74 | ////////////////////////////////////////////////////////////////////////// 75 | // GasToken specifics 76 | ////////////////////////////////////////////////////////////////////////// 77 | 78 | uint8 constant public decimals = 2; 79 | string constant public name = "Gastoken.io"; 80 | string constant public symbol = "GST2"; 81 | 82 | // We build a queue of nonces at which child contracts are stored. s_head is 83 | // the nonce at the head of the queue, s_tail is the nonce behind the tail 84 | // of the queue. The queue grows at the head and shrinks from the tail. 85 | // Note that when and only when a contract CREATEs another contract, the 86 | // creating contract's nonce is incremented. 87 | // The first child contract is created with nonce == 1, the second child 88 | // contract is created with nonce == 2, and so on... 89 | // For example, if there are child contracts at nonces [2,3,4], 90 | // then s_head == 4 and s_tail == 1. If there are no child contracts, 91 | // s_head == s_tail. 92 | uint256 s_head; 93 | uint256 s_tail; 94 | 95 | // totalSupply gives the number of tokens currently in existence 96 | // Each token corresponds to one child contract that can be SELFDESTRUCTed 97 | // for a gas refund. 98 | function totalSupply() public constant returns (uint256 supply) { 99 | return s_head - s_tail; 100 | } 101 | 102 | // Creates a child contract that can only be destroyed by this contract. 103 | function makeChild() internal returns (address addr) { 104 | assembly { 105 | // EVM assembler of runtime portion of child contract: 106 | // ;; Pseudocode: if (msg.sender != 0x0000000000b3f879cb30fe243b4dfee438691c04) { throw; } 107 | // ;; suicide(msg.sender) 108 | // PUSH15 0xb3f879cb30fe243b4dfee438691c04 ;; hardcoded address of this contract 109 | // CALLER 110 | // XOR 111 | // PC 112 | // JUMPI 113 | // CALLER 114 | // SELFDESTRUCT 115 | // Or in binary: 6eb3f879cb30fe243b4dfee438691c043318585733ff 116 | // Since the binary is so short (22 bytes), we can get away 117 | // with a very simple initcode: 118 | // PUSH22 0x6eb3f879cb30fe243b4dfee438691c043318585733ff 119 | // PUSH1 0 120 | // MSTORE ;; at this point, memory locations mem[10] through 121 | // ;; mem[31] contain the runtime portion of the child 122 | // ;; contract. all that's left to do is to RETURN this 123 | // ;; chunk of memory. 124 | // PUSH1 22 ;; length 125 | // PUSH1 10 ;; offset 126 | // RETURN 127 | // Or in binary: 756eb3f879cb30fe243b4dfee438691c043318585733ff6000526016600af3 128 | // Almost done! All we have to do is put this short (31 bytes) blob into 129 | // memory and call CREATE with the appropriate offsets. 130 | let solidity_free_mem_ptr := mload(0x40) 131 | mstore(solidity_free_mem_ptr, 0x00756eb3f879cb30fe243b4dfee438691c043318585733ff6000526016600af3) 132 | addr := create(0, add(solidity_free_mem_ptr, 1), 31) 133 | } 134 | } 135 | 136 | // Mints `value` new sub-tokens (e.g. cents, pennies, ...) by creating `value` 137 | // new child contracts. The minted tokens are owned by the caller of this 138 | // function. 139 | function mint(uint256 value) public { 140 | for (uint256 i = 0; i < value; i++) { 141 | require(makeChild() != 0); 142 | } 143 | s_head += value; 144 | s_balances[msg.sender] += value; 145 | } 146 | 147 | // Destroys `value` child contracts and updates s_tail. 148 | function destroyChildren(uint256 value) internal { 149 | uint256 tail = s_tail; 150 | // tail points to slot behind the last contract in the queue 151 | for (uint256 i = tail + 1; i <= tail + value; i++) { 152 | require(mk_contract_address(this, i).call.gas(msg.gas)()); 153 | } 154 | 155 | s_tail = tail + value; 156 | } 157 | 158 | // Frees `value` sub-tokens (e.g. cents, pennies, ...) belonging to the 159 | // caller of this function by destroying `value` child contracts, which 160 | // will trigger a partial gas refund. 161 | function free(uint256 value) public returns (bool success) { 162 | uint256 from_balance = s_balances[msg.sender]; 163 | if (value > from_balance) { 164 | return false; 165 | } 166 | 167 | destroyChildren(value); 168 | 169 | s_balances[msg.sender] = from_balance - value; 170 | 171 | return true; 172 | } 173 | 174 | // Frees up to `value` sub-tokens. Returns how many tokens were freed. 175 | // Otherwise, identical to free. 176 | function freeUpTo(uint256 value) public returns (uint256 freed) { 177 | uint256 from_balance = s_balances[msg.sender]; 178 | if (value > from_balance) { 179 | value = from_balance; 180 | } 181 | 182 | destroyChildren(value); 183 | 184 | s_balances[msg.sender] = from_balance - value; 185 | 186 | return value; 187 | } 188 | 189 | // Frees `value` sub-tokens owned by address `from`. Requires that `msg.sender` 190 | // has been approved by `from`. 191 | function freeFrom(address from, uint256 value) public returns (bool success) { 192 | address spender = msg.sender; 193 | uint256 from_balance = s_balances[from]; 194 | if (value > from_balance) { 195 | return false; 196 | } 197 | 198 | mapping(address => uint256) from_allowances = s_allowances[from]; 199 | uint256 spender_allowance = from_allowances[spender]; 200 | if (value > spender_allowance) { 201 | return false; 202 | } 203 | 204 | destroyChildren(value); 205 | 206 | s_balances[from] = from_balance - value; 207 | from_allowances[spender] = spender_allowance - value; 208 | 209 | return true; 210 | } 211 | 212 | // Frees up to `value` sub-tokens owned by address `from`. Returns how many tokens were freed. 213 | // Otherwise, identical to `freeFrom`. 214 | function freeFromUpTo(address from, uint256 value) public returns (uint256 freed) { 215 | address spender = msg.sender; 216 | uint256 from_balance = s_balances[from]; 217 | if (value > from_balance) { 218 | value = from_balance; 219 | } 220 | 221 | mapping(address => uint256) from_allowances = s_allowances[from]; 222 | uint256 spender_allowance = from_allowances[spender]; 223 | if (value > spender_allowance) { 224 | value = spender_allowance; 225 | } 226 | 227 | destroyChildren(value); 228 | 229 | s_balances[from] = from_balance - value; 230 | from_allowances[spender] = spender_allowance - value; 231 | 232 | return value; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /contract/GST2_ETH.asm: -------------------------------------------------------------------------------- 1 | 606060405236156100ce576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100d3578063079d229f14610162578063095ea7b3146101b857806318160ddd1461021257806323b872dd1461023b578063313ce567146102b45780635f2e2b45146102e35780636366b9361461033d57806370a082311461037457806395d89b41146103c1578063a0712d6814610450578063a9059cbb14610473578063d8ccd0f3146104cd578063dd62ed3e14610508575b600080fd5b34156100de57600080fd5b6100e6610574565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101275780820151818401525b60208101905061010b565b50505050905090810190601f1680156101545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561016d57600080fd5b6101a2600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506105ad565b6040518082815260200191505060405180910390f35b34156101c357600080fd5b6101f8600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610734565b604051808215151515815260200191505060405180910390f35b341561021d57600080fd5b6102256108c8565b6040518082815260200191505060405180910390f35b341561024657600080fd5b61029a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506108d7565b604051808215151515815260200191505060405180910390f35b34156102bf57600080fd5b6102c7610a1a565b604051808260ff1660ff16815260200191505060405180910390f35b34156102ee57600080fd5b610323600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610a1f565b604051808215151515815260200191505060405180910390f35b341561034857600080fd5b61035e6004808035906020019091905050610bb1565b6040518082815260200191505060405180910390f35b341561037f57600080fd5b6103ab600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610c59565b6040518082815260200191505060405180910390f35b34156103cc57600080fd5b6103d4610ca2565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104155780820151818401525b6020810190506103f9565b50505050905090810190601f1680156104425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561045b57600080fd5b6104716004808035906020019091905050610cdb565b005b341561047e57600080fd5b6104b3600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610d61565b604051808215151515815260200191505060405180910390f35b34156104d857600080fd5b6104ee6004808035906020019091905050610d7c565b604051808215151515815260200191505060405180910390f35b341561051357600080fd5b61055e600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610e2a565b6040518082815260200191505060405180910390f35b6040805190810160405280600b81526020017f476173746f6b656e2e696f00000000000000000000000000000000000000000081525081565b60008060008060003393506000808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054925082861115610605578295505b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002091508160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080861115610693578095505b61069c86610eb2565b8583036000808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508581038260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508594505b5050505092915050565b600080339050600083141580156107c857506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156107d657600091506108c1565b82600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a3600191505b5092915050565b60006003546002540390505b90565b600080339050600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548311158015610970575061096f858585610f22565b5b15610a085782600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555060019150610a12565b60009150610a12565b5b509392505050565b600281565b60008060008060003393506000808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054925082861115610a7c5760009450610ba7565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002091508160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080861115610b0f5760009450610ba7565b610b1886610eb2565b8583036000808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508581038260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600194505b5050505092915050565b6000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080831115610c01578092505b610c0a83610eb2565b8281036000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508291505b50919050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b6040805190810160405280600481526020017f475354320000000000000000000000000000000000000000000000000000000081525081565b60008090505b81811015610d0057610cf1611082565b505b8080600101915050610ce1565b81600260008282540192505081905550816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505b5050565b600080339050610d72818585610f22565b91505b5092915050565b6000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905080831115610dd15760009150610e24565b610dda83610eb2565b8281036000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600191505b50919050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b92915050565b60008060035491506001820190505b82820181111515610f1357610ed630826110b9565b73ffffffffffffffffffffffffffffffffffffffff1660405160006040518083038160008661646e5a03f1915050505b8080600101915050610ec1565b8282016003819055505b505050565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151561107157816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905061107b565b6000905061107b565b5b9392505050565b60006040517e756eb3f879cb30fe243b4dfee438691c043318585733ff6000526016600af38152601f600182016000f09150505b90565b60008060008060008068ffffffffffffffffff87111515156110da57600080fd5b8660001080156110ea5750608087105b156110fc57600194506001935061110e565b611105876111fe565b94506001850193505b8360146001010192506a01000000000000000000008873ffffffffffffffffffffffffffffffffffffffff16027e010000000000000000000000000000000000000000000000000000000000006014608001027f01000000000000000000000000000000000000000000000000000000000000008560c001020101915086600010801561119b5750608087105b156111b65769010000000000000000008702820191506111d9565b6901000000000000000000856080010282019150846009036101000a8702820191505b604051602081016040528281526001840181209150508095505b505050505092915050565b6000806000809150600190505b8084101515611226576001820191506101008102905061120b565b8192505b50509190505600a165627a7a72305820b86bb85a6e7dcfc4473f394716365fd772c0511b80fdd7833b2966335f3a07b20029 -------------------------------------------------------------------------------- /contract/GST2_ETH.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | import "rlp.sol"; 4 | 5 | contract GasToken2 is Rlp { 6 | ////////////////////////////////////////////////////////////////////////// 7 | // Generic ERC20 8 | ////////////////////////////////////////////////////////////////////////// 9 | 10 | // owner -> amount 11 | mapping(address => uint256) s_balances; 12 | // owner -> spender -> max amount 13 | mapping(address => mapping(address => uint256)) s_allowances; 14 | 15 | event Transfer(address indexed from, address indexed to, uint256 value); 16 | 17 | event Approval(address indexed owner, address indexed spender, uint256 value); 18 | 19 | // Spec: Get the account balance of another account with address `owner` 20 | function balanceOf(address owner) public constant returns (uint256 balance) { 21 | return s_balances[owner]; 22 | } 23 | 24 | function internalTransfer(address from, address to, uint256 value) internal returns (bool success) { 25 | if (value <= s_balances[from]) { 26 | s_balances[from] -= value; 27 | s_balances[to] += value; 28 | Transfer(from, to, value); 29 | return true; 30 | } else { 31 | return false; 32 | } 33 | } 34 | 35 | // Spec: Send `value` amount of tokens to address `to` 36 | function transfer(address to, uint256 value) public returns (bool success) { 37 | address from = msg.sender; 38 | return internalTransfer(from, to, value); 39 | } 40 | 41 | // Spec: Send `value` amount of tokens from address `from` to address `to` 42 | function transferFrom(address from, address to, uint256 value) public returns (bool success) { 43 | address spender = msg.sender; 44 | if(value <= s_allowances[from][spender] && internalTransfer(from, to, value)) { 45 | s_allowances[from][spender] -= value; 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } 51 | 52 | // Spec: Allow `spender` to withdraw from your account, multiple times, up 53 | // to the `value` amount. If this function is called again it overwrites the 54 | // current allowance with `value`. 55 | function approve(address spender, uint256 value) public returns (bool success) { 56 | address owner = msg.sender; 57 | if (value != 0 && s_allowances[owner][spender] != 0) { 58 | return false; 59 | } 60 | s_allowances[owner][spender] = value; 61 | Approval(owner, spender, value); 62 | return true; 63 | } 64 | 65 | // Spec: Returns the `amount` which `spender` is still allowed to withdraw 66 | // from `owner`. 67 | // What if the allowance is higher than the balance of the `owner`? 68 | // Callers should be careful to use min(allowance, balanceOf) to make sure 69 | // that the allowance is actually present in the account! 70 | function allowance(address owner, address spender) public constant returns (uint256 remaining) { 71 | return s_allowances[owner][spender]; 72 | } 73 | 74 | ////////////////////////////////////////////////////////////////////////// 75 | // GasToken specifics 76 | ////////////////////////////////////////////////////////////////////////// 77 | 78 | uint8 constant public decimals = 2; 79 | string constant public name = "Gastoken.io"; 80 | string constant public symbol = "GST2"; 81 | 82 | // We build a queue of nonces at which child contracts are stored. s_head is 83 | // the nonce at the head of the queue, s_tail is the nonce behind the tail 84 | // of the queue. The queue grows at the head and shrinks from the tail. 85 | // Note that when and only when a contract CREATEs another contract, the 86 | // creating contract's nonce is incremented. 87 | // The first child contract is created with nonce == 1, the second child 88 | // contract is created with nonce == 2, and so on... 89 | // For example, if there are child contracts at nonces [2,3,4], 90 | // then s_head == 4 and s_tail == 1. If there are no child contracts, 91 | // s_head == s_tail. 92 | uint256 s_head; 93 | uint256 s_tail; 94 | 95 | // totalSupply gives the number of tokens currently in existence 96 | // Each token corresponds to one child contract that can be SELFDESTRUCTed 97 | // for a gas refund. 98 | function totalSupply() public constant returns (uint256 supply) { 99 | return s_head - s_tail; 100 | } 101 | 102 | // Creates a child contract that can only be destroyed by this contract. 103 | function makeChild() internal returns (address addr) { 104 | assembly { 105 | // EVM assembler of runtime portion of child contract: 106 | // ;; Pseudocode: if (msg.sender != 0x0000000000b3f879cb30fe243b4dfee438691c04) { throw; } 107 | // ;; suicide(msg.sender) 108 | // PUSH15 0xb3f879cb30fe243b4dfee438691c04 ;; hardcoded address of this contract 109 | // CALLER 110 | // XOR 111 | // PC 112 | // JUMPI 113 | // CALLER 114 | // SELFDESTRUCT 115 | // Or in binary: 6eb3f879cb30fe243b4dfee438691c043318585733ff 116 | // Since the binary is so short (22 bytes), we can get away 117 | // with a very simple initcode: 118 | // PUSH22 0x6eb3f879cb30fe243b4dfee438691c043318585733ff 119 | // PUSH1 0 120 | // MSTORE ;; at this point, memory locations mem[10] through 121 | // ;; mem[31] contain the runtime portion of the child 122 | // ;; contract. all that's left to do is to RETURN this 123 | // ;; chunk of memory. 124 | // PUSH1 22 ;; length 125 | // PUSH1 10 ;; offset 126 | // RETURN 127 | // Or in binary: 756eb3f879cb30fe243b4dfee438691c043318585733ff6000526016600af3 128 | // Almost done! All we have to do is put this short (31 bytes) blob into 129 | // memory and call CREATE with the appropriate offsets. 130 | let solidity_free_mem_ptr := mload(0x40) 131 | mstore(solidity_free_mem_ptr, 0x00756eb3f879cb30fe243b4dfee438691c043318585733ff6000526016600af3) 132 | addr := create(0, add(solidity_free_mem_ptr, 1), 31) 133 | } 134 | } 135 | 136 | // Mints `value` new sub-tokens (e.g. cents, pennies, ...) by creating `value` 137 | // new child contracts. The minted tokens are owned by the caller of this 138 | // function. 139 | function mint(uint256 value) public { 140 | for (uint256 i = 0; i < value; i++) { 141 | makeChild(); 142 | } 143 | s_head += value; 144 | s_balances[msg.sender] += value; 145 | } 146 | 147 | // Destroys `value` child contracts and updates s_tail. 148 | // 149 | // This function is affected by an issue in solc: https://github.com/ethereum/solidity/issues/2999 150 | // The `mk_contract_address(this, i).call();` doesn't forward all available gas, but only GAS - 25710. 151 | // As a result, when this line is executed with e.g. 30000 gas, the callee will have less than 5000 gas 152 | // available and its SELFDESTRUCT operation will fail leading to no gas refund occurring. 153 | // The remaining ~29000 gas left after the call is enough to update s_tail and the caller's balance. 154 | // Hence tokens will have been destroyed without a commensurate gas refund. 155 | // Fortunately, there is a simple workaround: 156 | // Whenever you call free, freeUpTo, freeFrom, or freeUpToFrom, ensure that you pass at least 157 | // 25710 + `value` * (1148 + 5722 + 150) gas. (It won't all be used) 158 | function destroyChildren(uint256 value) internal { 159 | uint256 tail = s_tail; 160 | // tail points to slot behind the last contract in the queue 161 | for (uint256 i = tail + 1; i <= tail + value; i++) { 162 | mk_contract_address(this, i).call(); 163 | } 164 | 165 | s_tail = tail + value; 166 | } 167 | 168 | // Frees `value` sub-tokens (e.g. cents, pennies, ...) belonging to the 169 | // caller of this function by destroying `value` child contracts, which 170 | // will trigger a partial gas refund. 171 | // You should ensure that you pass at least 25710 + `value` * (1148 + 5722 + 150) gas 172 | // when calling this function. For details, see the comment above `destroyChildren`. 173 | function free(uint256 value) public returns (bool success) { 174 | uint256 from_balance = s_balances[msg.sender]; 175 | if (value > from_balance) { 176 | return false; 177 | } 178 | 179 | destroyChildren(value); 180 | 181 | s_balances[msg.sender] = from_balance - value; 182 | 183 | return true; 184 | } 185 | 186 | // Frees up to `value` sub-tokens. Returns how many tokens were freed. 187 | // Otherwise, identical to free. 188 | // You should ensure that you pass at least 25710 + `value` * (1148 + 5722 + 150) gas 189 | // when calling this function. For details, see the comment above `destroyChildren`. 190 | function freeUpTo(uint256 value) public returns (uint256 freed) { 191 | uint256 from_balance = s_balances[msg.sender]; 192 | if (value > from_balance) { 193 | value = from_balance; 194 | } 195 | 196 | destroyChildren(value); 197 | 198 | s_balances[msg.sender] = from_balance - value; 199 | 200 | return value; 201 | } 202 | 203 | // Frees `value` sub-tokens owned by address `from`. Requires that `msg.sender` 204 | // has been approved by `from`. 205 | // You should ensure that you pass at least 25710 + `value` * (1148 + 5722 + 150) gas 206 | // when calling this function. For details, see the comment above `destroyChildren`. 207 | function freeFrom(address from, uint256 value) public returns (bool success) { 208 | address spender = msg.sender; 209 | uint256 from_balance = s_balances[from]; 210 | if (value > from_balance) { 211 | return false; 212 | } 213 | 214 | mapping(address => uint256) from_allowances = s_allowances[from]; 215 | uint256 spender_allowance = from_allowances[spender]; 216 | if (value > spender_allowance) { 217 | return false; 218 | } 219 | 220 | destroyChildren(value); 221 | 222 | s_balances[from] = from_balance - value; 223 | from_allowances[spender] = spender_allowance - value; 224 | 225 | return true; 226 | } 227 | 228 | // Frees up to `value` sub-tokens owned by address `from`. Returns how many tokens were freed. 229 | // Otherwise, identical to `freeFrom`. 230 | // You should ensure that you pass at least 25710 + `value` * (1148 + 5722 + 150) gas 231 | // when calling this function. For details, see the comment above `destroyChildren`. 232 | function freeFromUpTo(address from, uint256 value) public returns (uint256 freed) { 233 | address spender = msg.sender; 234 | uint256 from_balance = s_balances[from]; 235 | if (value > from_balance) { 236 | value = from_balance; 237 | } 238 | 239 | mapping(address => uint256) from_allowances = s_allowances[from]; 240 | uint256 spender_allowance = from_allowances[spender]; 241 | if (value > spender_allowance) { 242 | value = spender_allowance; 243 | } 244 | 245 | destroyChildren(value); 246 | 247 | s_balances[from] = from_balance - value; 248 | from_allowances[spender] = spender_allowance - value; 249 | 250 | return value; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /contract/gst2_free_example.sol: -------------------------------------------------------------------------------- 1 | contract GST2 { 2 | function freeUpTo(uint256 value) public returns (uint256 freed); 3 | function freeFromUpTo(address from, uint256 value) public returns (uint256 freed); 4 | } 5 | 6 | contract GST2FreeExample { 7 | function freeExample(uint num_tokens) public returns (uint freed) { 8 | // we need at least 9 | // num_tokens * (1148 + 5722 + 150) + 25710 gas before entering destroyChildren 10 | // ^ mk_contract_address 11 | // ^ solidity bug constant 12 | // ^ cost of invocation 13 | // ^ loop, etc... 14 | // to be on the safe side, let's add another constant 2k gas 15 | // for CALLing freeFrom, reading from storage, etc... 16 | // so we get 17 | // gas cost to freeFromUpTo n tokens <= 27710 + n * (1148 + 5722 + 150) 18 | 19 | // Note that 27710 is sufficiently large that we always have enough 20 | // gas left to update s_tail, balance, etc... after we are done 21 | // with destroyChildren. 22 | 23 | GST2 gst2 = GST2(0x0000000000b3F879cb30FE243b4Dfee438691c04); 24 | 25 | uint safe_num_tokens = 0; 26 | uint gas = msg.gas; 27 | 28 | if (gas >= 27710) { 29 | safe_num_tokens = (gas - 27710) / (1148 + 5722 + 150); 30 | } 31 | 32 | if (num_tokens > safe_num_tokens) { 33 | num_tokens = safe_num_tokens; 34 | } 35 | 36 | if (num_tokens > 0) { 37 | return gst2.freeUpTo(num_tokens); 38 | } else { 39 | return 0; 40 | } 41 | } 42 | 43 | function freeFromExample(address from, uint num_tokens) public returns (uint freed) { 44 | // we need at least 45 | // num_tokens * (1148 + 5722 + 150) + 25710 gas before entering destroyChildren 46 | // ^ mk_contract_address 47 | // ^ solidity bug constant 48 | // ^ cost of invocation 49 | // ^ loop, etc... 50 | // to be on the safe side, let's add another constant 2k gas 51 | // for CALLing freeFrom, reading from storage, etc... 52 | // so we get 53 | // gas cost to freeFromUpTo n tokens <= 27710 + n * (1148 + 5722 + 150) 54 | 55 | // Note that 27710 is sufficiently large that we always have enough 56 | // gas left to update s_tail, balance, etc... after we are done 57 | // with destroyChildren. 58 | 59 | GST2 gst2 = GST2(0x0000000000b3F879cb30FE243b4Dfee438691c04); 60 | 61 | uint safe_num_tokens = 0; 62 | uint gas = msg.gas; 63 | 64 | if (gas >= 27710) { 65 | safe_num_tokens = (gas - 27710) / (1148 + 5722 + 150); 66 | } 67 | 68 | if (num_tokens > safe_num_tokens) { 69 | num_tokens = safe_num_tokens; 70 | } 71 | 72 | if (num_tokens > 0) { 73 | return gst2.freeFromUpTo(from, num_tokens); 74 | } else { 75 | return 0; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contract/rlp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | contract Rlp { 4 | 5 | uint256 constant ADDRESS_BYTES = 20; 6 | uint256 constant MAX_SINGLE_BYTE = 128; 7 | uint256 constant MAX_NONCE = 256**9 - 1; 8 | 9 | // count number of bytes required to represent an unsigned integer 10 | function count_bytes(uint256 n) pure internal returns (uint256 c) { 11 | uint i = 0; 12 | uint mask = 1; 13 | while (n >= mask) { 14 | i += 1; 15 | mask *= 256; 16 | } 17 | 18 | return i; 19 | } 20 | 21 | function mk_contract_address(address a, uint256 n) pure internal returns (address rlp) { 22 | /* 23 | * make sure the RLP encoding fits in one word: 24 | * total_length 1 byte 25 | * address_length 1 byte 26 | * address 20 bytes 27 | * nonce_length 1 byte (or 0) 28 | * nonce 1-9 bytes 29 | * ========== 30 | * 24-32 bytes 31 | */ 32 | require(n <= MAX_NONCE); 33 | 34 | // number of bytes required to write down the nonce 35 | uint256 nonce_bytes; 36 | // length in bytes of the RLP encoding of the nonce 37 | uint256 nonce_rlp_len; 38 | 39 | if (0 < n && n < MAX_SINGLE_BYTE) { 40 | // nonce fits in a single byte 41 | // RLP(nonce) = nonce 42 | nonce_bytes = 1; 43 | nonce_rlp_len = 1; 44 | } else { 45 | // RLP(nonce) = [num_bytes_in_nonce nonce] 46 | nonce_bytes = count_bytes(n); 47 | nonce_rlp_len = nonce_bytes + 1; 48 | } 49 | 50 | // [address_length(1) address(20) nonce_length(0 or 1) nonce(1-9)] 51 | uint256 tot_bytes = 1 + ADDRESS_BYTES + nonce_rlp_len; 52 | 53 | // concatenate all parts of the RLP encoding in the leading bytes of 54 | // one 32-byte word 55 | uint256 word = ((192 + tot_bytes) * 256**31) + 56 | ((128 + ADDRESS_BYTES) * 256**30) + 57 | (uint256(a) * 256**10); 58 | 59 | if (0 < n && n < MAX_SINGLE_BYTE) { 60 | word += n * 256**9; 61 | } else { 62 | word += (128 + nonce_bytes) * 256**9; 63 | word += n * 256**(9 - nonce_bytes); 64 | } 65 | 66 | uint256 hash; 67 | 68 | assembly { 69 | let mem_start := mload(0x40) // get a pointer to free memory 70 | mstore(mem_start, word) // store the rlp encoding 71 | hash := sha3(mem_start, 72 | add(tot_bytes, 1)) // hash the rlp encoding 73 | } 74 | 75 | // interpret hash as address (20 least significant bytes) 76 | return address(hash); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contract/test_helper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | contract Token { 4 | function free(uint256 value) returns (bool success); 5 | function freeUpTo(uint256 value) returns (uint256 freed); 6 | function freeFrom(address from, uint256 value) returns (bool success); 7 | function freeFromUpTo(address from, uint256 value) returns (uint256 freed); 8 | } 9 | 10 | contract TestHelper { 11 | 12 | function dummy() { 13 | assembly{ 14 | invalid 15 | } 16 | } 17 | 18 | // Burns at least burn gas by calling itself and throwing 19 | function burnGas(uint256 burn) { 20 | // call self.dummy() to burn a bunch of gas 21 | assembly { 22 | mstore(0x0, 0x32e43a1100000000000000000000000000000000000000000000000000000000) 23 | let ret := call(burn, address, 0, 0x0, 0x04, 0x0, 0) 24 | } 25 | } 26 | 27 | function burnGasAndFree(address gas_token, uint256 burn, uint256 free) { 28 | burnGas(burn); 29 | require(Token(gas_token).free(free)); 30 | } 31 | 32 | function burnGasAndFreeUpTo(address gas_token, uint256 burn, uint256 free) { 33 | burnGas(burn); 34 | require(free == Token(gas_token).freeUpTo(free)); 35 | } 36 | 37 | function burnGasAndFreeFrom(address gas_token, uint256 burn, uint256 free) { 38 | burnGas(burn); 39 | require(Token(gas_token).freeFrom(tx.origin, free)); 40 | } 41 | 42 | function burnGasAndFreeFromUpTo(address gas_token, uint256 burn, uint256 free) { 43 | burnGas(burn); 44 | require(free == Token(gas_token).freeFromUpTo(tx.origin, free)); 45 | } 46 | } -------------------------------------------------------------------------------- /miner/README.md: -------------------------------------------------------------------------------- 1 | THE MINERS HERE ARE PROVIDED FOR EXAMPLE ONLY AND MAY BURN YOUR MONEY. 2 | 3 | USE AT YOUR OWN RISK; NO WARANTEE OR GUARANTEES ARE PROVIDED 4 | -------------------------------------------------------------------------------- /miner/example_constant_price.py: -------------------------------------------------------------------------------- 1 | from eth_abi import encode_abi 2 | from eth_utils import ( 3 | encode_hex, 4 | function_abi_to_4byte_selector, 5 | ) 6 | import web3, time, json 7 | from ethereum.tester import languages 8 | 9 | BATCH_SIZE = 4 10 | w3 = web3.Web3(web3.IPCProvider('/home/debian/geth_temp_fast/geth.ipc')) 11 | timeout = 999999999 # seconds 12 | batch_timeout = 1200 13 | gas_delta = 0 14 | pool_addr = '0xTODO' # your addr here 15 | contract_address = "0x0000000000b3f879cb30fe243b4dfee438691c04" 16 | gas_price = int(6e9) + 5 # desired gas price here TODO 17 | abi = json.loads('[{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"free","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]') 18 | start_nonce = w3.eth.getTransactionCount(pool_addr) 19 | 20 | curr_nonce = start_nonce 21 | while True: 22 | batch_start_nonce = w3.eth.getTransactionCount(pool_addr) 23 | w3.personal.unlockAccount(pool_addr, '') 24 | t0 = time.time() 25 | # do tx 26 | fn_abi = abi[4] 27 | assert fn_abi['name'] == 'mint' 28 | fn_selector = function_abi_to_4byte_selector(fn_abi) 29 | for i in range(0, BATCH_SIZE): 30 | transaction = { 31 | "from": pool_addr, 32 | "to": contract_address, 33 | "gas": 999000 + gas_delta, # 1M, 0x1a | 216k 5 34 | "gasPrice": (gas_price), 35 | "data": encode_hex(fn_selector + encode_abi(["uint256"], [0x1a])), 36 | } 37 | if curr_nonce is not None: 38 | transaction["nonce"] = curr_nonce 39 | curr_nonce += 1 40 | txn_hash = w3.eth.sendTransaction(transaction) 41 | print(txn_hash, curr_nonce - 1) 42 | receipt = None 43 | batch_done = time.time() 44 | while True: 45 | try: 46 | while w3.eth.getTransactionCount(pool_addr) < batch_start_nonce + BATCH_SIZE and time.time() - batch_done < batch_timeout: 47 | time.sleep(2) 48 | curr_nonce = w3.eth.getTransactionCount(pool_addr) 49 | if time.time() - batch_done > batch_timeout: 50 | gas_delta += 1 51 | break 52 | except: 53 | pass 54 | print(time.time(), "\n", receipt) 55 | 56 | 57 | -------------------------------------------------------------------------------- /miner/example_dynamic_price.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | from eth_abi import encode_abi 4 | from eth_utils import ( 5 | encode_hex, 6 | function_abi_to_4byte_selector, 7 | ) 8 | import web3, time, json 9 | from ethereum.tester import languages 10 | 11 | batch_size = 4 12 | w3 = web3.Web3(web3.IPCProvider('/home/debian/geth_temp_fast/geth.ipc')) 13 | timeout = 999999999 # seconds 14 | batch_timeout = 1000 15 | wei_per_second = 42738118437506803190 / (604800.0 * 2.8) # target wei to consume / (seconds in 1wk) 16 | code = '/home/ubuntu/gas/token.sol' 17 | pool_addr = '0xTODO' # your address here 18 | contract_address = "0x0000000000b3f879cb30fe243b4dfee438691c04" 19 | gas_price = int(4e9) + 5 # start gas price (1gwei + epsilon) 20 | gas_delta = int(2e9) 21 | max_buy = int(25e9) 22 | abi = json.loads('[{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"free","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]') 23 | tx_gas_consumed = 982614 24 | 25 | # Load old batch times 26 | # batchtimes is list of (end_nonce,time_start,time_end,wei_consumed) 27 | batchtimes = open("batchtimes").read().splitlines() 28 | batchtimes = [[int(float(y)) for y in x.split(",")] for x in batchtimes] 29 | 30 | print("Initial batchtimes loaded, showing last 50") 31 | print(batchtimes[-50:]) 32 | 33 | while True: 34 | batch_start_nonce = w3.eth.getTransactionCount(pool_addr) 35 | curr_nonce = batch_start_nonce 36 | while True: 37 | try: 38 | w3.personal.unlockAccount(pool_addr, '') 39 | break 40 | except: 41 | pass 42 | t0 = time.time() 43 | # do tx 44 | fn_abi = abi[4] 45 | assert fn_abi['name'] == 'mint' 46 | fn_selector = function_abi_to_4byte_selector(fn_abi) 47 | if gas_price > max_buy: 48 | gas_price = max_buy 49 | assert(gas_price <= max_buy) # sanity check circuit breaker 50 | for i in range(0, batch_size): 51 | transaction = { 52 | "from": pool_addr, 53 | "to": contract_address, 54 | "gas": 999999, # 982615, 0x1a | 216k 5 55 | "gasPrice": (gas_price), 56 | "data": encode_hex(fn_selector + encode_abi(["uint256"], [0x1a])), 57 | "nonce": curr_nonce 58 | } 59 | txn_hash = None 60 | while True: 61 | try: 62 | txn_hash = w3.eth.sendTransaction(transaction) 63 | break 64 | except Exception as e: 65 | print("web3 comms failed") 66 | print(e) 67 | if "known" in str(e) or "underpriced" in str(e): 68 | break # script was restarted, tx already sent 69 | time.sleep(5) 70 | print("tx sent", txn_hash, curr_nonce) 71 | curr_nonce += 1 72 | batch_sent = time.time() 73 | print("batch sent!", batch_sent) 74 | while True: 75 | try: 76 | timed_out = False 77 | while w3.eth.getTransactionCount(pool_addr) < (batch_start_nonce + batch_size): 78 | time.sleep(2) 79 | if time.time() - batch_sent > batch_timeout: 80 | gas_price += gas_delta 81 | timed_out = True 82 | print("Timed out, upping price. New price", gas_price) 83 | break 84 | if timed_out: 85 | break 86 | curr_nonce = w3.eth.getTransactionCount(pool_addr) 87 | # batchtimes is list of (end_nonce,time_start,time_end,wei_consumed) 88 | batchtuple = (curr_nonce,batch_sent,time.time(),1.0*gas_price*tx_gas_consumed*batch_size) 89 | batchtimes += [batchtuple] 90 | print("batch mined", batchtuple) 91 | open("batchtimes", "a").write(",".join([str(int(x)) for x in batchtuple]) + "\n") 92 | burn_rate = batchtuple[3] / (batchtuple[2] - batchtuple[1]) 93 | adjustment_factor = min(max((wei_per_second / burn_rate), .75), 2.0) 94 | new_price = max(int(adjustment_factor * gas_price), int(1e9) + 20) 95 | print("Adjusting gas. Burn rate / target / adjustment factor / old gas / new gas") 96 | print(burn_rate, wei_per_second, adjustment_factor, gas_price, new_price) 97 | gas_price = new_price 98 | break 99 | except Exception as e: 100 | print(e) 101 | pass 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # version 1.1.2 is broken 2 | py_ecc==1.1.1 3 | pycryptodome 4 | git+https://github.com/ethereum/pyethereum.git 5 | -------------------------------------------------------------------------------- /security_notes: -------------------------------------------------------------------------------- 1 | [ THESE ARE SECURITY NOTES AND SHOULD NOT BE TAKEN AS A FULL, PROFESSIONAL, OR 2 | INDEPENDENT AUDIT: THEIR CREATOR WAS PARTY TO THE DEVELOPMENT OF GASTOKEN AND 3 | CAN THUS NOT OBJECTIVELY EVALUATE THE SECURITY OF THIS PROJECT. PLEASE 4 | PERFORM OR COMMISION YOUR OWN REVIEWS IF TRUSTING ANY CONTRACTS WITH 5 | FUNDS ] 6 | 7 | [ Contract Versions Analyzed ] 8 | 9 | gas_coin/contract$ sha256sum *.sol 10 | c0828747f108f506d7880212f32d1d11fb1aa9ff9e51d3048d3a6019dc2bf83b contract_gas_token.sol 11 | b983d85cb4bc5ac1c067a71cd900504592908063ae3ac4e08d8cc0f52326324c frugal_gst2_example.sol 12 | e8e9cf5ac59a95b85d47da9dcaa134b470034c487ae54bf4e8e47edf1a643df2 gas_token.sol 13 | d0223c3f56cca4c2a204805056784596697415f1b6b212b9f37002ce184006fe rlp.sol 14 | 0ffab56081c08d6b03b48c6f23b3bec966b077265a0a54e22d3c2c7670c1520a test_helper.sol 15 | 16 | [ ERC20 Exploits ] 17 | 18 | The contract is vulnerable to the classic ERC20 anti-patterns, including the short address 19 | and transaction ordering-based exploits. 20 | 21 | In terms of ERC20 correctness, the contract is based on a differentially developed Hydra-based 22 | ERC20 where one of the heads has undergone formal verification. The differential test suite 23 | used to test both this and the official Viper ERC20, with full statement and decision coverage, 24 | is tested against all GasToken ERC20 code. 25 | 26 | [ Arithmetic Errors ] 27 | 28 | (rlp.sol): 29 | 30 | - count_bytes ; Will always be called with a maximum nonce of n = 256 ** 9 (a reasonable 31 | upper bound on the number of GST2 contracts ever created), which is required for 32 | correctness. This invariant is shared with the mk_contract_address function, which 33 | is the only caller of count_bytes, can can be trivially audited to call passing through 34 | the correct value. No overflow in either *i* or *mask* is possible in this value range 35 | by inspection. This invariant is enforced in the code by the check to the caller, 36 | checking that the provided nonce is less than MAX_NONCE, the maximum number of contracts 37 | ever created for GST2. 38 | 39 | - mk_contract_address ; Will always be called with a maximum nonce of n = 256 ** 9 (a reasonable 40 | upper bound on the number of GST2 contracts ever created), which is required for 41 | correctness. The lines with potentially problematic arithmetic: 42 | 43 | uint256 tot_bytes = 1 + ADDRESS_BYTES + nonce_rlp_len; (max of 21 if nonce_rlp_len functions) 44 | uint256 word = ((192 + tot_bytes) * 256**31) + 45 | ((128 + ADDRESS_BYTES) * 256**30) + 46 | (uint256(a) * 256**10); 47 | 48 | uint256 word = ((192 + 21) * 256**31) + 49 | ((128 + 20) * 256**30) + 50 | (2 ** 160 * 256**10); 51 | 52 | max value: ((192 + 21) * 256**31) + ((128 + 20) * 256**30) + (2 ** 160 * 256**10) 53 | (< 2^256) 54 | 55 | if (0 < n && n < MAX_SINGLE_BYTE) { 56 | word += n * 256**9; 57 | } else { 58 | word += (128 + nonce_bytes) * 256**9; 59 | word += n * 256**(9 - nonce_bytes); 60 | } 61 | 62 | Top branch; n is at most 128; adding (128 * 256 ** 9) to word; no overflow 63 | 64 | Bottom branch; always less than the above if nonce_bytes is truly the number of bytes 65 | representing *n* 66 | 67 | 68 | (ERC20 functions): 69 | 70 | All subtractions are overflow-checked by inspection. Additions are not, but are safe assuming that value 71 | is conserved by transfers, no exploits exist in mint/free/etc, and totalSupply never exceeds ~2 ** 255, far 72 | higher than we require. 73 | 74 | (contract_gas_token.sol): 75 | 76 | totalSupply(): no overhead if invariant that s_head >= s_tail. If s_head < s_tail, contract state is anyway corrupted. 77 | 78 | mint(): loop will run exactly value times (no overflow possible in loop counter / bounds). Value is then bounded by 79 | available gas to quite a small number, and if both balances and totalSupply are not corrupted, no overflow should occur 80 | in the remainder of the function. 81 | 82 | destroyChildren(): This time, value is bounded by balance of sender's account; overflow is impossible if tail + value < INT_MAX, 83 | which is certainly the case if the loop variant is enforced (max value that can be mined in 1 block is in the hundreds). 84 | The function is internal, so cannot be called directly; in all calls, we manually inspect that the critical overflow present in the 85 | loop bounds of this function, that could cause the loop not to run, is guarded against by checks that the sender actually has *value* 86 | balance available, placing implicit bounds on tail + value (as we assume supply will never exceed 5 billion). 87 | 88 | Additional ERC20 helper functions all do subtraction after explicitly overflow checking, so are not vulnerable to overflow. 89 | Any subtracted balances have been bound-checked, as have subtracted allowances. 90 | 91 | (gas_token.sol): 92 | 93 | mint(): as above, loop will run exactly value times (no overflow possible in loop counter / bounds by explicit overflow check). 94 | Value is then bounded by available gas to quite a small number, and if both balances and totalSupply are not corrupted, 95 | no overflow should occur in the remainder of the function. 96 | 97 | freeStorage(): identical to above but with subtraction. In all calls to this internal function, it is explicitly checked that 98 | value < sender_balance, which can be bounded at 5 billion by our supply bound invariants. storage_location_array + supply 99 | can then not overflow; subtracting value also cannot underflow as value < supply if supply is uncorrupted. 100 | 101 | ERC20 helpers: Identical to above. 102 | 103 | 104 | [ Denial of Service / Gas Limits ] 105 | 106 | We exclude ERC20 functions here; as previously described, the ERC20 functions in this contract 107 | were duplicated from an existing ERC20, with overflow checks _removed_, making them strictly 108 | more efficient in gas behavior and more permissive w.r.t. the class of all inputs. 109 | 110 | (rlp.sol): 111 | 112 | - count_bytes ; Only possible DoS is while loop running infinitely, which WILL occur with 113 | a called value of between 2^255 and 2^256-1. This however is not an issue as such values are explicitly 114 | disallowed by the maximum nonce guard in the only caller of this internal function. 115 | 116 | - mk_contract_address ; The only potential DoS vector is in the inline assembly, as none of the Solidity 117 | operations in this function can throw. 118 | 119 | assembly { 120 | let mem_start := mload(0x40) // get a pointer to free memory 121 | mstore(mem_start, word) // store the rlp encoding 122 | hash := sha3(mem_start, 123 | add(tot_bytes, 1)) // hash the rlp encoding 124 | } 125 | 126 | mload should never fail, nor should mstore; the sha3 operation is tested as working against all reasonable 127 | inputs, which cannot be adversarially controlled (the only calls to rlp.sol's internal functions are with 128 | controlled, monotonically increasing addresses, and our base balance). SHA3 of word should always succeed 129 | anyway, so no operations in this function are vulnerable to DoS. 130 | 131 | (contract_gas_token.sol): 132 | 133 | makeChild(): the only failure possible here is in create, which can fail due to OOG. 134 | There are a few possible cases here: 135 | - There is not enough gas for the CREATE itself (the 32k required gas). In this case, the outer call 136 | frame OOGs, no vulnrability occurs. 137 | - There is enough gas for the CREATE to run, but the constructor or storage of the constructed contract to 138 | OOG. Both these operations together require <7k gas, which we have empirically checked. Two SSTOREs need to 139 | occur in the outer frame, which costs at minimum 10k gas (the minimum cost for a storage update is 5k gas). 140 | Therefore, OOGing in CREATE will cause an OOG in the outer call frame. 141 | 142 | A potential uncaught OOG in a CREATE in makeChild could be serious, as it could credit a user for minting 143 | a token without debiting the requisite gas. This would essentially break GasToken, allowing attackers to 144 | arbitrarily create tokens at will. Today, the security of GasToken relies STRONGLY on the current gas rules 145 | of the EVM, and could at any time be compromised by their modification. 146 | 147 | create can potentially fail for reasons other than OOG, such as a hash collision causing an existing contract 148 | to exist at that address or indexing corruption in the outer contract, though these are unlikely eventualities. 149 | 150 | A similar pattern exists in free / destroyChildren by inspection, though we do not analyze this because a failure 151 | here results only in a user losing their refund (and can for example surely occur due to compiler bugs such as 152 | https://github.com/ethereum/solidity/issues/2999 , which create is not vulnerable to by virtue of its inability 153 | to take custom gas amounts) 154 | 155 | There are no operations vulnerable to failure, by inspection, in the ERC20 helper or mint functions, 156 | unless the balances array is corrupted (in which case service can obviously be denied to victims of this 157 | corruption). 158 | 159 | (gas_token.sol): 160 | 161 | No variable-gas or potential DoS vulnerabilities (arbitrary throws, etc.) are possible in any of the functions by 162 | inspection. The only exception is the overflow check on mint loop bounds, which if an attacker can trigger to always 163 | throw, would deny service to token generation. This is not a security issue, as existing users can still transfer 164 | or free tokens, and is only possible with corruption of totalSupply (as totalSupply + value + 0xDEADBEEF must overflow). 165 | 166 | There are no operations vulnerable to failure, by inspection, in the ERC20 helper or mint functions, 167 | unless the balances array is corrupted (in which case service can obviously be denied to victims of this 168 | corruption). 169 | 170 | Other operations like SSTORE can only fail due to OOG, which will propagate a failure in the outer frame. 171 | 172 | [ Inheritance Shadowing ] 173 | 174 | The only inheritance in the codebase is GST2 extending RLP, which has been manually 175 | inspected and does not contain any shadowing. 176 | 177 | [ Upgradeability ] 178 | 179 | Contracts are explicitly non-upgradeable. See: "Human in the Loop". 180 | 181 | [ Stuck Ether ] 182 | 183 | No payable functions exist in the contract, so no stuck Ether is possible. 184 | 185 | [ Timestamp / RNG ] 186 | 187 | No timestamp or RNG functionality is included in any of the analyzed contracts. 188 | 189 | [ Human in the loop ] 190 | 191 | We explicitly place no human in the loop on these contracts, warning clients in our website documentation. 192 | 193 | Human in the loop would be undesireable for this contract, where it would give the human an ability to 194 | deny service to stored gas or redistribute stored gas, violating the intended purpose of the contract. 195 | 196 | [ Game Theoretic Bugs ] 197 | 198 | Outside the scope of this review. 199 | 200 | [ Re-entrancy / recursive send ] 201 | 202 | The only external call in any of the contracts potentially vulnerable to re-entrancy is on line 203 | 162 of GST2: 204 | 205 | mk_contract_address(this, i).call(); 206 | 207 | If an attacker is able to control the output of mk_contract_address to the extent that it generates 208 | an adversarially chosen address, the attacker would be able to exploit the following re-entrancy bug: 209 | 210 | 211 | Call free -> re-enter into fre from destroyChildren's call -> call free -> (...) 212 | 213 | 214 | Essentially allowing them to free a larger amount of contracts than should be enabled by their 215 | balance, "stealing" gas refunds from legitimate users of GST2. 216 | 217 | Note that mk_contract_address only has a single call anywhere in GST2, and is otherwise marked 218 | internal (cannot be called externally). In this call, the base address of "this" is used, which 219 | will always resolve to the deployment address of GST2 (0x0000....). The nonce / "i" value is also 220 | not user controlled; it will sweep from s_tail+1 to tail + value inclusive. It is therefore 221 | unlikely that, barring hash collisions, a user will be able to deploy arbitrary code at freed 222 | addresses, assuming the correctness of the mk_contract_address function given its inputs. 223 | Because there are relatively exhaustive tests for this in the suite, the probability of 224 | exploitability is low. 225 | 226 | [ Stack issues ] 227 | 228 | Solidity will primarily loudly fail on stack overflows, with the exception of the "unchecked 229 | call/create pattern". We use this pattern in only two locations in the entire audited codebase, 230 | both in GST2. We rely on the built-in protection of Tangerine Whistle to prevent critical failures due 231 | to silent stack overflow failure, e.g. in the mint function where it would allow for creation of 232 | unbounded tokens. We analyze these "unchecked sends" w.r.t. gas exhaustion in the DoS section, 233 | and argue that they are benign, but recommend asserting the create in the mint function for 234 | added redundancy (a change that has been made for ETC but deemed not-secutity-critical for ETH). 235 | 236 | [ Input-Controlled Jumps ] 237 | 238 | No jumps that are controlled by user inputs (that would allow for example accessing of internal functions). 239 | This includes default functions, which are not present in any of our contracts (eg contracts will throw 240 | with unknown data). 241 | 242 | [ Incorrect / Missing Modifiers ] 243 | 244 | The following modifiers are critically used in our code: payable, public, internal (pure is also used but 245 | not security critical so out of scope of this brief analysis; all instances where pure is used have 246 | been checked to not have side effects, and access only constants from global storage that do trigger 247 | some false positives on static analysis tools). 248 | 249 | Public functions are: ERC20 functions and helpers, mint and free. 250 | 251 | All other functions are internal. 252 | 253 | The above has been manually validated on all files. 254 | 255 | [ Function-Level Correctness / Testing Analysis ] 256 | 257 | We exclude full correctness analysis and testing analysis from the scope of this report due to limited time; 258 | any external investors should do their due diligence w.r.t. the provided tests and their completion. 259 | 260 | Several of the non-ERC20 tests do not check all boundary values or have full branch/decision coverage, 261 | and we recommend remedying this before trusting the contract with funds. 262 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectchicago/gastoken/da37d16390f3b91ebbb7d8e7744f4bdd16b3d16a/test/__init__.py -------------------------------------------------------------------------------- /test/compare_schemes.py: -------------------------------------------------------------------------------- 1 | 2 | from matplotlib import pyplot as plt 3 | import numpy as np 4 | from matplotlib import rc 5 | rc('font', **{'family': 'sans-serif', 'sans-serif': ['Helvetica']}) 6 | rc('text', usetex=True) 7 | 8 | from .test_GST1 import TestGST1 9 | 10 | STORAGE_TOKEN_COST = TestGST1.MINT_TOKEN_COST 11 | STORAGE_FREE_COST = TestGST1.FREE_TOKEN_COST 12 | STORAGE_REFUND = TestGST1.REFUND 13 | 14 | from .test_GST2 import TestGST2 15 | CONTRACT_TOKEN_COST = TestGST2.MINT_TOKEN_COST 16 | CONTRACT_FREE_COST = TestGST2.FREE_TOKEN_COST 17 | CONTRACT_REFUND = TestGST2.REFUND 18 | 19 | gs = np.arange(1, 50, 1) 20 | 21 | eff_storage = lambda g: STORAGE_REFUND * g / (STORAGE_TOKEN_COST + STORAGE_FREE_COST * g) 22 | eff_contract = lambda g: CONTRACT_REFUND * g / (CONTRACT_TOKEN_COST + CONTRACT_FREE_COST * g) 23 | 24 | plt.plot(gs, list(map(eff_storage, gs)), 25 | gs, list(map(eff_contract, gs))) 26 | 27 | EXPECTED_BREAK_STORAGE = 2.01 28 | EXPECTED_BREAK_CONTRACT = 2.13 29 | EXPECTED_INTERSECTION = 3.71 30 | 31 | break_storage = STORAGE_TOKEN_COST / (STORAGE_REFUND - STORAGE_FREE_COST) 32 | break_contract = CONTRACT_TOKEN_COST / (CONTRACT_REFUND - CONTRACT_FREE_COST) 33 | 34 | intersection = (STORAGE_TOKEN_COST * CONTRACT_REFUND - STORAGE_REFUND * CONTRACT_TOKEN_COST) \ 35 | / (STORAGE_REFUND * CONTRACT_FREE_COST - CONTRACT_REFUND * STORAGE_FREE_COST) 36 | 37 | assert np.round(break_storage, 2) == EXPECTED_BREAK_STORAGE, \ 38 | "Expected {} got {}".format(EXPECTED_BREAK_STORAGE, np.round(break_storage, 2)) 39 | assert np.round(break_contract, 2) == EXPECTED_BREAK_CONTRACT, \ 40 | "Expected {} got {}".format(EXPECTED_BREAK_CONTRACT, np.round(break_contract, 2)) 41 | assert np.round(intersection, 2) == EXPECTED_INTERSECTION, \ 42 | "Expected {} got {}".format(EXPECTED_INTERSECTION, np.round(intersection, 2)) 43 | 44 | print("break even for storage: {:.2f}".format(break_storage)) 45 | print("break even for contract: {:.2f}".format(break_contract)) 46 | print('intersection at {:.2f}'.format(intersection)) 47 | 48 | plt.plot((EXPECTED_BREAK_STORAGE, EXPECTED_BREAK_STORAGE), (0, eff_storage(EXPECTED_BREAK_STORAGE)), 'k--') 49 | plt.plot((0, EXPECTED_BREAK_STORAGE), (eff_storage(EXPECTED_BREAK_STORAGE), eff_storage(EXPECTED_BREAK_STORAGE)), 'k--') 50 | 51 | plt.plot((EXPECTED_INTERSECTION, EXPECTED_INTERSECTION), (0, eff_storage(EXPECTED_INTERSECTION)), 'k--') 52 | plt.plot((0, EXPECTED_INTERSECTION), (eff_storage(EXPECTED_INTERSECTION), eff_storage(EXPECTED_INTERSECTION)), 'k--') 53 | 54 | 55 | plt.xlim([0, gs[-1]]) 56 | plt.ylim([0, 1.1 * eff_contract(gs[-1])]) 57 | 58 | plt.legend(['GST1 (storage based)', 'GST2 (contract based)']) 59 | plt.xlabel(r'$gas_{high} / gas_{low}$') 60 | plt.ylabel('Maximal Savings/Efficiency') 61 | plt.savefig('comp.png') 62 | -------------------------------------------------------------------------------- /test/generic_ERC20_token.py: -------------------------------------------------------------------------------- 1 | # Requires Python 3.6 and pyethereum dependencies 2 | 3 | import unittest 4 | from ethereum.tools import tester 5 | from ethereum import utils 6 | 7 | 8 | def bytes_to_int(bytez): 9 | o = 0 10 | for b in bytez: 11 | o = o * 256 + b 12 | return o 13 | 14 | 15 | class TestGenericERC20Token(unittest.TestCase): 16 | 17 | t = None 18 | s = None 19 | c = None 20 | 21 | transfer_topic = bytes_to_int(utils.sha3("Transfer(address,address,uint256)")) 22 | approval_topic = bytes_to_int(utils.sha3("Approval(address,address,uint256)")) 23 | 24 | @classmethod 25 | def setUpClass(cls): 26 | super(TestGenericERC20Token, cls).setUpClass() 27 | 28 | # Initialize tester, contract and expose relevant objects 29 | cls.t = tester 30 | cls.s = cls.t.Chain() 31 | 32 | cls.s.head_state.gas_limit = 10**80 33 | cls.s.head_state.set_balance(cls.t.a0, 10**80) 34 | cls.s.head_state.set_balance(cls.t.a1, 10**80) 35 | cls.s.head_state.set_balance(cls.t.a2, 10**80) 36 | cls.s.head_state.set_balance(cls.t.a3, 10**80) 37 | 38 | cls.initial_state = cls.s.snapshot() 39 | 40 | def setUp(self): 41 | self.s.revert(self.initial_state) 42 | 43 | def get_refund_from_tx(self, func): 44 | from ethereum.slogging import get_logger 45 | log_tx = get_logger('eth.pb.tx') 46 | log_msg_prefix = 'DEBUG:eth.pb.tx:Refunding gas_refunded=' 47 | 48 | with self.assertLogs(log_tx, "DEBUG") as cm: 49 | func() 50 | 51 | refund_log = [log_msg for log_msg in cm.output 52 | if log_msg.startswith(log_msg_prefix)] 53 | 54 | if len(refund_log) == 0: 55 | refund = 0 56 | else: 57 | refund = int(refund_log[0].split(log_msg_prefix)[1]) 58 | return refund 59 | 60 | def assert_tx_failed(self, function_to_test, 61 | exception=tester.TransactionFailed): 62 | """ 63 | Ensure that transaction fails, reverting state 64 | (to prevent gas exhaustion) 65 | """ 66 | initial_state = self.s.snapshot() 67 | self.assertRaises(exception, function_to_test) 68 | self.s.revert(initial_state) 69 | 70 | def check_logs(self, topics, data): 71 | found = False 72 | for log_entry in self.s.head_state.receipts[-1].logs: 73 | if topics == log_entry.topics and data == log_entry.data: 74 | found = True 75 | if not found: 76 | self.fail(msg="Expected log not found in last log entry.") 77 | 78 | self.assertTrue(found) 79 | 80 | def test_abi(self): 81 | self.assertEqual( 82 | set(self.c.translator.function_data.keys()), 83 | {'name', 'approve', 'totalSupply', 'transferFrom', 'decimals', 84 | 'balanceOf', 'symbol', 'mint', 'transfer', 'free', 'allowance'}) 85 | 86 | def test_initial_state(self): 87 | # Check total supply is 0 88 | self.assertEqual(self.c.totalSupply(), 0) 89 | 90 | # Check several account balances as 0 91 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 92 | self.assertEqual(self.c.balanceOf(self.t.a2), 0) 93 | self.assertEqual(self.c.balanceOf(self.t.a3), 0) 94 | 95 | # Check several allowances as 0 96 | self.assertEqual(self.c.allowance(self.t.a1, self.t.a1), 0) 97 | self.assertEqual(self.c.allowance(self.t.a1, self.t.a2), 0) 98 | self.assertEqual(self.c.allowance(self.t.a1, self.t.a3), 0) 99 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a3), 0) 100 | 101 | def test_mint_and_free(self): 102 | 103 | # 104 | # Test scenario where a1 mints 2, frees twice 105 | # (check balance consistency) 106 | # 107 | 108 | # a1 mints 2 109 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 110 | self.assertIsNone(self.c.mint(2, sender=self.t.k1)) 111 | self.assertEqual(self.c.balanceOf(self.t.a1), 2) 112 | self.assertEqual(self.c.totalSupply(), 2) 113 | 114 | # free 1 115 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 116 | self.assertEqual(self.c.balanceOf(self.t.a1), 1) 117 | self.assertEqual(self.c.totalSupply(), 1) 118 | 119 | # free 1 120 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 121 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 122 | self.assertEqual(self.c.totalSupply(), 0) 123 | 124 | # test free on empty balance 125 | self.assertFalse(self.c.free(1, sender=self.t.k1)) 126 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 127 | self.assertEqual(self.c.totalSupply(), 0) 128 | 129 | # 130 | # Test scenario where a2 mints 0, frees 131 | # (check balance consistency, false free) 132 | # 133 | self.assertIsNone(self.c.mint(0, sender=self.t.k2)) 134 | self.assertEqual(self.c.balanceOf(self.t.a2), 0) 135 | self.assertEqual(self.c.totalSupply(), 0) 136 | self.assertFalse(self.c.free(1, sender=self.t.k2)) 137 | self.assertEqual(self.c.balanceOf(self.t.a2), 0) 138 | self.assertEqual(self.c.totalSupply(), 0) 139 | 140 | # free 0 should be a NOP 141 | self.assertTrue(self.c.free(0, sender=self.t.k2)) 142 | self.assertEqual(self.c.balanceOf(self.t.a2), 0) 143 | self.assertEqual(self.c.totalSupply(), 0) 144 | 145 | def test_totalSupply(self): 146 | # 147 | # Test total supply initially, after mint, between two free, 148 | # and after failed free 149 | # 150 | 151 | self.assertEqual(self.c.totalSupply(), 0) 152 | self.assertIsNone(self.c.mint(2, sender=self.t.k1)) 153 | self.assertEqual(self.c.totalSupply(), 2) 154 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 155 | self.assertEqual(self.c.totalSupply(), 1) 156 | 157 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 158 | self.assertEqual(self.c.totalSupply(), 0) 159 | self.assertFalse(self.c.free(1, sender=self.t.k1)) 160 | self.assertEqual(self.c.totalSupply(), 0) 161 | 162 | # Test that 0-valued mint can't affect supply 163 | self.assertIsNone(self.c.mint(value=0, sender=self.t.k1)) 164 | self.assertEqual(self.c.totalSupply(), 0) 165 | 166 | def test_transfer(self): 167 | # Test interaction between mint/free and transfer 168 | self.assertFalse(self.c.free(1, sender=self.t.k2)) 169 | self.assertIsNone(self.c.mint(2, sender=self.t.k1)) 170 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 171 | self.assertTrue(self.c.transfer(self.t.a2, 1, sender=self.t.k1)) 172 | self.assertFalse(self.c.free(1, sender=self.t.k1)) 173 | self.assertTrue(self.c.free(1, sender=self.t.k2)) 174 | self.assertFalse(self.c.free(1, sender=self.t.k2)) 175 | # Ensure transfer fails with insufficient balance 176 | self.assertFalse(self.c.transfer(self.t.a1, 1, sender=self.t.k2)) 177 | # Ensure 0-transfer always succeeds 178 | self.assertTrue(self.c.transfer(self.t.a1, 0, sender=self.t.k2)) 179 | 180 | def test_transferFromAndAllowance(self): 181 | # Test interaction between mint/free and transferFrom 182 | self.assertFalse(self.c.free(1, sender=self.t.k2)) 183 | self.assertIsNone(self.c.mint(1, sender=self.t.k1)) 184 | self.assertIsNone(self.c.mint(1, sender=self.t.k2)) 185 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 186 | # This should fail; no allowance or balance (0 always succeeds) 187 | self.assertFalse(self.c.transferFrom(self.t.a1, self.t.a3, 1, sender=self.t.k2)) 188 | self.assertTrue(self.c.transferFrom(self.t.a1, self.t.a3, 0, sender=self.t.k2)) 189 | # Correct call to approval should update allowance (but not for reverse pair) 190 | self.assertTrue(self.c.approve(self.t.a2, 1, sender=self.t.k1)) 191 | self.assertEqual(self.c.allowance(self.t.a1, self.t.a2, sender=self.t.k3), 1) 192 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 0) 193 | # transferFrom should succeed when allowed, fail with wrong sender 194 | self.assertFalse(self.c.transferFrom(self.t.a2, self.t.a3, 1, sender=self.t.k3)) 195 | self.assertEqual(self.c.balanceOf(self.t.a2), 1) 196 | self.assertTrue(self.c.approve(self.t.a1, 1, sender=self.t.k2)) 197 | self.assertTrue(self.c.transferFrom(self.t.a2, self.t.a3, 1, sender=self.t.k1)) 198 | # Allowance should be correctly updated after transferFrom 199 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 0) 200 | # transferFrom with no funds should fail despite approval 201 | self.assertTrue(self.c.approve(self.t.a1, 1, sender=self.t.k2)) 202 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 1) 203 | self.assertFalse(self.c.transferFrom(self.t.a2, self.t.a3, 1, sender=self.t.k1)) 204 | # 0-approve should not change balance or allow transferFrom to change balance 205 | self.assertIsNone(self.c.mint(1, sender=self.t.k2)) 206 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 1) 207 | self.assertTrue(self.c.approve(self.t.a1, 0, sender=self.t.k2)) 208 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 0) 209 | self.assertTrue(self.c.approve(self.t.a1, 0, sender=self.t.k2)) 210 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 0) 211 | self.assertFalse(self.c.transferFrom(self.t.a2, self.t.a3, 1, sender=self.t.k1)) 212 | # Test that if non-zero approval exists, 0-approval is required to proceed 213 | # as described in countermeasures at 214 | # https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit#heading=h.m9fhqynw2xvt 215 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 0) 216 | self.assertTrue(self.c.approve(self.t.a1, 1, sender=self.t.k2)) 217 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 1) 218 | self.assertFalse(self.c.approve(self.t.a1, 2, sender=self.t.k2)) 219 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 1) 220 | # Check that approving 0 then amount works 221 | self.assertTrue(self.c.approve(self.t.a1, 0, sender=self.t.k2)) 222 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 0) 223 | self.assertTrue(self.c.approve(self.t.a1, 5, sender=self.t.k2)) 224 | self.assertEqual(self.c.allowance(self.t.a2, self.t.a1, sender=self.t.k2), 5) 225 | 226 | def test_payability(self): 227 | # Make sure functions are not payable 228 | 229 | # Non payable functions - ensure all fail with value, succeed without 230 | self.assert_tx_failed(lambda: self.c.mint(2, value=2, sender=self.t.k1)) 231 | self.assertIsNone(self.c.mint(2, value=0, sender=self.t.k1)) 232 | self.assert_tx_failed(lambda: self.c.free(0, value=2, sender=self.t.k1)) 233 | self.assertTrue(self.c.free(0, value=0, sender=self.t.k1)) 234 | self.assert_tx_failed(lambda: self.c.totalSupply(value=2, sender=self.t.k1)) 235 | self.assertEqual(self.c.totalSupply(value=0, sender=self.t.k1), 2) 236 | self.assert_tx_failed(lambda: self.c.balanceOf(self.t.a1, value=2, sender=self.t.k1)) 237 | self.assertEqual(self.c.balanceOf(self.t.a1, value=0, sender=self.t.k1), 2) 238 | self.assert_tx_failed(lambda: self.c.transfer(self.t.a2, 0, value=2, sender=self.t.k1)) 239 | self.assertTrue(self.c.transfer(self.t.a2, 0, value=0, sender=self.t.k1)) 240 | self.assert_tx_failed(lambda: self.c.approve(self.t.a2, 1, value=2, sender=self.t.k1)) 241 | self.assertTrue(self.c.approve(self.t.a2, 1, value=0, sender=self.t.k1)) 242 | self.assert_tx_failed(lambda: self.c.allowance(self.t.a1, self.t.a2, value=2, sender=self.t.k1)) 243 | self.assertEqual(self.c.allowance(self.t.a1, self.t.a2, value=0, sender=self.t.k1), 1) 244 | self.assert_tx_failed(lambda: self.c.transferFrom(self.t.a1, self.t.a2, 0, value=2, sender=self.t.k1)) 245 | self.assertTrue(self.c.transferFrom(self.t.a1, self.t.a2, 0, value=0, sender=self.t.k1)) 246 | 247 | def test_raw_logs(self): 248 | self.s.head_state.receipts[-1].logs = [] 249 | 250 | # Check that mint emits no event 251 | self.assertIsNone(self.c.mint(2, sender=self.t.k1)) 252 | self.assertEqual(self.s.head_state.receipts[-1].logs, []) 253 | 254 | # Check that free emits not event 255 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 256 | self.assertEqual(self.s.head_state.receipts[-1].logs, []) 257 | 258 | # Check that transfer appropriately emits Transfer event 259 | self.assertTrue(self.c.transfer(self.t.a2, 1, sender=self.t.k1)) 260 | self.check_logs([self.transfer_topic, bytes_to_int(self.t.a1), 261 | bytes_to_int(self.t.a2)], (1).to_bytes(32, byteorder='big')) 262 | self.assertTrue(self.c.transfer(self.t.a2, 0, sender=self.t.k1)) 263 | self.check_logs([self.transfer_topic, bytes_to_int(self.t.a1), 264 | bytes_to_int(self.t.a2)], (0).to_bytes(32, byteorder='big')) 265 | 266 | # Check that approving amount emits events 267 | self.assertTrue(self.c.approve(self.t.a1, 1, sender=self.t.k2)) 268 | self.check_logs([self.approval_topic, bytes_to_int(self.t.a2), 269 | bytes_to_int(self.t.a1)], (1).to_bytes(32, byteorder='big')) 270 | self.assertTrue(self.c.approve(self.t.a2, 0, sender=self.t.k3)) 271 | self.check_logs([self.approval_topic, bytes_to_int(self.t.a3), 272 | bytes_to_int(self.t.a2)], (0).to_bytes(32, byteorder='big')) 273 | 274 | # Check that transferFrom appropriately emits Transfer event 275 | self.assertTrue(self.c.transferFrom(self.t.a2, self.t.a3, 1, sender=self.t.k1)) 276 | self.check_logs([self.transfer_topic, bytes_to_int(self.t.a2), 277 | bytes_to_int(self.t.a3)], (1).to_bytes(32, byteorder='big')) 278 | self.assertTrue(self.c.transferFrom(self.t.a2, self.t.a3, 0, sender=self.t.k1)) 279 | self.check_logs([self.transfer_topic, bytes_to_int(self.t.a2), 280 | bytes_to_int(self.t.a3)], (0).to_bytes(32, byteorder='big')) 281 | 282 | # Check that no other ERC-compliant calls emit any events 283 | self.s.head_state.receipts[-1].logs = [] 284 | self.assertEqual(self.c.totalSupply(), 1) 285 | self.assertEqual(self.s.head_state.receipts[-1].logs, []) 286 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 287 | self.assertEqual(self.s.head_state.receipts[-1].logs, []) 288 | self.assertEqual(self.c.allowance(self.t.a1, self.t.a2), 0) 289 | self.assertEqual(self.s.head_state.receipts[-1].logs, []) 290 | 291 | # Check that failed approve, transfer calls emit no events 292 | self.assertFalse(self.c.transfer(self.t.a2, 1, sender=self.t.k1)) 293 | self.assertEqual(self.s.head_state.receipts[-1].logs, []) 294 | self.assertFalse(self.c.transferFrom(self.t.a2, self.t.a3, 1, sender=self.t.k1)) 295 | self.assertEqual(self.s.head_state.receipts[-1].logs, []) 296 | 297 | self.assertTrue(self.c.approve(self.t.a2, 2, sender=self.t.k3)) 298 | self.check_logs([self.approval_topic, bytes_to_int(self.t.a3), 299 | bytes_to_int(self.t.a2)], (2).to_bytes(32, byteorder='big')) 300 | self.s.head_state.receipts[-1].logs = [] 301 | self.assertFalse(self.c.approve(self.t.a2, 3, sender=self.t.k3)) 302 | self.assertEqual(self.s.head_state.receipts[-1].logs, []) 303 | 304 | def test_boundaries(self): 305 | MAX_UINT256 = (2 ** 256) - 1 # Max num256 value 306 | STORAGE_LOCATION_ARRAY = 0xFFFFFFFFFF # beginning of storage in GST 307 | 308 | self.assert_tx_failed(lambda: self.c.mint(MAX_UINT256, sender=self.t.k1, startgas=10**8)) 309 | self.assert_tx_failed(lambda: self.c.mint(MAX_UINT256 - STORAGE_LOCATION_ARRAY, sender=self.t.k1, startgas=10**8)) 310 | self.assert_tx_failed(lambda: self.c.mint(MAX_UINT256 - STORAGE_LOCATION_ARRAY - 1, sender=self.t.k1, startgas=10**8)) 311 | self.assertFalse(self.c.free(MAX_UINT256, sender=self.t.k1)) 312 | self.assertFalse(self.c.free(MAX_UINT256 - STORAGE_LOCATION_ARRAY, sender=self.t.k1)) 313 | self.assertFalse(self.c.free(MAX_UINT256 - STORAGE_LOCATION_ARRAY - 1, sender=self.t.k1)) 314 | 315 | if self.c.freeUpTo is not None: 316 | self.assertFalse(self.c.freeUpTo(MAX_UINT256, sender=self.t.k1)) 317 | self.assertFalse(self.c.freeUpTo(MAX_UINT256 - STORAGE_LOCATION_ARRAY, sender=self.t.k1)) 318 | self.assertFalse(self.c.freeUpTo(MAX_UINT256 - STORAGE_LOCATION_ARRAY - 1, sender=self.t.k1)) 319 | 320 | if self.c.freeFrom is not None: 321 | self.assertFalse(self.c.freeFrom(self.t.a0, MAX_UINT256, sender=self.t.k1)) 322 | self.assertFalse(self.c.freeFrom(self.t.a0, MAX_UINT256 - STORAGE_LOCATION_ARRAY, sender=self.t.k1)) 323 | self.assertFalse(self.c.freeFrom(self.t.a0, MAX_UINT256 - STORAGE_LOCATION_ARRAY - 1, sender=self.t.k1)) 324 | 325 | if self.c.freeFromUpTo is not None: 326 | self.assertFalse(self.c.freeFromUpTo(self.t.a0, MAX_UINT256, sender=self.t.k1)) 327 | self.assertFalse(self.c.freeFromUpTo(self.t.a0, MAX_UINT256 - STORAGE_LOCATION_ARRAY, sender=self.t.k1)) 328 | self.assertFalse(self.c.freeFromUpTo(self.t.a0, MAX_UINT256 - STORAGE_LOCATION_ARRAY - 1, sender=self.t.k1)) 329 | 330 | def test_internal_transfer_private(self): 331 | with self.assertRaises(AttributeError): 332 | self.c.internalTransfer(self.t.a0, self.t.a1, 0) 333 | fn_selector = utils.sha3("internalTransfer(address,address,uint256)")[:4] 334 | assert isinstance(fn_selector, bytes) 335 | data = fn_selector + self.t.a0 + self.t.a1 + 32 * b'\x00' 336 | with self.assertRaises(tester.TransactionFailed): 337 | self.s.tx( 338 | sender=self.t.k0, 339 | to=self.c.address, 340 | value=0, 341 | data=data, 342 | startgas=10**20) 343 | -------------------------------------------------------------------------------- /test/generic_gas_token.py: -------------------------------------------------------------------------------- 1 | 2 | from .generic_ERC20_token import TestGenericERC20Token 3 | import ethereum.opcodes as op 4 | import collections 5 | import warnings 6 | 7 | # cost of SLOAD op (200), CALL op (700) and CREATE op (24000) 8 | GSLOAD = op.opcodes[op.reverse_opcodes['SLOAD']][-1] + op.SLOAD_SUPPLEMENTAL_GAS 9 | GCALL = op.opcodes[op.reverse_opcodes['CALL']][-1] + op.CALL_SUPPLEMENTAL_GAS 10 | GCREATE = op.opcodes[op.reverse_opcodes['CREATE']][-1] 11 | 12 | 13 | def input_data_cost(x, num_bytes=32): 14 | bytez = tuple((x).to_bytes(num_bytes, byteorder='big')) 15 | zero_bytes = sum([1 for b in bytez if b == 0]) 16 | 17 | # 4 for zero bytes, 68 for non-zero 18 | return op.GTXDATAZERO * zero_bytes + \ 19 | op.GTXDATANONZERO * (len(bytez) - zero_bytes) 20 | 21 | 22 | class TestGenericGasToken(TestGenericERC20Token): 23 | 24 | MINT_COST_LOWER_BOUND = None 25 | REFUND = None 26 | 27 | def mint_cost(self, x): 28 | pass 29 | 30 | def free_cost(self, x): 31 | pass 32 | 33 | def free_up_to_cost(self, x): 34 | pass 35 | 36 | def free_from_cost(self, x): 37 | pass 38 | 39 | def free_from_up_to_cost(self, x): 40 | pass 41 | 42 | @classmethod 43 | def setUpClass(cls): 44 | super(TestGenericGasToken, cls).setUpClass() 45 | 46 | with open('contract/test_helper.sol') as fd: 47 | helper_contract_code = fd.read() 48 | cls.helper = cls.s.contract(helper_contract_code, language='solidity') 49 | 50 | def setUp(self): 51 | super().setUp() 52 | 53 | def test_abi(self): 54 | self.assertEqual( 55 | set(self.c.translator.function_data.keys()), 56 | {'name', 'approve', 'totalSupply', 'transferFrom', 'decimals', 57 | 'balanceOf', 'symbol', 'mint', 'transfer', 'free', 'allowance', 58 | 'freeUpTo', 'freeFrom', 'freeFromUpTo'}) 59 | 60 | def test_freeFrom(self): 61 | 62 | def freeFrom(address_from, amount, address_spender, key_spender): 63 | original_balance = self.c.balanceOf(address_from) 64 | original_allowance = self.c.allowance(address_from, address_spender) 65 | self.assertTrue(self.c.freeFrom(address_from, amount, sender=key_spender)) 66 | self.assertEqual(original_balance - amount, self.c.balanceOf(address_from)) 67 | self.assertEqual(original_allowance - amount, self.c.allowance(address_from, address_spender)) 68 | 69 | freeFrom(self.t.a0, 0, self.t.a1, self.t.k1) 70 | self.c.mint(20, sender=self.t.k0) 71 | self.c.approve(self.t.a1, 10) 72 | freeFrom(self.t.a0, 0, self.t.a1, self.t.k1) 73 | freeFrom(self.t.a0, 1, self.t.a1, self.t.k1) 74 | freeFrom(self.t.a0, 4, self.t.a1, self.t.k1) 75 | freeFrom(self.t.a0, 5, self.t.a1, self.t.k1) 76 | self.assertFalse(self.c.freeFrom(self.t.a0, 1)) 77 | self.c.approve(self.t.a1, 10) 78 | freeFrom(self.t.a0, 10, self.t.a1, self.t.k1) 79 | freeFrom(self.t.a0, 0, self.t.a1, self.t.k1) 80 | self.assertFalse(self.c.freeFrom(self.t.a0, 1)) 81 | self.c.approve(self.t.a1, 10) 82 | self.assertFalse(self.c.freeFrom(self.t.a0, 1)) 83 | self.c.mint(10, sender=self.t.k0) 84 | freeFrom(self.t.a0, 10, self.t.a1, self.t.k1) 85 | self.assertEqual(0, self.c.balanceOf(self.t.a0)) 86 | self.assertEqual(0, self.c.allowance(self.t.a0, self.t.a1)) 87 | 88 | def test_freeUpTo(self): 89 | 90 | self.assertEqual(0, self.c.balanceOf(self.t.a0)) 91 | self.c.mint(20, sender=self.t.k0) 92 | self.assertEqual(20, self.c.balanceOf(self.t.a0)) 93 | self.assertEqual(0, self.c.freeUpTo(0)) 94 | self.assertEqual(20, self.c.balanceOf(self.t.a0)) 95 | self.assertEqual(1, self.c.freeUpTo(1)) 96 | self.assertEqual(19, self.c.balanceOf(self.t.a0)) 97 | self.assertEqual(19, self.c.freeUpTo(20)) 98 | self.assertEqual(0, self.c.balanceOf(self.t.a0)) 99 | self.assertEqual(0, self.c.freeUpTo(100)) 100 | 101 | def test_freeFromUpTo(self): 102 | 103 | def freeFromUpTo(address_from, amount, address_spender, key_spender): 104 | original_balance = self.c.balanceOf(address_from) 105 | original_allowance = self.c.allowance(address_from, address_spender) 106 | expected_freed = min([original_allowance, original_balance, amount]) 107 | self.assertEqual(expected_freed, self.c.freeFromUpTo(address_from, amount, sender=key_spender)) 108 | self.assertEqual(original_balance - expected_freed, self.c.balanceOf(address_from)) 109 | self.assertEqual(original_allowance - expected_freed, self.c.allowance(address_from, address_spender)) 110 | 111 | freeFromUpTo(self.t.a0, 0, self.t.a1, self.t.k1) 112 | freeFromUpTo(self.t.a0, 10, self.t.a1, self.t.k1) 113 | self.c.mint(20, sender=self.t.k0) 114 | self.c.approve(self.t.a1, 9, sender=self.t.k0) 115 | freeFromUpTo(self.t.a0, 0, self.t.a1, self.t.k1) 116 | freeFromUpTo(self.t.a0, 1, self.t.a1, self.t.k1) 117 | freeFromUpTo(self.t.a0, 9, self.t.a1, self.t.k1) 118 | self.c.approve(self.t.a1, 10, sender=self.t.k0) 119 | freeFromUpTo(self.t.a0, 10, self.t.a1, self.t.k1) 120 | freeFromUpTo(self.t.a0, 10, self.t.a1, self.t.k1) 121 | self.c.mint(20, sender=self.t.k0) 122 | self.c.approve(self.t.a1, 1000, sender=self.t.k0) 123 | freeFromUpTo(self.t.a0, 20, self.t.a1, self.t.k1) 124 | 125 | def test_mint_scaling(self): 126 | # make a call that initializes totalSupply and balance[a1] 127 | gas_used_before = self.s.head_state.gas_used 128 | self.assertIsNone(self.c.mint(1, sender=self.t.k1, startgas=10 ** 20)) 129 | gas_used = self.s.head_state.gas_used - gas_used_before 130 | 131 | cost_lower_bound = self.MINT_COST_LOWER_BOUND 132 | cost_upper_bound = cost_lower_bound + 2000 133 | 134 | self.assertTrue(cost_lower_bound <= gas_used <= cost_upper_bound) 135 | 136 | mints = [0, # 0 non-zero byte 137 | 1, 2, 3, 4, 5, 10, 20, 50, 100, 255, 256, 256**2, # 1 non-zero byte 138 | 257, 500, 1000, 256**2 + 1, # 2 non-zero bytes 139 | 256**2 + 256 + 1] # 3 non-zero bytes 140 | 141 | expected_balance = 1 142 | scaled_as_expected = True 143 | 144 | print() 145 | for i in mints: 146 | print("\tminting {} tokens".format(i)) 147 | expected_balance += i 148 | 149 | gas_used_before = self.s.head_state.gas_used 150 | self.assertIsNone( 151 | self.c.mint(i, sender=self.t.k1, startgas=10 ** 20)) 152 | gas_used = self.s.head_state.gas_used - gas_used_before 153 | 154 | expected = self.mint_cost(i) 155 | as_expected = -0.01 <= ((gas_used - expected) / expected) <= 0.01 156 | 157 | if not as_expected: 158 | scaled_as_expected = False 159 | warnings.warn("Mint({}) did not scale as expected. Got {}. " 160 | "Expected {}".format(i, gas_used, expected)) 161 | 162 | self.assertEqual(self.c.totalSupply(), expected_balance) 163 | self.assertEqual(self.c.balanceOf(self.t.a1), expected_balance) 164 | 165 | self.assertTrue(scaled_as_expected) 166 | 167 | def test_free_cost_and_refund(self): 168 | """Check that the various free* methods actually refund the correct 169 | amount of gas and that the cost of freeing x tokens is roughly linear 170 | in x. 171 | 172 | Since we can only get a refund up to half of the total tx cost, we use 173 | a helper contract that burns a large amount of gas before calling free. 174 | This will cause the transaction to always consume at least 2x the refunded 175 | amount and will thus be able to detect the full refund. 176 | """ 177 | 178 | free_amounts =\ 179 | [0, # 0 non-zero byte 180 | 1, 2, 3, 4, 5, 10, 20, 50, 100, 255, 256, 256**2, # 1 non-zero byte 181 | 257, 500, 1000] 182 | 183 | # we can only get a refund up to half the transaction price 184 | ideal_burn = max(free_amounts) * self.REFUND * 2 + 1000 185 | 186 | TestCase = collections.namedtuple( 187 | 'TestCase', 'name fn needs_transfer cost_fn') 188 | 189 | if self.c.freeUpTo is None: 190 | test_cases = [ 191 | TestCase('free', self.helper.burnGasAndFree, True, 192 | self.free_cost) 193 | ] 194 | else: 195 | test_cases = [ 196 | TestCase('free', self.helper.burnGasAndFree, True, 197 | self.free_cost), 198 | TestCase('freeUpTo', self.helper.burnGasAndFreeUpTo, True, 199 | self.free_up_to_cost), 200 | TestCase('freeFrom', self.helper.burnGasAndFreeFrom, False, 201 | self.free_from_cost), 202 | TestCase('freeFromUpTo', self.helper.burnGasAndFreeFromUpTo, False, 203 | self.free_from_up_to_cost), 204 | ] 205 | 206 | # gas cost changes after 256 tokens due to having two non-zero bytes 207 | self.c.mint(256, 208 | sender=self.t.k1, 209 | startgas=10 ** 20) 210 | self.assertTrue(self.c.free(256, 211 | sender=self.t.k1)) 212 | 213 | # Determine how much gas is actually burned by burnGas(ideal_burn) 214 | gas_used_before_burn = self.s.head_state.gas_used 215 | self.assertIsNone(self.helper.burnGas(ideal_burn, 216 | startgas=10 ** 20)) 217 | actual_burn = self.s.head_state.gas_used - gas_used_before_burn 218 | self.assertLessEqual(ideal_burn, actual_burn) 219 | 220 | # Approve helper contract to spend (almost) infinite amounts of tokens 221 | self.assertTrue(self.c.approve(self.helper.address, 2 ** 128, 222 | sender=self.t.k1)) 223 | 224 | print() 225 | for test_case in test_cases: 226 | with self.subTest(name=test_case.name): 227 | 228 | # mint tokens required for test case and transfer them to 229 | # the helper contract if the test case requires it 230 | self.c.mint(sum(free_amounts) + 10, 231 | sender=self.t.k1, 232 | startgas=10**20) 233 | if test_case.needs_transfer: 234 | self.assertTrue(self.c.transfer(self.helper.address, 235 | sum(free_amounts) + 1, 236 | sender=self.t.k1)) 237 | 238 | scaled_as_expected = True 239 | # record cost and check expected refund for each free_amount 240 | for free_amount in free_amounts: 241 | print("\tfreeing {} tokens".format(free_amount)) 242 | gas_used_before = self.s.head_state.gas_used 243 | refund = self.get_refund_from_tx( 244 | lambda: test_case.fn(self.c.address, ideal_burn, 245 | free_amount, 246 | sender=self.t.k1, 247 | startgas=10 ** 20)) 248 | 249 | expected = test_case.cost_fn(free_amount) 250 | gas_used = self.s.head_state.gas_used - gas_used_before - actual_burn + refund 251 | 252 | print(gas_used, expected) 253 | 254 | if isinstance(expected, tuple): 255 | as_expected = (expected[0] <= gas_used <= expected[1]) 256 | else: 257 | as_expected = -0.01 <= ((gas_used - expected) / expected) <= 0.01 258 | 259 | if not as_expected: 260 | scaled_as_expected = False 261 | warnings.warn("{}({}) did not scale as expected. Got {}. " 262 | "Expected {}".format(test_case.name, free_amount, gas_used, expected)) 263 | 264 | self.assertEqual(free_amount * self.REFUND, refund) 265 | 266 | self.assertTrue(scaled_as_expected) 267 | -------------------------------------------------------------------------------- /test/test_GST1.py: -------------------------------------------------------------------------------- 1 | # Requires Python 3.6 and pyethereum dependencies 2 | 3 | import unittest 4 | import ethereum.opcodes as op 5 | from ethereum import utils 6 | 7 | from .generic_gas_token import TestGenericGasToken, GSLOAD, input_data_cost 8 | 9 | 10 | class TestGST1(TestGenericGasToken): 11 | 12 | MINT_COST_LOWER_BOUND = op.GTXCOST + 3*op.GSTORAGEADD + 2*GSLOAD 13 | 14 | # Base cost of mint transaction (includes base transaction fee) 15 | MINT_BASE = 32259 16 | 17 | # Additional minting cost per token 18 | MINT_TOKEN_COST = 20046 19 | 20 | # Base cost of free transaction (includes CALL from external contract) 21 | FREE_BASE = 14505 22 | FREE_UP_TO_BASE = 14419 23 | FREE_FROM_BASE = 20223 24 | FREE_FROM_UP_TO_BASE = 20089 25 | 26 | # Additional free cost per token 27 | FREE_TOKEN_COST = 5046 28 | 29 | def mint_cost(self, x): 30 | if x == 0: 31 | return 21800 32 | 33 | return self.MINT_BASE + x * self.MINT_TOKEN_COST + input_data_cost(x) 34 | 35 | def free_cost(self, x): 36 | return self.FREE_BASE + x * self.FREE_TOKEN_COST + input_data_cost(x) 37 | 38 | def free_up_to_cost(self, x): 39 | return self.FREE_UP_TO_BASE + x * self.FREE_TOKEN_COST + input_data_cost(x) 40 | 41 | def free_from_cost(self, x): 42 | return self.FREE_FROM_BASE + x * self.FREE_TOKEN_COST + input_data_cost(x) 43 | 44 | def free_from_up_to_cost(self, x): 45 | return self.FREE_FROM_UP_TO_BASE + x * self.FREE_TOKEN_COST + input_data_cost(x) 46 | 47 | # Refund per freed token 48 | REFUND = op.GSTORAGEREFUND 49 | 50 | @classmethod 51 | def setUpClass(cls): 52 | super(TestGST1, cls).setUpClass() 53 | 54 | with open('contract/GST1.sol') as fd: 55 | contract_code = fd.read() 56 | cls.c = cls.s.contract(contract_code, language='solidity') 57 | cls.initial_state = cls.s.snapshot() 58 | 59 | def setUp(self): 60 | super().setUp() 61 | 62 | def get_storage(self): 63 | return self.s.head_state.account_to_dict(self.c.address)['storage'] 64 | 65 | def test_ERC20_attributes(self): 66 | self.assertEqual(self.c.decimals(), 2) 67 | self.assertEqual(self.c.name(), b'Gastoken.io') 68 | self.assertEqual(self.c.symbol(), b'GST1') 69 | 70 | def test_storage(self): 71 | 72 | # check that storage is initially empty 73 | self.assertEqual(len(self.get_storage()), 0) 74 | 75 | # a1 mints 2 76 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 77 | self.assertIsNone(self.c.mint(2, sender=self.t.k1)) 78 | self.assertEqual(self.c.balanceOf(self.t.a1), 2) 79 | self.assertEqual(self.c.totalSupply(), 2) 80 | 81 | # check that 4 words are stored (totalSupply, balance[a1], two mints) 82 | storage = self.get_storage() 83 | self.assertEqual(len(storage), 4) 84 | 85 | # free 1 86 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 87 | self.assertEqual(self.c.balanceOf(self.t.a1), 1) 88 | self.assertEqual(self.c.totalSupply(), 1) 89 | 90 | storage = self.get_storage() 91 | self.assertEqual(len(storage), 3) 92 | 93 | # free 1 94 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 95 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 96 | self.assertEqual(self.c.totalSupply(), 0) 97 | storage = self.get_storage() 98 | self.assertEqual(len(storage), 0) 99 | 100 | 101 | class TestDeployedGST1(TestGST1): 102 | 103 | @classmethod 104 | def setUpClass(cls): 105 | super(TestDeployedGST1, cls).setUpClass() 106 | 107 | with open('contract/GST1.asm') as fd: 108 | contract_code = utils.decode_hex(fd.read()) 109 | cls.s.head_state.set_code(cls.c.address, contract_code) 110 | cls.initial_state = cls.s.snapshot() 111 | 112 | def setUp(self): 113 | super().setUp() 114 | 115 | 116 | def load_tests(loader, tests, pattern): 117 | full_suite = unittest.TestSuite() 118 | 119 | for suite in [TestDeployedGST1, TestGST1]: 120 | tests = loader.loadTestsFromTestCase(suite) 121 | full_suite.addTests(tests) 122 | return full_suite 123 | 124 | if __name__ == '__main__': 125 | unittest.main(verbosity=2) 126 | -------------------------------------------------------------------------------- /test/test_GST2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import ethereum.opcodes as op 3 | from ethereum.tools.tester import k0, STARTGAS, GASPRICE, ABIContract, \ 4 | TransactionFailed 5 | from ethereum.exceptions import InsufficientStartGas 6 | from ethereum.abi import ContractTranslator 7 | from ethereum.tools._solidity import solc_parse_output, \ 8 | solidity_get_contract_data 9 | from ethereum import utils 10 | import os 11 | import subprocess 12 | 13 | from .generic_gas_token import TestGenericGasToken, GSLOAD, GCREATE, input_data_cost 14 | from .test_rlp import rlp_cost 15 | 16 | 17 | def deploy_solidity_contract(contract_path, contract_name, 18 | chain, sender=k0, value=0, 19 | startgas=STARTGAS, gasprice=GASPRICE, *args): 20 | output = subprocess.check_output( 21 | ["solc", "--combined-json", "bin,abi", contract_path]) 22 | result = solc_parse_output(output) 23 | data = solidity_get_contract_data(result, contract_path, contract_name) 24 | 25 | interface = data['abi'] 26 | ct = ContractTranslator(interface) 27 | 28 | code = data['bin'] \ 29 | + (ct.encode_constructor_arguments(args) if args else b'') 30 | addr = chain.tx( 31 | sender=sender, 32 | to=b'', 33 | value=value, 34 | data=code, 35 | startgas=startgas, 36 | gasprice=gasprice) 37 | return ABIContract(chain, ct, addr) 38 | 39 | 40 | class TestGST2(TestGenericGasToken): 41 | 42 | # hex version of child contract binary (runtime component without initcode) 43 | CHILD_CONTRACT_BIN = "6eb3f879cb30fe243b4dfee438691c043318585733ff" 44 | 45 | # length in bytes of the contract deployed when minting a token 46 | CONTRACT_LEN = len(CHILD_CONTRACT_BIN) // 2 47 | 48 | # approx cost of MINT 49 | MINT_COST_LOWER_BOUND = op.GTXCOST + 2 * op.GSTORAGEADD + 2 * GSLOAD \ 50 | + GCREATE + op.GCONTRACTBYTE * CONTRACT_LEN 51 | 52 | # Base cost of mint transaction (includes base transaction fee) 53 | MINT_BASE = 32254 54 | 55 | # Additional minting cost per token 56 | MINT_TOKEN_COST = 36543 57 | 58 | # Base cost of free transaction (includes CALL from external contract) 59 | FREE_BASE = 14154 60 | FREE_UP_TO_BASE = 14053 61 | FREE_FROM_BASE = 19809 62 | FREE_FROM_UP_TO_BASE = 19664 63 | 64 | # min-max estimated costs for the `mk_contract_address` function that 65 | # produces an RLP encoding of the nonce and address 66 | RLP_LOWER_BOUND = rlp_cost(1) 67 | RLP_UPPER_BOUND = rlp_cost(256**9-1) 68 | 69 | # Upper bound on the additional free cost per token. 70 | # For small values of nonce ( 1 <= nonce <= 127 ), the cost is exactly 6228 71 | # gas. 72 | # As an upper bound, we add the difference in costs of the most expensive 73 | # nonce to RLP Encode (nonce = 256**9 - 1) and the least expensive 74 | # (nonce = 1). This difference seems to be 642 gas. 75 | FREE_TOKEN_COST = 6870 76 | 77 | def nth_child_addr(self, n): 78 | a = utils.encode_hex(utils.mk_contract_address(self.c.address, n)) 79 | return a 80 | 81 | def nth_child_has_code(self, n): 82 | return len(self.s.head_state.get_code(self.nth_child_addr(n))) > 0 83 | 84 | def mint_cost(self, x): 85 | return self.MINT_BASE + x * self.MINT_TOKEN_COST + input_data_cost(x) 86 | 87 | def _free_cost(self, base, token, x): 88 | if x == 0: 89 | return (base - 10 + input_data_cost(x), base + 10 + input_data_cost(x)) 90 | 91 | low = base + x * (token - self.RLP_UPPER_BOUND + self.RLP_LOWER_BOUND) + input_data_cost(x) 92 | high = base + x * token + input_data_cost(x) 93 | return (low, high) 94 | 95 | def free_cost(self, x): 96 | return self._free_cost(self.FREE_BASE, self.FREE_TOKEN_COST, x) 97 | 98 | def free_up_to_cost(self, x): 99 | return self._free_cost(self.FREE_UP_TO_BASE, self.FREE_TOKEN_COST, x) 100 | 101 | def free_from_cost(self, x): 102 | return self._free_cost(self.FREE_FROM_BASE, self.FREE_TOKEN_COST, x) 103 | 104 | def free_from_up_to_cost(self, x): 105 | return self._free_cost(self.FREE_FROM_UP_TO_BASE, self.FREE_TOKEN_COST, x) 106 | 107 | # Refund per freed token 108 | REFUND = op.GSUICIDEREFUND 109 | 110 | @classmethod 111 | def deploy(cls, contract_path): 112 | cwd = os.getcwd() 113 | os.chdir('contract') 114 | 115 | # Our ContractGasToken contract relies on being deployed at address 116 | # 0x0000000000b3F879cb30FE243b4Dfee438691c04 117 | # Through magic, we have determined that a contract created in a 118 | # transaction with nonce == magic_nonce sent from 119 | # address == magic_address will have this address. 120 | magic_key = 0xa7d79a51ff835c80c1f5c2c3b350b15f95550e41e379e50a10ef2ff3f6a215aa 121 | magic_address = 0x470F1C3217A2F408769bca5AB8a5c67A9040664A 122 | magic_nonce = 125 123 | contract_address = 0x0000000000b3F879cb30FE243b4Dfee438691c04 124 | 125 | computed_address = int(utils.encode_hex(utils.mk_contract_address(magic_address, magic_nonce)), 16) 126 | assert (computed_address == contract_address) 127 | 128 | cls.s.head_state.set_nonce(magic_address, magic_nonce) 129 | 130 | # check that we have reached magic_nonce and that there is no code at magic_address 131 | assert (cls.s.head_state.get_nonce(magic_address) == magic_nonce) 132 | assert (0 == len(cls.s.head_state.get_code(contract_address))) 133 | 134 | # deploy contract and check that it has been deployed successfully 135 | cls.c = deploy_solidity_contract(contract_path, 136 | 'GasToken2', 137 | cls.s, 138 | sender=magic_key) 139 | assert (0 < len(cls.s.head_state.get_code(contract_address))) 140 | os.chdir(cwd) 141 | 142 | @classmethod 143 | def setUpClass(cls): 144 | super(TestGST2, cls).setUpClass() 145 | 146 | cls.deploy('GST2_ETH.sol') 147 | cls.initial_state = cls.s.snapshot() 148 | 149 | def setUp(self): 150 | super().setUp() 151 | 152 | def get_storage(self): 153 | return self.s.head_state.account_to_dict(self.c.address)['storage'] 154 | 155 | def test_ERC20_attributes(self): 156 | self.assertEqual(self.c.decimals(), 2) 157 | self.assertEqual(self.c.name(), b'Gastoken.io') 158 | self.assertEqual(self.c.symbol(), b'GST2') 159 | 160 | def test_storage_and_contracts(self): 161 | 162 | num_deployed_contracts = len(self.s.head_state.to_dict().keys()) 163 | 164 | # check nonce 165 | self.assertEqual(self.s.head_state.get_nonce(self.c.address), 1) 166 | 167 | # check that storage is initially empty 168 | self.assertEqual(len(self.get_storage()), 0) 169 | 170 | # a1 mints 2 171 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 172 | self.assertIsNone(self.c.mint(2, sender=self.t.k1)) 173 | self.assertEqual(self.c.balanceOf(self.t.a1), 2) 174 | self.assertEqual(self.c.totalSupply(), 2) 175 | 176 | # check that 2 words are stored (balance[a1], head) 177 | storage = self.get_storage() 178 | self.assertEqual(len(storage), 2) 179 | 180 | # check that two contracts were created 181 | a1 = utils.encode_hex(utils.mk_contract_address(self.c.address, 1)) 182 | a2 = utils.encode_hex(utils.mk_contract_address(self.c.address, 2)) 183 | 184 | self.assertEqual(len(self.s.head_state.to_dict().keys()), 185 | num_deployed_contracts + 2) 186 | 187 | self.assertTrue(a1 in self.s.head_state.to_dict().keys()) 188 | self.assertTrue(a2 in self.s.head_state.to_dict().keys()) 189 | 190 | code1 = utils.encode_hex(self.s.head_state.get_code(a1)) 191 | self.assertEqual(code1, self.CHILD_CONTRACT_BIN) 192 | 193 | code2 = utils.encode_hex(self.s.head_state.get_code(a2)) 194 | self.assertEqual(code2, self.CHILD_CONTRACT_BIN) 195 | 196 | # free 1 197 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 198 | self.assertEqual(self.c.balanceOf(self.t.a1), 1) 199 | self.assertEqual(self.c.totalSupply(), 1) 200 | 201 | # check that 2 words are stored (balance[a1], head, tail) 202 | storage = self.get_storage() 203 | self.assertEqual(len(storage), 3) 204 | 205 | # check that a contract was killed 206 | self.assertEqual(len(self.s.head_state.to_dict().keys()), 207 | num_deployed_contracts + 1) 208 | self.assertFalse(a1 in self.s.head_state.to_dict().keys()) 209 | self.assertTrue(a2 in self.s.head_state.to_dict().keys()) 210 | 211 | # free 1 212 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 213 | self.assertEqual(self.c.balanceOf(self.t.a1), 0) 214 | self.assertEqual(self.c.totalSupply(), 0) 215 | 216 | # check that 2 words are stored (head, tail) 217 | storage = self.get_storage() 218 | self.assertEqual(len(storage), 2) 219 | 220 | # check that a contract was killed 221 | self.assertEqual(len(self.s.head_state.to_dict().keys()), 222 | num_deployed_contracts) 223 | self.assertFalse(a1 in self.s.head_state.to_dict().keys()) 224 | self.assertFalse(a2 in self.s.head_state.to_dict().keys()) 225 | 226 | def test_gst2_eth_example_free(self): 227 | # Create contract 228 | os.chdir('contract') 229 | example_contract = deploy_solidity_contract( 230 | 'gst2_free_example.sol', 231 | 'GST2FreeExample', 232 | self.s, 233 | sender=self.t.k0) 234 | os.chdir('..') 235 | 236 | # Intial mint and free to set storage to non-zero values 237 | self.assertFalse(self.nth_child_has_code(1)) 238 | self.assertIsNone(self.c.mint(1)) 239 | self.assertTrue(self.nth_child_has_code(1)) 240 | self.assertTrue(self.c.free(1)) 241 | self.assertFalse(self.nth_child_has_code(1)) 242 | 243 | # Supply example_contract with some tokens 244 | self.assertIsNone(self.c.mint(50)) 245 | self.assertTrue(self.c.transfer(example_contract.address, 50)) 246 | self.assertEqual(50, self.c.balanceOf(example_contract.address)) 247 | 248 | # Free with varying gas amounts to check that we never destroy tokens without 249 | # self-destructing a contract. 250 | tail_nonce = 2 251 | for gas in range(24000, 1000000, 1000): 252 | old_balance = self.c.balanceOf(example_contract.address) 253 | freed = example_contract.freeExample(180, startgas=gas) 254 | print('Freed', freed, 'with', gas, 'gas') 255 | self.assertEqual(old_balance - freed, self.c.balanceOf(example_contract.address)) 256 | tail_nonce += freed 257 | self.assertFalse(self.nth_child_has_code(tail_nonce - 1)) 258 | self.assertTrue(self.nth_child_has_code(tail_nonce)) 259 | 260 | # Replenish example_contract's suply 261 | while self.c.balanceOf(example_contract.address) < 200: 262 | self.assertIsNone(self.c.mint(50)) 263 | self.assertTrue(self.c.transfer(example_contract.address, 50)) 264 | 265 | def test_gst2_eth_example_freeFrom(self): 266 | # Create contract 267 | os.chdir('contract') 268 | example_contract = deploy_solidity_contract( 269 | 'gst2_free_example.sol', 270 | 'GST2FreeExample', 271 | self.s, 272 | sender=self.t.k0) 273 | os.chdir('..') 274 | 275 | # Intial mint and free to set storage to non-zero values 276 | self.assertFalse(self.nth_child_has_code(1)) 277 | self.assertIsNone(self.c.mint(1)) 278 | self.assertTrue(self.nth_child_has_code(1)) 279 | self.assertTrue(self.c.free(1)) 280 | self.assertFalse(self.nth_child_has_code(1)) 281 | 282 | # Supply example_contract with some tokens 283 | self.assertIsNone(self.c.mint(60)) 284 | self.assertTrue(self.c.approve(example_contract.address, 1000000000)) 285 | self.assertEqual(60, self.c.balanceOf(self.t.a0)) 286 | self.assertEqual(1000000000, self.c.allowance(self.t.a0, example_contract.address)) 287 | 288 | # Free with varying gas amounts to check that we never destroy tokens without 289 | # self-destructing a contract. 290 | tail_nonce = 2 291 | for gas in range(24000, 1000000, 1000): 292 | old_balance = self.c.balanceOf(self.t.a0) 293 | freed = example_contract.freeFromExample(self.t.a0, 180, startgas=gas) 294 | print('Freed', freed, 'with', gas, 'gas') 295 | self.assertEqual(old_balance - freed, self.c.balanceOf(self.t.a0)) 296 | tail_nonce += freed 297 | self.assertFalse(self.nth_child_has_code(tail_nonce - 1)) 298 | self.assertTrue(self.nth_child_has_code(tail_nonce)) 299 | 300 | # Replenish example_contract's suply 301 | while self.c.balanceOf(self.t.a0) < 200: 302 | self.assertIsNone(self.c.mint(50)) 303 | 304 | def test_solidity_compiler_bug(self): 305 | self.assertFalse(self.nth_child_has_code(1)) 306 | 307 | self.assertIsNone(self.c.mint(18, sender=self.t.k1)) 308 | self.assertEqual(self.c.totalSupply(), 18) 309 | 310 | self.assertTrue(self.nth_child_has_code(1)) 311 | 312 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 313 | self.assertEqual(self.c.totalSupply(), 17) 314 | 315 | self.assertFalse(self.nth_child_has_code(1)) 316 | self.assertTrue(self.nth_child_has_code(2)) 317 | self.assertTrue(self.nth_child_has_code(3)) 318 | 319 | self.assertEqual(True, self.c.free(2, sender=self.t.k1, startgas=65000 - 17000 + 700)) 320 | 321 | # Due to the bug, the second child wasn't destroyed 322 | self.assertTrue(self.nth_child_has_code(2)) 323 | self.assertFalse(self.nth_child_has_code(3)) 324 | 325 | def test_child_initcode(self): 326 | """Check that the initcode of the child contract creates the correct 327 | contract. 328 | """ 329 | child_address = utils.mk_contract_address(self.c.address, 1) 330 | self.assertEqual(b'', self.s.head_state.get_code(child_address)) 331 | self.c.mint(1) 332 | self.assertEqual(self.CHILD_CONTRACT_BIN, utils.encode_hex( 333 | self.s.head_state.get_code(child_address))) 334 | 335 | def test_child_address_check(self): 336 | """Check that the child contract will throw when it's called by anybody 337 | except the ERC20 contract. 338 | """ 339 | child_address = utils.mk_contract_address(self.c.address, 1) 340 | self.c.mint(1) 341 | self.assertLess(0, len(self.s.head_state.get_code(child_address))) 342 | with self.assertRaises(TransactionFailed): 343 | self.s.tx( 344 | sender=self.t.k0, 345 | to=child_address, 346 | value=0, 347 | data=b'', 348 | startgas=10 ** 20) 349 | 350 | def test_mint_oog(self): 351 | """ Check that mint(1) either fully succeeds (a new child contract is 352 | created and balances are increased) or fails with OOG and reverts all 353 | changes 354 | """ 355 | 356 | def get_all_contracts(): 357 | # ignore weird coinbase address that Pyethreum sometimes 358 | # randomly throws in 359 | CB = "3535353535353535353535353535353535353535" 360 | d = list(self.s.head_state.to_dict().keys()) 361 | if CB in d: 362 | d.remove(CB) 363 | return d 364 | 365 | original_contracts = get_all_contracts() 366 | num_deployed_contracts = len(original_contracts) 367 | 368 | # check original nonce 369 | tot_minted = 0 370 | self.assertEqual(self.s.head_state.get_nonce(self.c.address), tot_minted + 1) 371 | 372 | # mint one token to already get the storage values in place 373 | self.assertIsNone(self.c.mint(1, sender=self.t.k1)) 374 | tot_minted += 1 375 | 376 | # check original nonce 377 | self.assertEqual(self.s.head_state.get_nonce(self.c.address), tot_minted + 1) 378 | 379 | min_gas = 0 380 | max_gas = 2**20 381 | 382 | # mint(1) with max_gas should succeed 383 | self.assertIsNone(self.c.mint(1, sender=self.t.k1, startgas=max_gas)) 384 | tot_minted += 1 385 | 386 | # check balance 387 | self.assertEqual(self.c.balanceOf(self.t.a1), tot_minted) 388 | self.assertEqual(self.c.totalSupply(), tot_minted) 389 | 390 | # check new nonce and created contract 391 | a = utils.encode_hex(utils.mk_contract_address(self.c.address, tot_minted)) 392 | self.assertEqual(self.s.head_state.get_nonce(self.c.address), tot_minted + 1) 393 | self.assertEqual(len(get_all_contracts()), num_deployed_contracts + tot_minted) 394 | self.assertTrue(a in get_all_contracts()) 395 | 396 | # mint(1) with min_gas should fail 397 | self.assertRaises(InsufficientStartGas, lambda: self.c.mint(1, sender=self.t.k1, startgas=min_gas)) 398 | 399 | # check balance didn't change 400 | self.assertEqual(self.c.balanceOf(self.t.a1), tot_minted) 401 | self.assertEqual(self.c.totalSupply(), tot_minted) 402 | 403 | # check nonce and number of contracts didn't change 404 | self.assertEqual(self.s.head_state.get_nonce(self.c.address), tot_minted + 1) 405 | self.assertEqual(len(get_all_contracts()), num_deployed_contracts + tot_minted) 406 | 407 | # search for the minimal amount of gas for a mint(1) call to succeed 408 | while max_gas - min_gas > 1: 409 | gas = int((max_gas + min_gas)/2) 410 | try: 411 | self.assertIsNone(self.c.mint(1, sender=self.t.k1, startgas=gas)) 412 | print("\tmint(1) succeeded with {} gas\t (nonce={})".format(gas, self.s.head_state.get_nonce(self.c.address))) 413 | tot_minted += 1 414 | max_gas = gas 415 | 416 | # make sure we created a child at the right address 417 | a = utils.encode_hex(utils.mk_contract_address(self.c.address, tot_minted)) 418 | self.assertTrue(a in get_all_contracts()) 419 | self.assertTrue(utils.encode_hex(self.s.head_state.get_code(a)) == self.CHILD_CONTRACT_BIN) 420 | 421 | except (TransactionFailed, InsufficientStartGas): 422 | print("\tmint(1) failed with {} gas\t\t (nonce={})".format(gas, self.s.head_state.get_nonce(self.c.address))) 423 | 424 | # make sure the child was not created 425 | a = utils.encode_hex(utils.mk_contract_address(self.c.address, tot_minted + 1)) 426 | self.assertTrue(a not in get_all_contracts() or self.s.head_state.get_code(a) == b'') 427 | min_gas = gas 428 | 429 | # check balance agrees with tot_minted 430 | self.assertEqual(self.c.balanceOf(self.t.a1), tot_minted) 431 | self.assertEqual(self.c.totalSupply(), tot_minted) 432 | 433 | # check nonce and number of contracts agree with tot_minted 434 | self.assertEqual(self.s.head_state.get_nonce(self.c.address), tot_minted + 1) 435 | self.assertEqual(len(get_all_contracts()), num_deployed_contracts + tot_minted) 436 | 437 | print(max_gas, min_gas) 438 | self.assertIsNone(self.c.mint(1, sender=self.t.k1, startgas=max_gas)) 439 | self.assertRaises(TransactionFailed, lambda: self.c.mint(1, sender=self.t.k1, startgas=min_gas)) 440 | 441 | 442 | class TestDeployedGST2(TestGST2): 443 | 444 | @classmethod 445 | def setUpClass(cls): 446 | super(TestDeployedGST2, cls).setUpClass() 447 | 448 | with open('contract/GST2_ETH.asm') as fd: 449 | contract_code = utils.decode_hex(fd.read()) 450 | cls.s.head_state.set_code(cls.c.address, contract_code) 451 | 452 | cls.initial_state = cls.s.snapshot() 453 | 454 | def setUp(self): 455 | super().setUp() 456 | 457 | 458 | class TestGST2ETC(TestGST2): 459 | 460 | @classmethod 461 | def setUpClass(cls): 462 | super(TestGST2, cls).setUpClass() 463 | cls.deploy('GST2_ETC.sol') 464 | cls.initial_state = cls.s.snapshot() 465 | 466 | def setUp(self): 467 | super().setUp() 468 | 469 | @unittest.skip("This bug is fixed in GST2 deployed on ETC") 470 | def test_gst2_eth_example_free(self): 471 | pass 472 | 473 | @unittest.skip("This bug is fixed in GST2 deployed on ETC") 474 | def test_gst2_eth_example_freeFrom(self): 475 | pass 476 | 477 | @unittest.skip("This bug is fixed in GST2 deployed on ETC") 478 | def test_solidity_compiler_bug(self): 479 | pass 480 | 481 | def test_solidity_compiler_bug_workaround(self): 482 | self.assertFalse(self.nth_child_has_code(1)) 483 | 484 | self.assertIsNone(self.c.mint(18, sender=self.t.k1)) 485 | self.assertEqual(self.c.totalSupply(), 18) 486 | 487 | self.assertTrue(self.nth_child_has_code(1)) 488 | 489 | self.assertTrue(self.c.free(1, sender=self.t.k1)) 490 | self.assertEqual(self.c.totalSupply(), 17) 491 | 492 | self.assertFalse(self.nth_child_has_code(1)) 493 | self.assertTrue(self.nth_child_has_code(2)) 494 | self.assertTrue(self.nth_child_has_code(3)) 495 | 496 | self.assertEqual(True, self.c.free(2, sender=self.t.k1, startgas=65000 - 17000 + 700)) 497 | 498 | # We have a working workaround, the second child was destroyed 499 | self.assertFalse(self.nth_child_has_code(2)) 500 | self.assertFalse(self.nth_child_has_code(3)) 501 | 502 | 503 | 504 | class TestDeployedGST2ETC(TestGST2ETC): 505 | 506 | @classmethod 507 | def setUpClass(cls): 508 | super(TestDeployedGST2ETC, cls).setUpClass() 509 | 510 | with open('contract/GST2_ETC.asm') as fd: 511 | contract_code = utils.decode_hex(fd.read()) 512 | cls.s.head_state.set_code(cls.c.address, contract_code) 513 | 514 | cls.initial_state = cls.s.snapshot() 515 | 516 | def setUp(self): 517 | super().setUp() 518 | 519 | 520 | def load_tests(loader, tests, pattern): 521 | full_suite = unittest.TestSuite() 522 | 523 | for suite in [TestDeployedGST2, TestGST2, TestDeployedGST2ETC, TestGST2ETC]: 524 | tests = loader.loadTestsFromTestCase(suite) 525 | full_suite.addTests(tests) 526 | return full_suite 527 | 528 | 529 | if __name__ == '__main__': 530 | unittest.main(verbosity=2) 531 | -------------------------------------------------------------------------------- /test/test_rlp.py: -------------------------------------------------------------------------------- 1 | from ethereum.tools import tester 2 | from ethereum import utils 3 | import unittest 4 | import random 5 | from itertools import chain 6 | from .generic_gas_token import input_data_cost 7 | import warnings 8 | 9 | RLP_BASE = 21000 10 | 11 | 12 | # Cost of calling the mk_contract_address function for a given nonce. 13 | # The value returned includes the cost of traversing the function jump table. 14 | # These values are best used to compute the delta in gas costs as the nonce 15 | # increases. We find that rlp_cost(256**9 - 1) - rlp_cost(1) = 1526 - 884 = 642 16 | def rlp_cost(nonce): 17 | if nonce == 0: 18 | return 1036 19 | elif nonce <= 127: 20 | return 906 21 | else: 22 | hex = utils.encode_hex(utils.encode_int(nonce)) 23 | num_bytes = len(hex) / 2 24 | cost = 1058 + num_bytes * 60 25 | if nonce >= 256**8: 26 | cost -= 50 27 | return cost 28 | 29 | 30 | class TestRLP(unittest.TestCase): 31 | 32 | t = None 33 | s = None 34 | c = None 35 | 36 | @classmethod 37 | def setUpClass(cls): 38 | super(TestRLP, cls).setUpClass() 39 | 40 | # Initialize tester, contract and expose relevant objects 41 | cls.t = tester 42 | cls.s = cls.t.Chain() 43 | 44 | cls.s.head_state.gas_limit = 10**80 45 | cls.s.head_state.set_balance(cls.t.a0, 10**80) 46 | cls.s.head_state.set_balance(cls.t.a1, 10**80) 47 | cls.s.head_state.set_balance(cls.t.a2, 10**80) 48 | cls.s.head_state.set_balance(cls.t.a3, 10**80) 49 | 50 | with open('contract/rlp.sol') as fd: 51 | contract_code = fd.read() 52 | contract_code = contract_code.replace(" pure internal ", " public ") 53 | cls.c = cls.s.contract(contract_code, language='solidity') 54 | cls.initial_state = cls.s.snapshot() 55 | 56 | def tot_rlp_cost(self, nonce, address): 57 | cost = RLP_BASE + rlp_cost(nonce) 58 | cost += input_data_cost(nonce) 59 | 60 | if isinstance(address, str): 61 | cost += input_data_cost(utils.decode_int(utils.decode_hex(address)), num_bytes=20) 62 | else: 63 | cost += input_data_cost(address, num_bytes=20) 64 | return cost 65 | 66 | def test_rlp(self): 67 | addresses = [utils.encode_hex(self.c.address), 0x0, "{:x}".format(256**20-1)] 68 | nonces = [0, 69 | 1, 2, 5, 10, 20, 50, 100, 127, 128, 70 | 129, 255, 256, 257, 500, 1000, 256**2-1, 71 | 256**2, 256**2+256+1, 256**3-1, 72 | 256**3, 256**3+256**2+256+1, 256**4-1, 73 | 256**5-1, 74 | 256**6-1, 75 | 256**7-1, 76 | 256**8-1, 77 | 256**8, 256**8+1, 256**8 + 256**7 + 256**6 + 256**5 + 256**4 + 256**3+ 256**2 + 256 + 1, 256**9-2, 78 | 256**9-1] 79 | 80 | scaled_as_expected = True 81 | for address in addresses: 82 | for nonce in nonces: 83 | print("a={}, n={}".format(address, nonce)) 84 | 85 | gas_used_before = self.s.head_state.gas_used 86 | a1 = self.c.mk_contract_address(address, nonce) 87 | gas_used_after = self.s.head_state.gas_used 88 | 89 | gas_used = gas_used_after - gas_used_before 90 | expected = self.tot_rlp_cost(nonce, address) 91 | 92 | if gas_used != expected: 93 | scaled_as_expected = False 94 | warnings.warn("RLP({}, {}) did not scale as expected. Got {}. " 95 | "Expected {}".format(nonce, address, gas_used, expected)) 96 | 97 | a2 = "0x{}".format(utils.encode_hex( 98 | utils.mk_contract_address(address, nonce) 99 | )) 100 | 101 | self.assertEqual(a1, a2) 102 | 103 | self.assertTrue(scaled_as_expected) 104 | 105 | def test_exhaustive1(self): 106 | addresses = [ 107 | b'\x00' * 20, 108 | b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19', 109 | b'\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf7\xf6\xf5\xf4\xf3\xf2\x00\xf0\x53\x00\x00', 110 | ] 111 | for address in addresses: 112 | for nonce in range(1025): 113 | self.assertEqual(utils.encode_hex(utils.mk_contract_address(address, nonce)), self.c.mk_contract_address(address, nonce)[2:]) 114 | 115 | def test_exhaustive2(self): 116 | # This is the actual address we use on the mainnet 117 | address = utils.normalize_address("0x0000000000b3F879cb30FE243b4Dfee438691c04") 118 | for nonce in chain(range(72000), range(4722366482869645213696-72000, 4722366482869645213696)): 119 | self.assertEqual(utils.encode_hex(utils.mk_contract_address(address, nonce)), self.c.mk_contract_address(address, nonce)[2:]) 120 | if nonce % 1000 == 0: 121 | print('exhaustive test currently at nonce:', nonce) 122 | 123 | def test_random(self): 124 | for i in range(20): 125 | address = random_address() 126 | print('testing 200 random nonces for address {}'.format(utils.encode_hex(address))) 127 | for i in range(200): 128 | nonce = random.randint(0, 4722366482869645213696) 129 | self.assertEqual(utils.encode_hex(utils.mk_contract_address(address, nonce)), self.c.mk_contract_address(address, nonce)[2:]) 130 | 131 | 132 | def random_address(): 133 | address = [0] * 20 134 | for i in range(20): 135 | address[i] = random.randint(0, 255) 136 | return bytes(address) 137 | 138 | if __name__ == '__main__': 139 | unittest.main(verbosity=2) 140 | --------------------------------------------------------------------------------