├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── bin └── teth ├── examples ├── .gitignore ├── Gemfile ├── bin │ ├── attach.sh │ ├── build.sh │ ├── import_keys.sh │ ├── init.sh │ ├── migrate.sh │ ├── private_blockchain.sh │ ├── solc_helper.rb │ └── test.sh ├── builds │ └── Token_compiled.js ├── contracts │ ├── Token.sol │ └── math.se ├── genesis.json ├── gtests │ └── Token_test.js ├── private_keys │ ├── 3ae88fe370c39384fc16da2c9e768cf5d2495b48.key │ ├── 81063419f13cab5ac090cd8329d8fff9feead4a0.key │ ├── 9da26fc2e1d6ad9fdd46138906b0104ae68a65d8.key │ └── import.sh ├── rakefile ├── temp │ ├── db │ │ └── Token.json │ └── migrations │ │ └── Token.js └── tests │ ├── math_test.rb │ └── token_test.rb ├── lib └── teth │ ├── configurable.rb │ ├── erbs │ ├── Gemfile │ ├── contract.sol │ ├── contract_test.rb │ └── migration │ ├── minitest.rb │ └── templates │ ├── bin │ ├── attach.sh │ ├── build.sh │ ├── import_keys.sh │ ├── init.sh │ ├── migrate.sh │ ├── private_blockchain.sh │ ├── solc_helper.rb │ └── test.sh │ ├── genesis.json │ ├── gitignore │ ├── private_keys │ └── import.sh │ └── rakefile └── teth.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | examples/Gemfile.lock 2 | .bundle 3 | examples/data 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | gemspec 2 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | teth (0.2.2) 5 | minitest (~> 5.8) 6 | ruby-ethereum (>= 0.9.6) 7 | serpent (>= 0.3.0) 8 | 9 | GEM 10 | specs: 11 | bitcoin-secp256k1 (0.4.0) 12 | ffi (>= 1.9.10) 13 | block_logger (0.1.2) 14 | logging (~> 2.0) 15 | digest-sha3 (1.1.0) 16 | ethash (0.2.0) 17 | ffi (1.9.10) 18 | fiddler-rb (0.1.2) 19 | leveldb (0.1.9) 20 | fiddler-rb (~> 0.1.1) 21 | little-plugger (1.1.4) 22 | logging (2.1.0) 23 | little-plugger (~> 1.1) 24 | multi_json (~> 1.10) 25 | lru_redux (1.1.0) 26 | minitest (5.8.4) 27 | multi_json (1.12.1) 28 | rlp (0.7.3) 29 | ruby-ethereum (0.9.6) 30 | bitcoin-secp256k1 (~> 0.4) 31 | block_logger (~> 0.1) 32 | digest-sha3 (~> 1.1) 33 | ethash (~> 0.2) 34 | ffi (~> 1.9) 35 | leveldb (= 0.1.9) 36 | lru_redux (~> 1.1) 37 | rlp (= 0.7.3) 38 | serpent (0.3.0) 39 | 40 | PLATFORMS 41 | ruby 42 | 43 | DEPENDENCIES 44 | teth! 45 | 46 | BUNDLED WITH 47 | 1.12.5 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 YaNing Zhang, Jan Xie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | Teth is a Ethereum smart contract test framework in ruby.It provides two testing environments: testing in ruby EVM and testing in geth.You don't need to understand ruby grammar, just enjoy syntactic sugar. 3 | 4 | ## Dependencies 5 | - Solidity 6 | - Ruby ~> 2.2.2 7 | - [ruby-bitcoin-secp256k1](https://github.com/cryptape/ruby-bitcoin-secp256k1) 8 | - Go-ethereum ~> 1.4.11 9 | 10 | ## Install 11 | ```shell 12 | bundle install teth 13 | ``` 14 | 15 | ## How to use 16 | 17 | #### Help command 18 | ``` 19 | $ teth 20 | Usage: teth COMMAND [ARGS] 21 | The most common teth commands are: 22 | new Create a new Smart Contract application. "teth new my_app" creates a 23 | new application called my_app in "./my_app" (short-cut alias: "n") 24 | generate Generate new solidity smart contract and test files. "teth generate token" 25 | creates Token contract and corresponding test files. (short-cut alias: "g") 26 | test Run your ruby tests in ruby evm. (short-cut alias: "t") 27 | init Bootstraps and initialises a new genesis block. "teth init" creates data directory 28 | for private chain.(short-cut alias: "i") 29 | import_keys Import keys to private chain (short-cut alias: "ik") 30 | build Build contract (short-cut alias: "b") 31 | migrate Deploy contract on private chain (short-cut alias: "m") 32 | server Start geth server (short-cut alias: "s") 33 | console Start geth attach (short-cut alias: "c") 34 | gtest Run your javascript tests on geth (short-cut alias: "gt") 35 | 36 | All commands can be run with -h (or --help) for more information. 37 | 38 | ``` 39 | #### Creeat project 40 | Create a new Smart Contract application 41 | ```shell 42 | $ teth n examples 43 | Creating project examples... 44 | Resolving dependencies... 45 | Using ffi 1.9.14 46 | Using little-plugger 1.1.4 47 | Using multi_json 1.12.1 48 | Using digest-sha3 1.1.0 49 | Using ethash 0.2.0 50 | Using fiddler-rb 0.1.2 51 | Using lru_redux 1.1.0 52 | Using minitest 5.9.0 53 | Using rlp 0.7.3 54 | Using bundler 1.12.5 55 | Using bitcoin-secp256k1 0.4.0 56 | Using logging 2.1.0 57 | Using leveldb 0.1.9 58 | Using block_logger 0.1.2 59 | Using ruby-ethereum 0.9.5 60 | Using teth 0.1.1 61 | Bundle complete! 1 Gemfile dependency, 16 gems now installed. 62 | Use `bundle show [gemname]` to see where a bundled gem is installed. 63 | Done. 64 | 65 | $ cd examples 66 | ``` 67 | #### Generate new Smart Contract and test files 68 | ```shell 69 | $ teth g token 70 | Creating Token contract file... 71 | Creating token test files... 72 | Done. 73 | ``` 74 | This generates contract file `contracts/Token.sol`, and test files `tests/token_test.rb`, `gtests/Token_test.js`. 75 | 76 | Edit token contract and test file. 77 | 78 | `contracts/Token.sol` 79 | ```javascript 80 | pragma solidity ^0.4.0; 81 | 82 | contract Token { 83 | address issuer; 84 | mapping (address => uint) balances; 85 | 86 | event Issue(address account, uint amount); 87 | event Transfer(address from, address to, uint amount); 88 | 89 | function Token() { 90 | issuer = msg.sender; 91 | } 92 | 93 | function issue(address account, uint amount) { 94 | if (msg.sender != issuer) throw; 95 | balances[account] += amount; 96 | Issue(account, amount); 97 | } 98 | 99 | function transfer(address to, uint amount) { 100 | if (balances[msg.sender] < amount) throw; 101 | 102 | balances[msg.sender] -= amount; 103 | balances[to] += amount; 104 | 105 | Transfer(msg.sender, to, amount); 106 | } 107 | 108 | function getBalance(address account) constant returns (uint) { 109 | return balances[account]; 110 | } 111 | } 112 | 113 | ``` 114 | 115 | `tests/token_test.rb` 116 | ```ruby 117 | require 'teth/minitest' 118 | 119 | class TokenTest < Teth::Minitest 120 | def test_contract_deployed 121 | assert_equal false, contract.address.nil? 122 | end 123 | 124 | def test_issue_balance 125 | assert_equal 0, contract.getBalance(bob) 126 | contract.issue bob, 100 127 | assert_equal 100, contract.getBalance(bob) 128 | end 129 | 130 | def test_issue_exception 131 | assert_raises(TransactionFailed) do 132 | contract.issue bob, 100, sender: eve_privkey 133 | end 134 | assert_equal 0, contract.getBalance(bob) 135 | end 136 | 137 | def test_token_transfer 138 | contract.issue bob, 100 139 | contract.transfer carol, 90, sender: bob_privkey 140 | assert_equal 90, contract.getBalance(carol) 141 | 142 | assert_raises(TransactionFailed) { contract.transfer carol, 90, sender: bob_privkey } 143 | end 144 | end 145 | 146 | ``` 147 | 148 | #### Run tests in ruby evm 149 | ```shell 150 | $ teth t token 151 | Test Token contract... 152 | Run options: --seed 2192 153 | 154 | # Running: 155 | 156 | .... 157 | 158 | Finished in 1.935546s, 2.0666 runs/s, 3.6166 assertions/s. 159 | 160 | 4 runs, 7 assertions, 0 failures, 0 errors, 0 skips 161 | Done. 162 | ``` 163 | 164 | #### Unit tests 165 | You can wirte fast, simple tests. 166 | ```ruby 167 | require 'teth/minitest' 168 | 169 | class TokenTest < Teth::Minitest 170 | def test_contract_deployed 171 | assert_equal false, contract.address.nil? 172 | end 173 | 174 | def test_issue_balance 175 | assert_equal 0, contract.getBalance(bob) 176 | contract.issue bob, 100 177 | assert_equal 100, contract.getBalance(bob) 178 | end 179 | 180 | def test_issue_exception 181 | assert_raises(TransactionFailed) do 182 | contract.issue bob, 100, sender: eve_privkey 183 | end 184 | assert_equal 0, contract.getBalance(bob) 185 | end 186 | 187 | def test_token_transfer 188 | contract.issue bob, 100 189 | contract.transfer carol, 90, sender: bob_privkey 190 | assert_equal 90, contract.getBalance(carol) 191 | 192 | assert_raises(TransactionFailed) { contract.transfer carol, 90, sender: bob_privkey } 193 | end 194 | end 195 | 196 | ``` 197 | More details: 198 | https://github.com/seattlerb/minitest 199 | 200 | #### Init geth block 201 | ```shell 202 | $ teth init 203 | Initialising a new genesis block... 204 | ***** Using geth at: geth 205 | I0917 16:01:17.338908 ethdb/database.go:82] Alloted 16MB cache and 16 file handles to /Users/u2/cryptape/teth/examples/data/chaindata 206 | I0917 16:01:17.347151 cmd/geth/main.go:299] successfully wrote genesis block and/or chain rule set: 611596e7979cd4e7ca1531260fa706093a5492ecbdf58f20a39545397e424d04 207 | ``` 208 | #### Import keys to geth 209 | ```shell 210 | $ teth ik 211 | Importing keys, this will take a while, please be patient...... 212 | ***** Using geth at: geth 213 | ***** Import all pre-funded private keys 214 | Notice: No need to input your password. The default password is 123456 215 | spawn geth --datadir data account import ./private_keys/3ae88fe370c39384fc16da2c9e768cf5d2495b48.key 216 | Your new account is locked with a password. Please give a password. Do not forget this password. 217 | Passphrase: 218 | Repeat passphrase: 219 | Address: {3ae88fe370c39384fc16da2c9e768cf5d2495b48} 220 | Notice: No need to input your password. The default password is 123456 221 | spawn geth --datadir data account import ./private_keys/81063419f13cab5ac090cd8329d8fff9feead4a0.key 222 | Your new account is locked with a password. Please give a password. Do not forget this password. 223 | Passphrase: 224 | Repeat passphrase: 225 | Address: {81063419f13cab5ac090cd8329d8fff9feead4a0} 226 | Notice: No need to input your password. The default password is 123456 227 | spawn geth --datadir data account import ./private_keys/9da26fc2e1d6ad9fdd46138906b0104ae68a65d8.key 228 | Your new account is locked with a password. Please give a password. Do not forget this password. 229 | Passphrase: 230 | Repeat passphrase: 231 | Address: {9da26fc2e1d6ad9fdd46138906b0104ae68a65d8} 232 | ***** Done. 233 | 234 | ``` 235 | Notice: *This will take a while, please be patient. No need to input your password.* 236 | 237 | #### Build contract 238 | ```shell 239 | $ teth build token 240 | Building contract Token 241 | 242 | ======= Token ======= 243 | Gas estimation: 244 | construction: 245 | 20201 + 73400 = 93601 246 | external: 247 | 248 | 249 | ------------------------------------- 250 | Enter Gas: 251 | 400000 252 | Enter Value To Be Transferred: 253 | 254 | Enter Input: 255 | 256 | Done. 257 | ``` 258 | Build all contracts if no contract name provided. 259 | 260 | #### Start geth server 261 | ```shell 262 | $ teth server 263 | ***** Using geth at: geth 264 | Start geth server... 265 | I0917 16:17:16.882572 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to data/chaindata 266 | I0917 16:17:16.894415 ethdb/database.go:169] closed db:data/chaindata 267 | I0917 16:17:16.895446 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to data/chaindata 268 | I0917 16:17:16.946341 eth/backend.go:621] upgrading db log bloom bins 269 | I0917 16:17:16.946478 eth/backend.go:629] upgrade completed in 142.276µs 270 | I0917 16:17:16.946513 ethdb/database.go:82] Alloted 16MB cache and 16 file handles to data/dapp 271 | I0917 16:17:16.951072 eth/backend.go:172] Protocol Versions: [63 62], Network Id: 31415926 272 | I0917 16:17:16.951142 eth/backend.go:201] Blockchain DB Version: 3 273 | I0917 16:17:16.953641 core/blockchain.go:206] Last header: #0 [611596e7…] TD=131072 274 | I0917 16:17:16.953667 core/blockchain.go:207] Last block: #0 [611596e7…] TD=131072 275 | I0917 16:17:16.953678 core/blockchain.go:208] Fast block: #0 [611596e7…] TD=131072 276 | I0917 16:17:16.954420 p2p/server.go:313] Starting Server 277 | I0917 16:17:16.955324 p2p/server.go:556] Listening on [::]:30303 278 | I0917 16:17:16.957427 node/node.go:296] IPC endpoint opened: data/geth.ipc 279 | I0917 16:17:16.959797 node/node.go:366] HTTP endpoint opened: http://localhost:8545 280 | I0917 16:17:17.945231 cmd/geth/accountcmd.go:189] Unlocked account 3ae88fe370c39384fc16da2c9e768cf5d2495b48 281 | I0917 16:17:19.158064 p2p/nat/nat.go:111] mapped network port tcp:30303 -> 30303 (ethereum p2p) using UPNP IGDv1-IP1 282 | ``` 283 | #### Migrate 284 | Deploy your contract on geth, must keep teth sever started. 285 | ```shell 286 | $ teth m 287 | Migrating contract token 288 | ***** Using geth at: geth 289 | null 290 | Contract transaction send: TransactionHash: 0x3a9ca7a774a4bc5b3ba23b57f3c65a5debbfcbba422f902009909604ee668a63 waiting to be mined... 291 | Compiled Object : TokenCompiled 292 | Contract : TokenContract 293 | Contract Instance : Token 294 | true 295 | Contract mined! Address: 0x3a020580345e79e223580d8d6a50e063667f19b5 296 | Done. 297 | 298 | ``` 299 | This deploys contract on geth, and creates two files.One is `temp/db/Token.json` which keeps abi and address. 300 | ```json 301 | { 302 | "Token": { 303 | "abi": [ 304 | { 305 | "constant": false, 306 | "inputs": [ 307 | { 308 | "name": "account", 309 | "type": "address" 310 | }, 311 | { 312 | "name": "amount", 313 | "type": "uint256" 314 | } 315 | ], 316 | "name": "issue", 317 | "outputs": [ 318 | 319 | ], 320 | "payable": false, 321 | "type": "function" 322 | }, 323 | { 324 | "constant": false, 325 | "inputs": [ 326 | { 327 | "name": "to", 328 | "type": "address" 329 | }, 330 | { 331 | "name": "amount", 332 | "type": "uint256" 333 | } 334 | ], 335 | "name": "transfer", 336 | "outputs": [ 337 | 338 | ], 339 | "payable": false, 340 | "type": "function" 341 | }, 342 | { 343 | "constant": true, 344 | "inputs": [ 345 | { 346 | "name": "account", 347 | "type": "address" 348 | } 349 | ], 350 | "name": "getBalance", 351 | "outputs": [ 352 | { 353 | "name": "", 354 | "type": "uint256" 355 | } 356 | ], 357 | "payable": false, 358 | "type": "function" 359 | }, 360 | { 361 | "inputs": [ 362 | 363 | ], 364 | "type": "constructor" 365 | }, 366 | { 367 | "anonymous": false, 368 | "inputs": [ 369 | { 370 | "indexed": false, 371 | "name": "account", 372 | "type": "address" 373 | }, 374 | { 375 | "indexed": false, 376 | "name": "amount", 377 | "type": "uint256" 378 | } 379 | ], 380 | "name": "Issue", 381 | "type": "event" 382 | }, 383 | { 384 | "anonymous": false, 385 | "inputs": [ 386 | { 387 | "indexed": false, 388 | "name": "from", 389 | "type": "address" 390 | }, 391 | { 392 | "indexed": false, 393 | "name": "to", 394 | "type": "address" 395 | }, 396 | { 397 | "indexed": false, 398 | "name": "amount", 399 | "type": "uint256" 400 | } 401 | ], 402 | "name": "Transfer", 403 | "type": "event" 404 | } 405 | ], 406 | "bin": "0x606060405260008054600160a060020a0319163317905561016f806100246000396000f3606060405260e060020a6000350463867904b48114610034578063a9059cbb1461005e578063f8b2cb4f14610092575b610002565b34610002576100bc600435602435600054600160a060020a0390811633909116146100be57610002565b34610002576100bc60043560243533600160a060020a03166000908152600160205260409020548190101561010f57610002565b3461000257600160a060020a03600435166000908152600160205260409020546060908152602090f35b005b600160a060020a03821660008181526001602052604090819020805484019055606091825260808390527fc65a3f767206d2fdcede0b094a4840e01c0dd0be1888b5ba800346eaa0123c1691a15050565b6040600081812080548490039055600160a060020a03808516808352929091208054840190553316606090815260809190915260a08290527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9080a1505056", 407 | "devdoc": { 408 | "methods": { 409 | } 410 | }, 411 | "userdoc": { 412 | "methods": { 413 | } 414 | } 415 | }, 416 | "address": "0x3a020580345e79e223580d8d6a50e063667f19b5" 417 | } 418 | ``` 419 | Another is `temp/migrations/Token.js` 420 | ```javascript 421 | var TokenContract = web3.eth.contract([{"constant":false, "inputs":[{"name":"account", "type":"address"}, {"name":"amount", "type":"uint256"}], "name":"issue", "outputs":[], "payable":false, "type":"function"}, {"constant":false, "inputs":[{"name":"to", "type":"address"}, {"name":"amount", "type":"uint256"}], "name":"transfer", "outputs":[], "payable":false, "type":"function"}, {"constant":true, "inputs":[{"name":"account", "type":"address"}], "name":"getBalance", "outputs":[{"name":"", "type":"uint256"}], "payable":false, "type":"function"}, {"inputs":[], "type":"constructor"}, {"anonymous":false, "inputs":[{"indexed":false, "name":"account", "type":"address"}, {"indexed":false, "name":"amount", "type":"uint256"}], "name":"Issue", "type":"event"}, {"anonymous":false, "inputs":[{"indexed":false, "name":"from", "type":"address"}, {"indexed":false, "name":"to", "type":"address"}, {"indexed":false, "name":"amount", "type":"uint256"}], "name":"Transfer", "type":"event"}]); 422 | 423 | var Token = TokenContract.at('0x80d29fb7f81d2ccd77c708b6135389c9c08653dc'); 424 | 425 | ``` 426 | Deploy all contracts if no contract name provided. 427 | 428 | #### Write your own javascript test 429 | `gtests/Token_test.js` 430 | ```javascript 431 | loadScript('temp/migrations/Token.js'); 432 | 433 | var balance = Token.getBalance.call(web3.eth.accounts[0], { from: web3.eth.accounts[0] }) 434 | 435 | console.log("balance is: ", balance); 436 | 437 | Token.issue.sendTransaction(web3.eth.accounts[0], 10000, { from: web3.eth.accounts[0] }, function(err, tx){ 438 | if(err){ 439 | console.log("issue error!"); 440 | } else { 441 | console.log("issue success. tx: ", tx); 442 | } 443 | }) 444 | 445 | miner.start();admin.sleepBlocks(2);miner.stop(); 446 | 447 | balance = Token.getBalance.call(web3.eth.accounts[0], { from: web3.eth.accounts[0] }) 448 | 449 | console.log("balance is: ", balance); 450 | 451 | ``` 452 | 453 | #### Run gtest 454 | ``` 455 | $ teth gt token 456 | ***** Using geth at: geth 457 | Testing contract Token... 458 | balance is: 0 459 | issue success. tx: 0x7fd24d1903345d4f70208c41fc3a1bd71be63f8dd7db7c654f2d3a7c176b4031 460 | balance is: 10000 461 | true 462 | Done. 463 | ``` 464 | 465 | :beers: 466 | 467 | ## Frequently Asked Questions 468 | 469 | #### Error: Account does not exist or account balance too low while `teth migrate token` 470 | This is just low balance for your accoutns. Just mining for a while will be ok. 471 | ```shell 472 | $ teth c 473 | ***** Using geth at: geth 474 | Starting geth attach... 475 | Welcome to the Geth JavaScript console! 476 | 477 | instance: Geth/v1.4.11-stable-fed692f6/darwin/go1.7 478 | coinbase: 0x3ae88fe370c39384fc16da2c9e768cf5d2495b48 479 | at block: 3 (Sat, 17 Sep 2016 17:42:50 CST) 480 | datadir: data 481 | modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 482 | 483 | > miner.start() 484 | true 485 | > web3.eth.getBalance(web3.eth.accounts[0]) 486 | 506406250000000000000 487 | > 488 | 489 | ``` 490 | 491 | 492 | ## TODO: 493 | 494 | - Add chai for js test 495 | -------------------------------------------------------------------------------- /bin/teth: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'erb' 4 | require 'json' 5 | require 'fileutils' 6 | require 'ethereum' 7 | 8 | ARGV << "--help" if ARGV.empty? 9 | 10 | ALIASES = { 11 | "n" => "new", 12 | "g" => "generate", 13 | "t" => "test", 14 | "i" => "init", 15 | "ik" => "import_keys", 16 | "b" => "build", 17 | "m" => "migrate", 18 | "s" => "server", 19 | "c" => "console", 20 | "gt" => "gtest" 21 | } 22 | 23 | command = ARGV.shift 24 | command = ALIASES[command] || command 25 | 26 | HELP_MESSAGE = <<-EOF 27 | Usage: teth COMMAND [ARGS] 28 | The most common teth commands are: 29 | new Create a new Smart Contract application. "teth new my_app" creates a 30 | new application called my_app in "./my_app" (short-cut alias: "n") 31 | generate Generate new solidity smart contract and test files. "teth generate token" 32 | creates Token contract and corresponding test files. (short-cut alias: "g") 33 | test Run your ruby tests in ruby evm. (short-cut alias: "t") 34 | init Bootstraps and initialises a new genesis block. "teth init" creates data directory 35 | for private chain.(short-cut alias: "i") 36 | import_keys Import keys to private chain (short-cut alias: "ik") 37 | build Build contract (short-cut alias: "b") 38 | migrate Deploy contract on private chain (short-cut alias: "m") 39 | server Start geth server (short-cut alias: "s") 40 | console Start geth attach (short-cut alias: "c") 41 | gtest Run your javascript tests on geth (short-cut alias: "gt") 42 | 43 | All commands can be run with -h (or --help) for more information. 44 | EOF 45 | 46 | KEYS_TEMPLATE = [ 47 | ["3ae88fe370c39384fc16da2c9e768cf5d2495b48", "095e53c9c20e23fd01eaad953c01da9e9d3ed9bebcfed8e5b2c2fce94037d963"], 48 | ["81063419f13cab5ac090cd8329d8fff9feead4a0", "5bc505a123a695176a9688ffe22798cfd40424c5b91c818e985574ea8ebda167"], 49 | ["9da26fc2e1d6ad9fdd46138906b0104ae68a65d8", "b6a03207128827eaae0d31d97a7a6243de31f2baf99eabd764e33389ecf436fc"] 50 | ] 51 | 52 | def gem_dir 53 | spec = Gem::Specification.find_by_name("teth") 54 | spec.gem_dir 55 | end 56 | 57 | def new 58 | name = ARGV.shift 59 | if name 60 | puts "Creating project #{name}..." 61 | dirs_commands = %w{ private_keys builds temp contracts tests gtests}.map{ |d| "mkdir #{d}" }.join(" && ") 62 | system("mkdir #{name} && cd #{name} && #{dirs_commands}") 63 | system("cd #{name} && cd temp && mkdir db && mkdir migrations") 64 | gemfile = File.read("#{gem_dir}/lib/teth/erbs/Gemfile") 65 | 66 | File.open("#{name}/Gemfile", "w+") {|f| f.write(gemfile) } 67 | system("cd #{name} && bundle install") 68 | 69 | KEYS_TEMPLATE.each do |k| 70 | File.open("#{name}/private_keys/#{k[0]}.key", "w+") { |f| f.write(k[1]) } 71 | end 72 | 73 | FileUtils.cp("#{gem_dir}/lib/teth/templates/private_keys/import.sh", "#{name}/private_keys/import.sh") 74 | FileUtils.chmod 0700, "#{name}/private_keys/import.sh" 75 | 76 | %w{genesis.json rakefile gitignore}.each do |f| 77 | FileUtils.cp("#{gem_dir}/lib/teth/templates/#{f}", "#{name}/#{f}") 78 | end 79 | FileUtils.mv("#{name}/gitignore", "#{name}/.gitignore") 80 | 81 | FileUtils.cp_r("#{gem_dir}/lib/teth/templates/bin/", "#{name}") 82 | FileUtils.chmod_R 0700, "#{name}/bin/" 83 | puts "Done." 84 | else 85 | puts "Need project name" 86 | end 87 | end 88 | 89 | def generate 90 | name = ARGV.shift 91 | if name 92 | puts "Creating #{name.capitalize} contract file..." 93 | contract_template = ERB.new File.read("#{gem_dir}/lib/teth/erbs/contract.sol") 94 | contract = contract_template.result(binding) 95 | 96 | version = Ethereum::Tester::Language.get(:solidity).compiler_version 97 | semver = version.split('-').first 98 | major, minor, patch = semver.split('.') 99 | unless major.to_i < 1 && minor.to_i < 4 100 | pragma = "pragma solidity ^#{major}.#{minor}.0;" # always set patch to 0 101 | contract = "#{pragma}\n\n#{contract}" 102 | end 103 | 104 | puts "Create #{name.capitalize}.sol contract file..." 105 | File.open("contracts/#{name.capitalize}.sol", "w+") { |f| f.write(contract) } 106 | 107 | puts "Creating #{name} test files..." 108 | test_template = ERB.new File.read("#{gem_dir}/lib/teth/erbs/contract_test.rb") 109 | test = test_template.result(binding) 110 | File.open("tests/#{name}_test.rb", "w+") {|f| f.write(test) } 111 | File.open("gtests/#{name.capitalize}_test.js", "w") do |f| 112 | f.write("loadScript('temp/migrations/#{name.capitalize}.js');") 113 | end 114 | puts "Done." 115 | else 116 | puts "Need contract name" 117 | end 118 | end 119 | 120 | def test 121 | name = ARGV.shift 122 | if name 123 | puts "Test #{name.capitalize} contract..." 124 | system("bundle exec ruby -Ilib:test tests/#{name}_test.rb") 125 | else 126 | puts "Test all contracts..." 127 | system("bundle exec rake") 128 | end 129 | puts "Done." 130 | end 131 | 132 | def init 133 | puts "Initialising a new genesis block..." 134 | system("./bin/init.sh") 135 | end 136 | 137 | def import_keys 138 | puts "Importing keys, this will take a while, please be patient..." 139 | system("./bin/import_keys.sh") 140 | end 141 | 142 | def build 143 | name = ARGV.shift 144 | system("./bin/build.sh #{name}") 145 | end 146 | 147 | def server 148 | system("./bin/private_blockchain.sh") 149 | end 150 | 151 | def migrate 152 | name = ARGV.shift 153 | if name 154 | puts "Migrating contract #{name}" 155 | else 156 | puts "Migrating all contracts" 157 | end 158 | output = `./bin/migrate.sh #{name}` 159 | puts output 160 | contract = "" 161 | output.split("\n").each do |o| 162 | if o.match("Contract Instance") 163 | contract = o.split("Contract Instance : ")[1] 164 | end 165 | if o.match("Contract mined!") 166 | address = o.split("Address: ")[1] 167 | data = File.read("temp/db/#{contract}.json") 168 | data = JSON.parse data 169 | data["address"] = address 170 | File.open("temp/db/#{contract}.json", "w") do |f| 171 | f.write(JSON.pretty_generate data) 172 | end 173 | File.open("temp/migrations/#{contract}.js", "w") do |f| 174 | abi = data[contract]["abi"] 175 | template = ERB.new File.read("#{gem_dir}/lib/teth/erbs/migration") 176 | migration = template.result(binding) 177 | f.write(migration.gsub("=>", ":")) 178 | end 179 | end 180 | end 181 | end 182 | 183 | def console 184 | system("./bin/attach.sh") 185 | end 186 | 187 | def gtest 188 | name = ARGV.shift 189 | system("./bin/test.sh #{name}") 190 | end 191 | 192 | def help 193 | write_help_message 194 | end 195 | 196 | def write_help_message 197 | puts HELP_MESSAGE 198 | end 199 | 200 | def parse_command(command) 201 | case command 202 | when "--help", "-h" 203 | "help" 204 | else 205 | command 206 | end 207 | end 208 | 209 | def run_command!(command) 210 | command = parse_command(command) 211 | 212 | if ALIASES.values.include?(command) 213 | send(command) 214 | else 215 | help 216 | end 217 | end 218 | 219 | run_command!(command) 220 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | data 3 | -------------------------------------------------------------------------------- /examples/Gemfile: -------------------------------------------------------------------------------- 1 | gem 'teth', '>= 0.1.1' 2 | -------------------------------------------------------------------------------- /examples/bin/attach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | echo "Starting geth attach..." 8 | 9 | $geth attach ipc:data/geth.ipc 10 | -------------------------------------------------------------------------------- /examples/bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -z "$1" ] 3 | then 4 | echo "Building all contracts ..." 5 | for sol in `find ./contracts -name '*.sol'` 6 | do 7 | echo "Building contract ${sol}" 8 | let len=${#sol}-16 9 | jsfile="${sol:12:len}_compiled.js" 10 | ./bin/solc_helper.rb ${sol} $jsfile 11 | mv $jsfile builds/ 12 | done 13 | echo "Done." 14 | else 15 | sol=$1 16 | sol="$(tr '[:lower:]' '[:upper:]' <<< ${sol:0:1})${sol:1}" 17 | echo "Building contract ${sol}" 18 | 19 | file="contracts/${sol}.sol" 20 | len=${#sol}-16 21 | jsfile="${sol}_compiled.js" 22 | ./bin/solc_helper.rb $file $jsfile 23 | mv $jsfile builds/ 24 | echo "Done." 25 | fi 26 | -------------------------------------------------------------------------------- /examples/bin/import_keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | echo "***** Import all pre-funded private keys" 8 | 9 | for key in `find ./private_keys -name '*.key'` 10 | do 11 | ./private_keys/import.sh $key $geth 12 | done 13 | 14 | echo "***** Done." 15 | -------------------------------------------------------------------------------- /examples/bin/init.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | $geth --datadir `pwd`/data init genesis.json 8 | -------------------------------------------------------------------------------- /examples/bin/migrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | if [ -z "$1" ] 8 | then 9 | scripts="" 10 | for file in `find ./builds -name '*compiled.js'` 11 | do 12 | scripts="${scripts};loadScript('$file')" 13 | done 14 | scripts="${scripts};miner.start();admin.sleepBlocks(2);miner.stop()" 15 | $geth --exec "$scripts" attach ipc:data/geth.ipc 16 | else 17 | file=$1 18 | file="$(tr '[:lower:]' '[:upper:]' <<< ${file:0:1})${file:1}" 19 | file+="_compiled.js" 20 | $geth --exec "loadScript('builds/$file');miner.start();admin.sleepBlocks(2);miner.stop()" attach ipc:data/geth.ipc 21 | fi 22 | 23 | echo "Done." 24 | -------------------------------------------------------------------------------- /examples/bin/private_blockchain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | geth=${GETH:-geth} 3 | 4 | echo "***** Using geth at: $geth" 5 | 6 | echo "Start geth server..." 7 | 8 | $geth --datadir data --networkid 31415926 --rpc --rpccorsdomain "*" --nodiscover --unlock 3ae88fe370c39384fc16da2c9e768cf5d2495b48 --password <(echo -n 123456) 9 | -------------------------------------------------------------------------------- /examples/bin/solc_helper.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | def library_code 5 | 'function create(abiDefinition) { return web3.eth.contract(abiDefinition);}/*function deploy(account, value, gas, contract, code, input) */function deploy() { var account = arguments[0]; var value = arguments[1]; var gas = arguments[2]; var contract = arguments[3]; var code = arguments[4]; var codeString = "contract.new(inputMarker,{from:\'accountMarker\', value: valueMarker, data: \'codeMarker\', gas: gasMarker}, function (e, contract) { if(!e) { if(!contract.address) { console.log(\"Contract transaction send: TransactionHash: \" + contract.transactionHash + \" waiting to be mined...\"); } else { console.log(\"Contract mined! Address: \" + contract.address); } } else { console.log(e) } })"; codeString = codeString.replace("accountMarker", account); codeString = codeString.replace("valueMarker", value); codeString = codeString.replace("codeMarker", code); codeString = codeString.replace("gasMarker", gas); input = "null"; if (arguments.length > 5) { if (arguments[5] != null) { var args = []; for (var i = 5;i < arguments.length; i++) { var val = arguments[i]; if (typeof(val) === \'string\') { val = "\"" + val + "\""; } args.push(val); } input = args.join(","); } } codeString = codeString.replace("inputMarker", input); console.log(input); var instance = eval(codeString); return instance;}function watcher(error, result) { if (!error) { console.log("Result"); console.log(JSON.stringify(result)); return; } console.log("Error" + error);}/*function call(account, gas, func, input) */function call() { var account = "eth.accounts["+arguments[0]+"]"; var gas = arguments[1]; var func = arguments[2]; input = "null"; if (arguments.length > 3) { if (arguments[3] != null) { var args = Array.prototype.slice.call(arguments, 3); input = args.join(","); } } codeString = "func.sendTransaction(inputMarker, gasMarker, {from:accountMarker}, watcher);"; codeString = codeString.replace("accountMarker",account); codeString = codeString.replace("gasMarker",gas); codeString = codeString.replace("inputMarker",input); eval(codeString);}function send(from_index, to, value, gas){ return eth.sendTransaction({from:eth.accounts[from_index], to:to, value:web3.toWei(value,\'ether\'), gas:gas});}function bal() { for (var i = 0; i < eth.accounts.length; i++) { account = eth.accounts[i]; balance = web3.fromWei(eth.getBalance(eth.accounts[i]), \'ether\'); console.log("Index : " + i); console.log("Account : "+ account); console.log("Balance : "+ balance); console.log("\n"); }}' 6 | end 7 | 8 | def compile_solidity(file) 9 | json_string = `solc --add-std --optimize --combined-json abi,bin,userdoc,devdoc #{file}` 10 | json_string = json_string.gsub("\\n","") 11 | begin 12 | json_object = JSON.parse(json_string) 13 | throw if json_object.nil? 14 | puts `solc --optimize --gas #{file}` 15 | puts "\n\n" 16 | puts "-------------------------------------" 17 | json_object["contracts"] 18 | rescue 19 | puts "Failed to Compile." 20 | abort 21 | end 22 | end 23 | 24 | def process_code(contracts) 25 | contracts.keys.each.with_index do |key, i| 26 | contracts[key]["bin"] = "0x" + contracts[key]["bin"] 27 | contracts[key]["abi"] = JSON.parse(contracts[key]["abi"]) 28 | contracts[key]["devdoc"] = JSON.parse(contracts[key]["devdoc"]) 29 | contracts[key]["userdoc"] = JSON.parse(contracts[key]["userdoc"]) 30 | end 31 | return contracts 32 | end 33 | 34 | 35 | def javascript_file_name(file_name) 36 | file_name = file_name.split('/')[-1] 37 | file_name.split('.')[0] + '_compiled.js' 38 | end 39 | 40 | def get_contract_to_deploy(compiled_object) 41 | return compiled_object.keys[0] if compiled_object.keys.count == 1 42 | puts "Which contract do you want to deploy?" 43 | choice = 0 44 | while choice <= 0 || choice > compiled_object.keys.count 45 | compiled_object.keys.each.with_index do |key, i| 46 | puts "#{(i+1)}. "+key 47 | end 48 | choice = $stdin.gets.to_i 49 | end 50 | return compiled_object.keys[choice - 1] 51 | end 52 | 53 | def get_input 54 | puts "Enter Input: " 55 | input = $stdin.gets 56 | input.strip.chomp == "" ? "null" : input 57 | end 58 | 59 | def get_gas 60 | gas = 0 61 | while gas == 0 62 | puts "Enter Gas: " 63 | gas = $stdin.gets.to_i 64 | end 65 | gas 66 | end 67 | 68 | def get_value 69 | gas = -1 70 | while gas < 0 71 | puts "Enter Value To Be Transferred: " 72 | gas = $stdin.gets.to_i 73 | end 74 | gas 75 | end 76 | 77 | file_name = ARGV[0] 78 | 79 | compiled_object = compile_solidity(file_name) 80 | compiled_object = process_code(compiled_object) 81 | javascript_file_name = javascript_file_name(file_name) 82 | 83 | current_contract = get_contract_to_deploy(compiled_object) 84 | 85 | compiled_variable_name = "#{current_contract}Compiled" 86 | contract_variable_name = "#{current_contract}Contract" 87 | contract_instance_variable_name = "#{current_contract}" 88 | 89 | gas = get_gas 90 | value = get_value 91 | input = get_input 92 | 93 | File.open(javascript_file_name, 'w') do |f| 94 | f.write("#{library_code};\nvar #{compiled_variable_name} = #{compiled_object.to_json};") 95 | f.write("#{contract_variable_name} = create(#{compiled_variable_name}.#{current_contract}.abi);") 96 | f.write("#{contract_instance_variable_name} = deploy(eth.coinbase,#{value},#{gas},#{contract_variable_name},#{compiled_variable_name}.#{current_contract}.bin,#{input});") 97 | f.write("console.log('Compiled Object : #{compiled_variable_name}');") 98 | f.write("console.log('Contract : #{contract_variable_name}');") 99 | f.write("console.log('Contract Instance : #{contract_instance_variable_name}');") 100 | end 101 | 102 | File.open("temp/db/#{current_contract}.json", "w") do |f| 103 | f.write(JSON.pretty_generate compiled_object) 104 | end 105 | -------------------------------------------------------------------------------- /examples/bin/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | scripts="" 8 | 9 | if [ -z "$1" ] 10 | then 11 | echo "Testing all contracts on geth..." 12 | for file in `find ./gtests -name '*.js'` 13 | do 14 | scripts="${scripts};loadScript('$file');" 15 | done 16 | else 17 | file=$1 18 | file="$(tr '[:lower:]' '[:upper:]' <<< ${file:0:1})${file:1}" 19 | echo "Testing contract $file..." 20 | file+="_test.js" 21 | scripts="loadScript('gtests/$file');" 22 | fi 23 | 24 | $geth --exec "$scripts" attach ipc:data/geth.ipc 25 | 26 | echo "Done." 27 | -------------------------------------------------------------------------------- /examples/builds/Token_compiled.js: -------------------------------------------------------------------------------- 1 | function create(abiDefinition) { return web3.eth.contract(abiDefinition);}/*function deploy(account, value, gas, contract, code, input) */function deploy() { var account = arguments[0]; var value = arguments[1]; var gas = arguments[2]; var contract = arguments[3]; var code = arguments[4]; var codeString = "contract.new(inputMarker,{from:'accountMarker', value: valueMarker, data: 'codeMarker', gas: gasMarker}, function (e, contract) { if(!e) { if(!contract.address) { console.log(\"Contract transaction send: TransactionHash: \" + contract.transactionHash + \" waiting to be mined...\"); } else { console.log(\"Contract mined! Address: \" + contract.address); } } else { console.log(e) } })"; codeString = codeString.replace("accountMarker", account); codeString = codeString.replace("valueMarker", value); codeString = codeString.replace("codeMarker", code); codeString = codeString.replace("gasMarker", gas); input = "null"; if (arguments.length > 5) { if (arguments[5] != null) { var args = []; for (var i = 5;i < arguments.length; i++) { var val = arguments[i]; if (typeof(val) === 'string') { val = "\"" + val + "\""; } args.push(val); } input = args.join(","); } } codeString = codeString.replace("inputMarker", input); console.log(input); var instance = eval(codeString); return instance;}function watcher(error, result) { if (!error) { console.log("Result"); console.log(JSON.stringify(result)); return; } console.log("Error" + error);}/*function call(account, gas, func, input) */function call() { var account = "eth.accounts["+arguments[0]+"]"; var gas = arguments[1]; var func = arguments[2]; input = "null"; if (arguments.length > 3) { if (arguments[3] != null) { var args = Array.prototype.slice.call(arguments, 3); input = args.join(","); } } codeString = "func.sendTransaction(inputMarker, gasMarker, {from:accountMarker}, watcher);"; codeString = codeString.replace("accountMarker",account); codeString = codeString.replace("gasMarker",gas); codeString = codeString.replace("inputMarker",input); eval(codeString);}function send(from_index, to, value, gas){ return eth.sendTransaction({from:eth.accounts[from_index], to:to, value:web3.toWei(value,'ether'), gas:gas});}function bal() { for (var i = 0; i < eth.accounts.length; i++) { account = eth.accounts[i]; balance = web3.fromWei(eth.getBalance(eth.accounts[i]), 'ether'); console.log("Index : " + i); console.log("Account : "+ account); console.log("Balance : "+ balance); console.log("\n"); }}; 2 | var TokenCompiled = {"Token":{"abi":[{"constant":false,"inputs":[{"name":"account","type":"address"},{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Transfer","type":"event"}],"bin":"0x606060405260008054600160a060020a0319163317905561016f806100246000396000f3606060405260e060020a6000350463867904b48114610034578063a9059cbb1461005e578063f8b2cb4f14610092575b610002565b34610002576100bc600435602435600054600160a060020a0390811633909116146100be57610002565b34610002576100bc60043560243533600160a060020a03166000908152600160205260409020548190101561010f57610002565b3461000257600160a060020a03600435166000908152600160205260409020546060908152602090f35b005b600160a060020a03821660008181526001602052604090819020805484019055606091825260808390527fc65a3f767206d2fdcede0b094a4840e01c0dd0be1888b5ba800346eaa0123c1691a15050565b6040600081812080548490039055600160a060020a03808516808352929091208054840190553316606090815260809190915260a08290527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9080a1505056","devdoc":{"methods":{}},"userdoc":{"methods":{}}}};TokenContract = create(TokenCompiled.Token.abi);Token = deploy(eth.coinbase,0,400000,TokenContract,TokenCompiled.Token.bin,null);console.log('Compiled Object : TokenCompiled');console.log('Contract : TokenContract');console.log('Contract Instance : Token'); -------------------------------------------------------------------------------- /examples/contracts/Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | contract Token { 4 | address issuer; 5 | mapping (address => uint) balances; 6 | 7 | event Issue(address account, uint amount); 8 | event Transfer(address from, address to, uint amount); 9 | 10 | function Token() { 11 | issuer = msg.sender; 12 | } 13 | 14 | function issue(address account, uint amount) { 15 | if (msg.sender != issuer) throw; 16 | balances[account] += amount; 17 | Issue(account, amount); 18 | } 19 | 20 | function transfer(address to, uint amount) { 21 | if (balances[msg.sender] < amount) throw; 22 | 23 | balances[msg.sender] -= amount; 24 | balances[to] += amount; 25 | 26 | Transfer(msg.sender, to, amount); 27 | } 28 | 29 | function getBalance(address account) constant returns (uint) { 30 | return balances[account]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/contracts/math.se: -------------------------------------------------------------------------------- 1 | def add(a, b): 2 | return a+b 3 | -------------------------------------------------------------------------------- /examples/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "nonce": "0x0000000000000042", 3 | "difficulty": "0x020000", 4 | "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", 5 | "coinbase": "0x0000000000000000000000000000000000000000", 6 | "timestamp": "0x00", 7 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 8 | "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", 9 | "gasLimit": "0x4c4b40" 10 | } 11 | -------------------------------------------------------------------------------- /examples/gtests/Token_test.js: -------------------------------------------------------------------------------- 1 | loadScript('temp/migrations/Token.js'); 2 | 3 | var balance = Token.getBalance.call(web3.eth.accounts[0], { from: web3.eth.accounts[0] }) 4 | 5 | console.log("balance is: ", balance); 6 | 7 | Token.issue.sendTransaction(web3.eth.accounts[0], 10000, { from: web3.eth.accounts[0] }, function(err, tx){ 8 | if(err){ 9 | console.log("issue error!"); 10 | } else { 11 | console.log("issue success. tx: ", tx); 12 | } 13 | }) 14 | 15 | miner.start();admin.sleepBlocks(2);miner.stop(); 16 | 17 | balance = Token.getBalance.call(web3.eth.accounts[0], { from: web3.eth.accounts[0] }) 18 | 19 | console.log("balance is: ", balance); 20 | -------------------------------------------------------------------------------- /examples/private_keys/3ae88fe370c39384fc16da2c9e768cf5d2495b48.key: -------------------------------------------------------------------------------- 1 | 095e53c9c20e23fd01eaad953c01da9e9d3ed9bebcfed8e5b2c2fce94037d963 -------------------------------------------------------------------------------- /examples/private_keys/81063419f13cab5ac090cd8329d8fff9feead4a0.key: -------------------------------------------------------------------------------- 1 | 5bc505a123a695176a9688ffe22798cfd40424c5b91c818e985574ea8ebda167 -------------------------------------------------------------------------------- /examples/private_keys/9da26fc2e1d6ad9fdd46138906b0104ae68a65d8.key: -------------------------------------------------------------------------------- 1 | b6a03207128827eaae0d31d97a7a6243de31f2baf99eabd764e33389ecf436fc -------------------------------------------------------------------------------- /examples/private_keys/import.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | 3 | set key [lindex $argv 0]; 4 | set geth [lindex $argv 1]; 5 | 6 | spawn $geth --datadir data account import $key 7 | expect "Passphrase:" 8 | send "123456\r" 9 | expect "Repeat Passphrase:" 10 | send "123456\r" 11 | interact 12 | -------------------------------------------------------------------------------- /examples/rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new do |t| 4 | t.libs += %w(lib test) 5 | t.test_files = FileList['tests/**/*_test.rb'] 6 | t.verbose = true 7 | end 8 | 9 | task default: [:test] 10 | -------------------------------------------------------------------------------- /examples/temp/db/Token.json: -------------------------------------------------------------------------------- 1 | { 2 | "Token": { 3 | "abi": [ 4 | { 5 | "constant": false, 6 | "inputs": [ 7 | { 8 | "name": "account", 9 | "type": "address" 10 | }, 11 | { 12 | "name": "amount", 13 | "type": "uint256" 14 | } 15 | ], 16 | "name": "issue", 17 | "outputs": [ 18 | 19 | ], 20 | "payable": false, 21 | "type": "function" 22 | }, 23 | { 24 | "constant": false, 25 | "inputs": [ 26 | { 27 | "name": "to", 28 | "type": "address" 29 | }, 30 | { 31 | "name": "amount", 32 | "type": "uint256" 33 | } 34 | ], 35 | "name": "transfer", 36 | "outputs": [ 37 | 38 | ], 39 | "payable": false, 40 | "type": "function" 41 | }, 42 | { 43 | "constant": true, 44 | "inputs": [ 45 | { 46 | "name": "account", 47 | "type": "address" 48 | } 49 | ], 50 | "name": "getBalance", 51 | "outputs": [ 52 | { 53 | "name": "", 54 | "type": "uint256" 55 | } 56 | ], 57 | "payable": false, 58 | "type": "function" 59 | }, 60 | { 61 | "inputs": [ 62 | 63 | ], 64 | "type": "constructor" 65 | }, 66 | { 67 | "anonymous": false, 68 | "inputs": [ 69 | { 70 | "indexed": false, 71 | "name": "account", 72 | "type": "address" 73 | }, 74 | { 75 | "indexed": false, 76 | "name": "amount", 77 | "type": "uint256" 78 | } 79 | ], 80 | "name": "Issue", 81 | "type": "event" 82 | }, 83 | { 84 | "anonymous": false, 85 | "inputs": [ 86 | { 87 | "indexed": false, 88 | "name": "from", 89 | "type": "address" 90 | }, 91 | { 92 | "indexed": false, 93 | "name": "to", 94 | "type": "address" 95 | }, 96 | { 97 | "indexed": false, 98 | "name": "amount", 99 | "type": "uint256" 100 | } 101 | ], 102 | "name": "Transfer", 103 | "type": "event" 104 | } 105 | ], 106 | "bin": "0x606060405260008054600160a060020a0319163317905561016f806100246000396000f3606060405260e060020a6000350463867904b48114610034578063a9059cbb1461005e578063f8b2cb4f14610092575b610002565b34610002576100bc600435602435600054600160a060020a0390811633909116146100be57610002565b34610002576100bc60043560243533600160a060020a03166000908152600160205260409020548190101561010f57610002565b3461000257600160a060020a03600435166000908152600160205260409020546060908152602090f35b005b600160a060020a03821660008181526001602052604090819020805484019055606091825260808390527fc65a3f767206d2fdcede0b094a4840e01c0dd0be1888b5ba800346eaa0123c1691a15050565b6040600081812080548490039055600160a060020a03808516808352929091208054840190553316606090815260809190915260a08290527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9080a1505056", 107 | "devdoc": { 108 | "methods": { 109 | } 110 | }, 111 | "userdoc": { 112 | "methods": { 113 | } 114 | } 115 | }, 116 | "address": "0x80d29fb7f81d2ccd77c708b6135389c9c08653dc" 117 | } -------------------------------------------------------------------------------- /examples/temp/migrations/Token.js: -------------------------------------------------------------------------------- 1 | var TokenContract = web3.eth.contract([{"constant":false, "inputs":[{"name":"account", "type":"address"}, {"name":"amount", "type":"uint256"}], "name":"issue", "outputs":[], "payable":false, "type":"function"}, {"constant":false, "inputs":[{"name":"to", "type":"address"}, {"name":"amount", "type":"uint256"}], "name":"transfer", "outputs":[], "payable":false, "type":"function"}, {"constant":true, "inputs":[{"name":"account", "type":"address"}], "name":"getBalance", "outputs":[{"name":"", "type":"uint256"}], "payable":false, "type":"function"}, {"inputs":[], "type":"constructor"}, {"anonymous":false, "inputs":[{"indexed":false, "name":"account", "type":"address"}, {"indexed":false, "name":"amount", "type":"uint256"}], "name":"Issue", "type":"event"}, {"anonymous":false, "inputs":[{"indexed":false, "name":"from", "type":"address"}, {"indexed":false, "name":"to", "type":"address"}, {"indexed":false, "name":"amount", "type":"uint256"}], "name":"Transfer", "type":"event"}]); 2 | 3 | var Token = TokenContract.at('0x80d29fb7f81d2ccd77c708b6135389c9c08653dc'); 4 | -------------------------------------------------------------------------------- /examples/tests/math_test.rb: -------------------------------------------------------------------------------- 1 | require 'teth/minitest' 2 | 3 | class MathTest < Teth::Minitest 4 | def test_add 5 | assert_equal 3, contract.add(1, 2) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /examples/tests/token_test.rb: -------------------------------------------------------------------------------- 1 | require 'teth/minitest' 2 | 3 | class TokenTest < Teth::Minitest 4 | def test_contract_deployed 5 | assert_equal false, contract.address.nil? 6 | end 7 | 8 | def test_issue_balance 9 | assert_equal 0, contract.getBalance(bob) 10 | contract.issue bob, 100 11 | assert_equal 100, contract.getBalance(bob) 12 | end 13 | 14 | def test_issue_exception 15 | assert_raises(TransactionFailed) do 16 | contract.issue bob, 100, sender: eve_privkey 17 | end 18 | assert_equal 0, contract.getBalance(bob) 19 | end 20 | 21 | def test_token_transfer 22 | contract.issue bob, 100 23 | contract.transfer carol, 90, sender: bob_privkey 24 | assert_equal 90, contract.getBalance(carol) 25 | 26 | assert_raises(TransactionFailed) { contract.transfer carol, 90, sender: bob_privkey } 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/teth/configurable.rb: -------------------------------------------------------------------------------- 1 | module Teth 2 | module Configurable 3 | def option(name, default=nil) 4 | singleton_class.send(:define_method, name) do |*args| 5 | if args.empty? 6 | v = instance_variable_get("@#{name}") 7 | return default if v.nil? 8 | v 9 | else 10 | instance_variable_set("@#{name}", args[0]) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/teth/erbs/Gemfile: -------------------------------------------------------------------------------- 1 | gem 'teth', '>= 0.2.0' 2 | -------------------------------------------------------------------------------- /lib/teth/erbs/contract.sol: -------------------------------------------------------------------------------- 1 | contract <%= name.capitalize %> { 2 | } 3 | -------------------------------------------------------------------------------- /lib/teth/erbs/contract_test.rb: -------------------------------------------------------------------------------- 1 | require 'teth/minitest' 2 | 3 | class <%=name.capitalize%>Test < Teth::Minitest 4 | 5 | def test_something 6 | assert !contract.address.nil? 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /lib/teth/erbs/migration: -------------------------------------------------------------------------------- 1 | var <%= contract %>Contract = web3.eth.contract(<%= abi %>); 2 | 3 | var <%= contract %> = <%=contract%>Contract.at('<%= address %>'); 4 | -------------------------------------------------------------------------------- /lib/teth/minitest.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'ethereum' 3 | 4 | require 'teth/configurable' 5 | 6 | module Teth 7 | class Minitest < ::Minitest::Test 8 | extend Configurable 9 | include Ethereum 10 | 11 | option :contract_dir_name, 'contracts' 12 | option :account_num, 10 13 | option :print_events, false 14 | option :print_logs, true 15 | 16 | def setup 17 | path = find_contract_source 18 | raise "Cannot find corresponding contract source" unless path 19 | setup_contract(path) 20 | end 21 | 22 | def teardown 23 | @state = nil 24 | @account = nil 25 | @contract = nil 26 | end 27 | 28 | def setup_contract(path) 29 | case path 30 | when /\.sol\z/ 31 | type = :solidity 32 | when /\.se\z/ 33 | type = :serpent 34 | else 35 | raise "Unknown contract source type: #{path}" 36 | end 37 | 38 | code = File.read path 39 | log_listener = ->(log) do 40 | if log.instance_of?(Log) # unrecognized event 41 | if self.class.print_logs 42 | topics = log.topics.map {|t| heuristic_prettify Utils.int_to_big_endian(t) } 43 | data = heuristic_prettify(log.data) 44 | puts "[Log] #{Utils.encode_hex(log.address)} >>> topics=#{topics} data=#{data.inspect}" 45 | end 46 | else # user defined event 47 | if self.class.print_logs && self.class.print_events 48 | from = log.delete '_from' 49 | name = log.delete '_event_type' 50 | s = log.keys.map {|k| "#{k}=#{log[k]}" }.join(' ') 51 | puts "[Event] #{from} #{name} >>> #{s}" 52 | end 53 | end 54 | end 55 | @contract = state.abi_contract code, 56 | language: type, sender: privkey, log_listener: log_listener 57 | end 58 | 59 | def heuristic_prettify(bytes) 60 | dry_bytes = bytes.gsub(/\A(\x00)+/, '') 61 | dry_bytes = dry_bytes.gsub(/(\x00)+\z/, '') 62 | if (bytes.size - dry_bytes.size) > 3 63 | # there's many ZERO bytes in the head or tail of bytes, it must be padded 64 | if dry_bytes.size == 20 # address 65 | Utils.encode_hex(dry_bytes) 66 | else 67 | dry_bytes 68 | end 69 | else 70 | Utils.encode_hex(bytes) 71 | end 72 | end 73 | 74 | def contract 75 | @contract 76 | end 77 | 78 | def find_contract_source 79 | name = self.class.name[0...-4] 80 | dir = find_contracts_directory 81 | find_source(dir, name, 'sol') || find_source(dir, name, 'se') 82 | end 83 | 84 | def find_contracts_directory 85 | last = nil 86 | cur = ENV['PWD'] 87 | while cur != last 88 | path = File.join cur, self.class.contract_dir_name 89 | return path if File.directory?(path) 90 | last = cur 91 | cur = File.dirname cur 92 | end 93 | nil 94 | end 95 | 96 | def find_source(dir, name, ext) 97 | name = "#{name}.#{ext}" 98 | list = Dir.glob File.join(dir, "**/*.#{ext}") 99 | list.find {|fn| File.basename(fn) =~ /\A#{name}\z/i } 100 | end 101 | 102 | ## 103 | # Helpers 104 | # 105 | 106 | def transfer(to, value, from: privkey) 107 | state.send_tx from, to, value 108 | end 109 | 110 | ## 111 | # Fixtures 112 | # 113 | 114 | @@privkeys = account_num.times.map do |i| 115 | Utils.keccak256 rand(Constant::TT256).to_s 116 | end 117 | 118 | def privkey 119 | privkeys[0] 120 | end 121 | 122 | def pubkey 123 | pubkeys[0] 124 | end 125 | 126 | def address 127 | addresses[0] 128 | end 129 | 130 | def privkeys 131 | @privkeys ||= @@privkeys 132 | end 133 | 134 | def pubkeys 135 | @pubkeys ||= privkeys.map {|k| PrivateKey.new(k).to_pubkey } 136 | end 137 | 138 | def addresses 139 | @addresses ||= privkeys.map {|k| PrivateKey.new(k).to_address } 140 | end 141 | 142 | %w(alice bob carol chuck dave eve mallet oscar sybil).each_with_index do |n, i| 143 | class_eval <<-METHOD 144 | def #{n} 145 | addresses[#{i}] 146 | end 147 | def #{n}_pubkey 148 | pubkeys[#{i}] 149 | end 150 | def #{n}_privkey 151 | privkeys[#{i}] 152 | end 153 | METHOD 154 | end 155 | 156 | def state 157 | @state ||= Tester::State.new privkeys: privkeys 158 | end 159 | 160 | def head 161 | state.head 162 | end 163 | 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /lib/teth/templates/bin/attach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | echo "Starting geth attach..." 8 | 9 | $geth attach ipc:data/geth.ipc 10 | -------------------------------------------------------------------------------- /lib/teth/templates/bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -z "$1" ] 3 | then 4 | echo "Building all contracts ..." 5 | for sol in `find ./contracts -name '*.sol'` 6 | do 7 | echo "Building contract ${sol}" 8 | let len=${#sol}-16 9 | jsfile="${sol:12:len}_compiled.js" 10 | ./bin/solc_helper.rb ${sol} $jsfile 11 | mv $jsfile builds/ 12 | done 13 | echo "Done." 14 | else 15 | sol=$1 16 | sol="$(tr '[:lower:]' '[:upper:]' <<< ${sol:0:1})${sol:1}" 17 | echo "Building contract ${sol}" 18 | 19 | file="contracts/${sol}.sol" 20 | len=${#sol}-16 21 | jsfile="${sol}_compiled.js" 22 | ./bin/solc_helper.rb $file $jsfile 23 | mv $jsfile builds/ 24 | echo "Done." 25 | fi 26 | -------------------------------------------------------------------------------- /lib/teth/templates/bin/import_keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | echo "***** Import all pre-funded private keys" 8 | 9 | for key in `find ./private_keys -name '*.key'` 10 | do 11 | echo "Notice: No need to input your password. The default password is 123456" 12 | ./private_keys/import.sh $key $geth 13 | done 14 | 15 | echo "***** Done." 16 | -------------------------------------------------------------------------------- /lib/teth/templates/bin/init.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | $geth --datadir `pwd`/data init genesis.json 8 | -------------------------------------------------------------------------------- /lib/teth/templates/bin/migrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | if [ -z "$1" ] 8 | then 9 | scripts="" 10 | for file in `find ./builds -name '*compiled.js'` 11 | do 12 | scripts="${scripts};loadScript('$file')" 13 | done 14 | scripts="${scripts};miner.start();admin.sleepBlocks(2);miner.stop()" 15 | $geth --exec "$scripts" attach ipc:data/geth.ipc 16 | else 17 | file=$1 18 | file="$(tr '[:lower:]' '[:upper:]' <<< ${file:0:1})${file:1}" 19 | file+="_compiled.js" 20 | $geth --exec "loadScript('builds/$file');miner.start();admin.sleepBlocks(2);miner.stop()" attach ipc:data/geth.ipc 21 | fi 22 | 23 | echo "Done." 24 | -------------------------------------------------------------------------------- /lib/teth/templates/bin/private_blockchain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | geth=${GETH:-geth} 3 | 4 | echo "***** Using geth at: $geth" 5 | 6 | echo "Start geth server..." 7 | 8 | $geth --datadir data --networkid 31415926 --rpc --rpccorsdomain "*" --nodiscover --unlock 3ae88fe370c39384fc16da2c9e768cf5d2495b48 --password <(echo -n 123456) 9 | -------------------------------------------------------------------------------- /lib/teth/templates/bin/solc_helper.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | def library_code 5 | 'function create(abiDefinition) { return web3.eth.contract(abiDefinition);}/*function deploy(account, value, gas, contract, code, input) */function deploy() { var account = arguments[0]; var value = arguments[1]; var gas = arguments[2]; var contract = arguments[3]; var code = arguments[4]; var codeString = "contract.new(inputMarker,{from:\'accountMarker\', value: valueMarker, data: \'codeMarker\', gas: gasMarker}, function (e, contract) { if(!e) { if(!contract.address) { console.log(\"Contract transaction send: TransactionHash: \" + contract.transactionHash + \" waiting to be mined...\"); } else { console.log(\"Contract mined! Address: \" + contract.address); } } else { console.log(e) } })"; codeString = codeString.replace("accountMarker", account); codeString = codeString.replace("valueMarker", value); codeString = codeString.replace("codeMarker", code); codeString = codeString.replace("gasMarker", gas); input = "null"; if (arguments.length > 5) { if (arguments[5] != null) { var args = []; for (var i = 5;i < arguments.length; i++) { var val = arguments[i]; if (typeof(val) === \'string\') { val = "\"" + val + "\""; } args.push(val); } input = args.join(","); } } codeString = codeString.replace("inputMarker", input); console.log(input); var instance = eval(codeString); return instance;}function watcher(error, result) { if (!error) { console.log("Result"); console.log(JSON.stringify(result)); return; } console.log("Error" + error);}/*function call(account, gas, func, input) */function call() { var account = "eth.accounts["+arguments[0]+"]"; var gas = arguments[1]; var func = arguments[2]; input = "null"; if (arguments.length > 3) { if (arguments[3] != null) { var args = Array.prototype.slice.call(arguments, 3); input = args.join(","); } } codeString = "func.sendTransaction(inputMarker, gasMarker, {from:accountMarker}, watcher);"; codeString = codeString.replace("accountMarker",account); codeString = codeString.replace("gasMarker",gas); codeString = codeString.replace("inputMarker",input); eval(codeString);}function send(from_index, to, value, gas){ return eth.sendTransaction({from:eth.accounts[from_index], to:to, value:web3.toWei(value,\'ether\'), gas:gas});}function bal() { for (var i = 0; i < eth.accounts.length; i++) { account = eth.accounts[i]; balance = web3.fromWei(eth.getBalance(eth.accounts[i]), \'ether\'); console.log("Index : " + i); console.log("Account : "+ account); console.log("Balance : "+ balance); console.log("\n"); }}' 6 | end 7 | 8 | def compile_solidity(file) 9 | json_string = `solc --add-std --optimize --combined-json abi,bin,userdoc,devdoc #{file}` 10 | json_string = json_string.gsub("\\n","") 11 | begin 12 | json_object = JSON.parse(json_string) 13 | throw if json_object.nil? 14 | puts `solc --optimize --gas #{file}` 15 | puts "\n\n" 16 | puts "-------------------------------------" 17 | json_object["contracts"] 18 | rescue 19 | puts "Failed to Compile." 20 | abort 21 | end 22 | end 23 | 24 | def process_code(contracts) 25 | contracts.keys.each.with_index do |key, i| 26 | contracts[key]["bin"] = "0x" + contracts[key]["bin"] 27 | contracts[key]["abi"] = JSON.parse(contracts[key]["abi"]) 28 | contracts[key]["devdoc"] = JSON.parse(contracts[key]["devdoc"]) 29 | contracts[key]["userdoc"] = JSON.parse(contracts[key]["userdoc"]) 30 | end 31 | return contracts 32 | end 33 | 34 | 35 | def javascript_file_name(file_name) 36 | file_name = file_name.split('/')[-1] 37 | file_name.split('.')[0] + '_compiled.js' 38 | end 39 | 40 | def get_contract_to_deploy(compiled_object) 41 | return compiled_object.keys[0] if compiled_object.keys.count == 1 42 | puts "Which contract do you want to deploy?" 43 | choice = 0 44 | while choice <= 0 || choice > compiled_object.keys.count 45 | compiled_object.keys.each.with_index do |key, i| 46 | puts "#{(i+1)}. "+key 47 | end 48 | choice = $stdin.gets.to_i 49 | end 50 | return compiled_object.keys[choice - 1] 51 | end 52 | 53 | def get_input 54 | puts "Enter Input: " 55 | input = $stdin.gets 56 | input.strip.chomp == "" ? "null" : input 57 | end 58 | 59 | def get_gas 60 | gas = 0 61 | while gas == 0 62 | puts "Enter Gas: " 63 | gas = $stdin.gets.to_i 64 | end 65 | gas 66 | end 67 | 68 | def get_value 69 | gas = -1 70 | while gas < 0 71 | puts "Enter Value To Be Transferred: " 72 | gas = $stdin.gets.to_i 73 | end 74 | gas 75 | end 76 | 77 | file_name = ARGV[0] 78 | 79 | compiled_object = compile_solidity(file_name) 80 | compiled_object = process_code(compiled_object) 81 | javascript_file_name = javascript_file_name(file_name) 82 | 83 | current_contract = get_contract_to_deploy(compiled_object) 84 | 85 | compiled_variable_name = "#{current_contract}Compiled" 86 | contract_variable_name = "#{current_contract}Contract" 87 | contract_instance_variable_name = "#{current_contract}" 88 | 89 | gas = get_gas 90 | value = get_value 91 | input = get_input 92 | 93 | File.open(javascript_file_name, 'w') do |f| 94 | f.write("#{library_code};\nvar #{compiled_variable_name} = #{compiled_object.to_json};") 95 | f.write("#{contract_variable_name} = create(#{compiled_variable_name}.#{current_contract}.abi);") 96 | f.write("#{contract_instance_variable_name} = deploy(eth.coinbase,#{value},#{gas},#{contract_variable_name},#{compiled_variable_name}.#{current_contract}.bin,#{input});") 97 | f.write("console.log('Compiled Object : #{compiled_variable_name}');") 98 | f.write("console.log('Contract : #{contract_variable_name}');") 99 | f.write("console.log('Contract Instance : #{contract_instance_variable_name}');") 100 | end 101 | 102 | File.open("temp/db/#{current_contract}.json", "w") do |f| 103 | f.write(JSON.pretty_generate compiled_object) 104 | end 105 | -------------------------------------------------------------------------------- /lib/teth/templates/bin/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | echo "***** Using geth at: $geth" 6 | 7 | scripts="" 8 | 9 | if [ -z "$1" ] 10 | then 11 | echo "Testing all contracts on geth..." 12 | for file in `find ./gtests -name '*.js'` 13 | do 14 | scripts="${scripts};loadScript('$file');" 15 | done 16 | else 17 | file=$1 18 | file="$(tr '[:lower:]' '[:upper:]' <<< ${file:0:1})${file:1}" 19 | echo "Testing contract $file..." 20 | file+="_test.js" 21 | scripts="loadScript('gtests/$file');" 22 | fi 23 | 24 | $geth --exec "$scripts" attach ipc:data/geth.ipc 25 | 26 | echo "Done." 27 | -------------------------------------------------------------------------------- /lib/teth/templates/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "nonce": "0x0000000000000042", 3 | "difficulty": "0x020000", 4 | "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", 5 | "coinbase": "0x0000000000000000000000000000000000000000", 6 | "timestamp": "0x00", 7 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 8 | "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", 9 | "gasLimit": "0x4c4b40" 10 | } 11 | -------------------------------------------------------------------------------- /lib/teth/templates/gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | data 3 | temp 4 | -------------------------------------------------------------------------------- /lib/teth/templates/private_keys/import.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | 3 | set key [lindex $argv 0]; 4 | set geth [lindex $argv 1]; 5 | 6 | spawn $geth --datadir data account import $key 7 | expect "Passphrase:" 8 | send "123456\r" 9 | expect "Repeat passphrase:" 10 | send "123456\r" 11 | interact 12 | -------------------------------------------------------------------------------- /lib/teth/templates/rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new do |t| 4 | t.libs += %w(lib test) 5 | t.test_files = FileList['tests/**/*_test.rb'] 6 | t.verbose = true 7 | end 8 | 9 | task default: [:test] 10 | -------------------------------------------------------------------------------- /teth.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "teth" 3 | s.version = "0.2.2" 4 | s.authors = ["Zhang YaNing", "Jan Xie"] 5 | s.email = ["zhangyaning1985@gmail.com", "jan.h.xie@gmail.com"] 6 | s.homepage = "https://github.com/cryptape/teth" 7 | s.summary = "Testing and deployment framework for Ethereum smart contracts." 8 | s.description = "Teth is a Ethereum smart contract test framework in ruby. It provides two testing environments: testing in ruby EVM and testing in geth. You don't need to understand ruby grammar, just enjoy syntactic sugar." 9 | s.license = "MIT" 10 | 11 | s.files = Dir["{lib}/**/*"] + ["LICENSE", "README.md"] 12 | 13 | s.add_dependency('ruby-ethereum', '>= 0.9.6') 14 | s.add_dependency('serpent', '>= 0.3.0') 15 | s.add_dependency('minitest', '~> 5.8') 16 | s.executables << 'teth' 17 | end 18 | --------------------------------------------------------------------------------