ether-fly 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -0
  3. data/bin/ether-fly +309 -20
  4. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 265d020e75eee2fd6b4cf9f5a46cd6da896d1f31
4
- data.tar.gz: 4bfdfa91dfd05eb816dea169f0d3bba2f8d28ecd
3
+ metadata.gz: 3df64de010310a17c6c11e09a1a2136cf3e84fca
4
+ data.tar.gz: 78f72289d962e0347d334982757718dbbae7fe05
5
5
  SHA512:
6
- metadata.gz: bcf213765e38178af13a7fd015e8da963cee22a3299a289016b88b46ff011a6a88022cfa43e1237d7673179eb93affd4ff3c018fe85c945f4c7c94f453405673
7
- data.tar.gz: 35bc1001e740579782fe76be69f127d64ee083e48b89f8d645e21d6053585b58c8e8565586e138b7231a91705b68b5d9c74e5946c82f11ec3262c3cc7d48dec9
6
+ metadata.gz: 22bb02684234b2b97e0b5ab24670e7ffe9bacd49501b3ce3ae2e7572dab5bc91242d607fe62790897e27b15ce6975792f8b99c8af44f7e2ae44ece8392fe0a7f
7
+ data.tar.gz: 3e00e49cce5219cd77c88e5a6385c0440b0c406f1715acd76dbe4971449f704f87525822020439f953a380d1cfa539241f871ba3102a9bfff0c6606be06e4d21
data/README.md CHANGED
@@ -1,7 +1,11 @@
1
+ ## Introduction
2
+ EtherFly 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
+
1
4
  ## Install
2
5
  ```shell
3
6
  bundle install ether-fly
4
7
  ```
8
+
5
9
  ## How to use
6
10
 
7
11
  Help command:
@@ -21,3 +25,48 @@ Run tests
21
25
  ```
22
26
  $ ether-fly t game
23
27
  ```
28
+
29
+ ## Unit tests
30
+ You can wirte fast, simple tests.
31
+ ```ruby
32
+ class TokenTest < Minitest::Test
33
+ include Ethereum
34
+
35
+ def setup
36
+ @state = Tester::State.new
37
+ @solidity_code = File.read('./contracts/Token.sol')
38
+ @c = @state.abi_contract @solidity_code, language: :solidity
39
+ end
40
+
41
+ def test_issue_balance
42
+ assert_equal 0, @c.getBalance(Tester::Fixture.accounts[2])
43
+ @c.issue Tester::Fixture.accounts[2], 100
44
+ assert_equal 100, @c.getBalance(Tester::Fixture.accounts[2])
45
+ end
46
+
47
+ def test_issue_exception
48
+ assert_raises(TransactionFailed) { @c.issue Tester::Fixture.accounts[3], 100, sender: Tester::Fixture.keys[4] }
49
+ assert_equal 0, @c.getBalance(Tester::Fixture.accounts[3])
50
+ end
51
+
52
+ def test_token_transfer
53
+ @c.issue Tester::Fixture.accounts[2], 100
54
+ @c.transfer Tester::Fixture.accounts[3], 90, sender: Tester::Fixture.keys[2]
55
+ assert_equal 90, @c.getBalance(Tester::Fixture.accounts[3])
56
+
57
+ assert_raises(TransactionFailed) { @c.transfer Tester::Fixture.accounts[3], 90, sender: Tester::Fixture.keys[2] }
58
+ end
59
+ end
60
+
61
+ ```
62
+ More details:
63
+ https://github.com/seattlerb/minitest
64
+
65
+ ## TODO
66
+ - Extract test file require and others
67
+ - Save migrate address
68
+ - Migrate ARGV
69
+ - Script for preload
70
+ - Easy way to load contract on chain
71
+ - Easy way to test on chain
72
+ - ES6
@@ -1,13 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'erb'
4
+ require 'json'
4
5
 
5
6
  ARGV << "--help" if ARGV.empty?
6
7
 
7
8
  aliases = {
9
+ "n" => "new",
8
10
  "g" => "generate",
9
11
  "t" => "test",
10
- "n" => "new"
12
+ "i" => "init",
13
+ "ik" => "import_keys",
14
+ "b" => "build",
15
+ "m" => "migrate",
16
+ "s" => "server",
17
+ "c" => "console",
18
+ "gt" => "geth_test"
11
19
  }
12
20
 
13
21
  command = ARGV.shift
@@ -16,14 +24,21 @@ command = aliases[command] || command
16
24
  HELP_MESSAGE = <<-EOF
17
25
  Usage: eth-fly COMMAND [ARGS]
18
26
  The most common eth-fly commands are:
19
- generate Generate new Smart Contract and test file (short-cut alias: "g")
20
- test Run tests (short-cut alias: "t")
21
- new Create a new Smart Contract application. "eth-fly new my_app" creates a
22
- new application called MyApp in "./my_app"
27
+ new Create a new Smart Contract application. "eth-fly new my_app" creates a
28
+ new application called MyApp in "./my_app" (short-cut alias: "n")
29
+ generate Generate new Smart Contract and test file (short-cut alias: "g")
30
+ test Run tests (short-cut alias: "t")
31
+ init Init private geth chain (short-cut alias: "i")
32
+ import_keys Import keys to chain (short-cut alias: "ik")
33
+ build Build contract (short-cut alias: "b")
34
+ migrate Deploy contract on chain (short-cut alias: "m")
35
+ server Start chain (short-cut alias: "s")
36
+ console Open Chain console (short-cut alias: "c")
37
+ geth_test Test on chain (short-cut alias: "gt")
23
38
  All commands can be run with -h (or --help) for more information.
24
39
  EOF
25
40
 
26
- COMMAND_WHITELIST = %w(generate new help test)
41
+ COMMAND_WHITELIST = %w(new generate test init import_keys build migrate server console geth_test)
27
42
 
28
43
  CONTRACT_TEMPLATE = ERB.new <<-EOF
29
44
  contract <%= name.capitalize %> {
@@ -50,24 +65,257 @@ GEMFILE_TEMPLATE = ERB.new <<-EOF
50
65
  gem 'ether-fly', '>= 0.0.1'
51
66
  EOF
52
67
 
53
- def run_command!(command)
54
- command = parse_command(command)
68
+ GENESIS_TEMPLATE = <<-EOF
69
+ {
70
+ "nonce": "0x0000000000000042",
71
+ "difficulty": "0x020000",
72
+ "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
73
+ "coinbase": "0x0000000000000000000000000000000000000000",
74
+ "timestamp": "0x00",
75
+ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
76
+ "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
77
+ "gasLimit": "0x4c4b40"
78
+ }
79
+ EOF
55
80
 
56
- if COMMAND_WHITELIST.include?(command)
57
- send(command)
58
- else
59
- help
81
+ KEYS_TEMPLATE = {
82
+ "3ae88fe370c39384fc16da2c9e768cf5d2495b48" => "095e53c9c20e23fd01eaad953c01da9e9d3ed9bebcfed8e5b2c2fce94037d963",
83
+ "81063419f13cab5ac090cd8329d8fff9feead4a0" => "5bc505a123a695176a9688ffe22798cfd40424c5b91c818e985574ea8ebda167",
84
+ "9da26fc2e1d6ad9fdd46138906b0104ae68a65d8" => "b6a03207128827eaae0d31d97a7a6243de31f2baf99eabd764e33389ecf436fc",
85
+ "bd2d69e3e68e1ab3944a865b3e566ca5c48740da" => "b35b8064c5c373629a05cc3ef39789ba4dacd404e6e864214ade934c198b636f",
86
+ "ca9f427df31a1f5862968fad1fe98c0a9ee068c4" => "9e35c48588711469e13c9a594f9f6d81491ce44ff1e8c5d968fcbd17168088a4"
87
+ }
88
+
89
+ SOLC_HELPER_TEMPLATE = %q{#!/usr/bin/env ruby
90
+
91
+ require 'json'
92
+ def library_code
93
+ '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"); }}'
94
+ end
95
+
96
+ def compile_solidity(file)
97
+ json_string = `solc --add-std --optimize --combined-json abi,bin,userdoc,devdoc #{file}`
98
+ json_string = json_string.gsub("\\n","")
99
+ begin
100
+ json_object = JSON.parse(json_string)
101
+ throw if json_object.nil?
102
+ puts `solc --optimize --gas #{file}`
103
+ puts "\n\n"
104
+ puts "-------------------------------------"
105
+ json_object["contracts"]
106
+ rescue
107
+ puts "Failed to Compile."
108
+ abort
109
+ end
110
+ end
111
+
112
+ def process_code(contracts)
113
+ contracts.keys.each.with_index do |key, i|
114
+ contracts[key]["bin"] = "0x" + contracts[key]["bin"]
115
+ contracts[key]["abi"] = JSON.parse(contracts[key]["abi"])
116
+ contracts[key]["devdoc"] = JSON.parse(contracts[key]["devdoc"])
117
+ contracts[key]["userdoc"] = JSON.parse(contracts[key]["userdoc"])
118
+ end
119
+ return contracts
120
+ end
121
+
122
+
123
+ def javascript_file_name(file_name)
124
+ file_name = file_name.split('/')[-1]
125
+ file_name.split('.')[0] + '_compiled.js'
126
+ end
127
+
128
+ def get_contract_to_deploy(compiled_object)
129
+ return compiled_object.keys[0] if compiled_object.keys.count == 1
130
+ puts "Which contract do you want to deploy?"
131
+ choice = 0
132
+ while choice <= 0 || choice > compiled_object.keys.count
133
+ compiled_object.keys.each.with_index do |key, i|
134
+ puts "#{(i+1)}. "+key
135
+ end
136
+ choice = $stdin.gets.to_i
60
137
  end
138
+ return compiled_object.keys[choice - 1]
61
139
  end
62
140
 
141
+ def get_input
142
+ puts "Enter Input: "
143
+ input = $stdin.gets
144
+ input.strip.chomp == "" ? "null" : input
145
+ end
146
+
147
+ def get_gas
148
+ gas = 0
149
+ while gas == 0
150
+ puts "Enter Gas: "
151
+ gas = $stdin.gets.to_i
152
+ end
153
+ gas
154
+ end
155
+
156
+ def get_value
157
+ gas = -1
158
+ while gas < 0
159
+ puts "Enter Value To Be Transferred: "
160
+ gas = $stdin.gets.to_i
161
+ end
162
+ gas
163
+ end
164
+
165
+ file_name = ARGV[0]
166
+
167
+ compiled_object = compile_solidity(file_name)
168
+ compiled_object = process_code(compiled_object)
169
+ javascript_file_name = javascript_file_name(file_name)
170
+
171
+ current_contract = get_contract_to_deploy(compiled_object)
172
+
173
+ compiled_variable_name = "#{current_contract}Compiled"
174
+ contract_variable_name = "#{current_contract}Contract"
175
+ contract_instance_variable_name = "#{current_contract}"
176
+
177
+ gas = get_gas
178
+ value = get_value
179
+ input = get_input
180
+
181
+ File.open(javascript_file_name, 'w') do |f|
182
+ f.write("#{library_code};\nvar #{compiled_variable_name} = #{compiled_object.to_json};")
183
+ f.write("#{contract_variable_name} = create(#{compiled_variable_name}.#{current_contract}.abi);")
184
+ f.write("#{contract_instance_variable_name} = deploy(eth.coinbase,#{value},#{gas},#{contract_variable_name},#{compiled_variable_name}.#{current_contract}.bin,#{input});")
185
+ f.write("console.log('Compiled Object : #{compiled_variable_name}');")
186
+ f.write("console.log('Contract : #{contract_variable_name}');")
187
+ f.write("console.log('Contract Instance : #{contract_instance_variable_name}');")
188
+ end
189
+ }
190
+
191
+ ATTACH_SH = <<-EOF
192
+ #!/bin/bash
193
+
194
+ geth=${GETH:-geth}
195
+ $geth attach ipc:data/geth.ipc
196
+ EOF
197
+
198
+ BUILD_SH = <<-EOF
199
+ #!/bin/bash
200
+ for sol in `find ./contracts -name '*.sol'`
201
+ do
202
+ filename="${sol}"
203
+ echo $filename
204
+ let len=${#filename}-16
205
+ # echo $len
206
+ jsfile="${filename:12:len}_compiled.js"
207
+ echo $jsfile
208
+ ./bin/solc_helper.rb $sol $jsfile
209
+ mv $jsfile builds/
210
+ done
211
+ EOF
212
+
213
+ IMPORT_KEYS_SH = <<-EOF
214
+ #!/bin/sh
215
+
216
+ geth=${GETH:-geth}
217
+ echo "***** Using geth at: $geth"
218
+
219
+ echo "***** Import all pre-funded private keys"
220
+
221
+ for key in `find ./private_keys -name '*.key'`
222
+ do
223
+ ./private_keys/import.sh $key $geth
224
+ done
225
+
226
+ echo "***** Done."
227
+ EOF
228
+
229
+ INIT_SH = <<-EOF
230
+ #! /bin/bash
231
+ geth --datadir `pwd`/data init genesis.json
232
+ EOF
233
+
234
+ PRIVATE_BLOCKCHAIN_SH = <<-EOF
235
+ #!/bin/bash
236
+
237
+ geth=${GETH:-geth}
238
+
239
+ $geth --datadir data --networkid 31415926 --rpc --rpccorsdomain "*" --nodiscover --unlock 3ae88fe370c39384fc16da2c9e768cf5d2495b48 --password <(echo -n 123456)
240
+ EOF
241
+
242
+ TEST_SH = <<-EOF
243
+ #!/bin/bash
244
+
245
+ geth=${GETH:-geth}
246
+
247
+ scripts=""
248
+
249
+ for file in `find ./builds -name '*compiled.js'`
250
+ do
251
+ scripts="${scripts};loadScript('$file')"
252
+ done
253
+
254
+ for file in `find ./test -name '*.js'`
255
+ do
256
+ scripts="${scripts};loadScript('$file');"
257
+ done
258
+
259
+ echo $scripts
260
+ $geth --datadir data --networkid 31415926 --rpc --rpccorsdomain "*" --nodiscover --unlock 3ae88fe370c39384fc16da2c9e768cf5d2495b48 --password <(echo -n 123456) --exec "$scripts" console 2>> ./logfile
261
+ EOF
262
+
263
+ IMPORT_SH = <<-EOF
264
+ #!/usr/bin/expect -f
265
+
266
+ set key [lindex $argv 0];
267
+ set geth [lindex $argv 1];
268
+
269
+ spawn $geth --datadir data account import $key
270
+ expect "Passphrase:"
271
+ send "123456\r"
272
+ expect "Repeat Passphrase:"
273
+ send "123456\r"
274
+ interact
275
+ EOF
276
+
277
+ RAKE_FILE = <<-EOF
278
+ require 'rake/testtask'
279
+
280
+ Rake::TestTask.new do |t|
281
+ t.libs += %w(lib test)
282
+ t.test_files = FileList['tests/**/*_test.rb']
283
+ t.verbose = true
284
+ end
285
+
286
+ task default: [:test]
287
+ EOF
288
+
63
289
  def new
64
290
  name = ARGV.shift
65
291
  if name
66
292
  puts "Creating project #{name}..."
67
- system("mkdir #{name}")
293
+ system("mkdir #{name} && cd #{name} && mkdir private_keys && mkdir builds && mkdir contracts && mkdir bin && mkdir tests")
68
294
  gemfile = GEMFILE_TEMPLATE.result(binding)
69
- File.open("#{name}/Gemfile", "w+") {|f| f.write(gemfile) }
70
- system("cd #{name} && mkdir contracts && mkdir tests && bundle install")
295
+ File.open("#{name}/Gemfile", "w+") { |f| f.write(gemfile) }
296
+ system("cd #{name} && bundle install")
297
+ File.open("#{name}/genesis.json", "w+") { |f| f.write(GENESIS_TEMPLATE) }
298
+
299
+ KEYS_TEMPLATE.each do |k, v|
300
+ File.open("#{name}/private_keys/#{k}.key", "w+") { |f| f.write(v) }
301
+ end
302
+ File.open("#{name}/rakefile", "w+") { |f| f.write(RAKE_FILE) }
303
+
304
+ File.open("#{name}/bin/attach.sh", "w+", 0777) { |f| f.write(ATTACH_SH) }
305
+
306
+ File.open("#{name}/bin/build.sh", "w+", 0777) { |f| f.write(BUILD_SH) }
307
+
308
+ File.open("#{name}/bin/import_keys.sh", "w+", 0777) { |f| f.write(IMPORT_KEYS_SH) }
309
+
310
+ File.open("#{name}/bin/init.sh", "w+", 0777) { |f| f.write(INIT_SH) }
311
+
312
+ File.open("#{name}/bin/private_blockchain.sh", "w+", 0777) { |f| f.write(PRIVATE_BLOCKCHAIN_SH) }
313
+
314
+ File.open("#{name}/bin/test.sh", "w+", 0777) { |f| f.write(TEST_SH) }
315
+
316
+ File.open("#{name}/private_keys/import.sh", "w+", 0777) { |f| f.write(IMPORT_SH) }
317
+
318
+ File.open("#{name}/bin/solc_helper.rb", "w+", 0777) { |f| f.write(SOLC_HELPER_TEMPLATE) }
71
319
  puts "You are flying..."
72
320
  else
73
321
  puts "Need project name"
@@ -79,10 +327,10 @@ def generate
79
327
  if name
80
328
  contract = CONTRACT_TEMPLATE.result(binding)
81
329
  puts "Create #{name.capitalize}.sol contract file..."
82
- File.open("contracts/#{name.capitalize}.sol", "w+") {|f| f.write(contract) }
330
+ File.open("contracts/#{name.capitalize}.sol", "w+") { |f| f.write(contract) }
83
331
  test = TEST_TEMPLATE.result(binding)
84
332
  puts "Create #{name}_test.rb test file..."
85
- File.open("tests/#{name.capitalize}_test.rb", "w+") {|f| f.write(test) }
333
+ File.open("tests/#{name.capitalize}_test.rb", "w+") { |f| f.write(test) }
86
334
  puts "You are flying..."
87
335
  else
88
336
  puts "Need contract name!"
@@ -91,9 +339,40 @@ end
91
339
 
92
340
  def test
93
341
  name = ARGV.shift
94
- puts "Test #{name.capitalize} contract..."
95
- system("bundle exec ruby -Ilib:test tests/#{name}_test.rb")
96
- puts "You are flying..."
342
+ if name
343
+ puts "Test #{name.capitalize} contract..."
344
+ system("bundle exec ruby -Ilib:test tests/#{name}_test.rb")
345
+ else
346
+ puts "Test all contracts..."
347
+ system("bundle exec rake")
348
+ end
349
+ end
350
+
351
+ def init
352
+ system("./bin/init.sh")
353
+ end
354
+
355
+ def import_keys
356
+ system("./bin/import_keys.sh")
357
+ end
358
+
359
+ def build
360
+ system("./bin/build.sh")
361
+ end
362
+
363
+ def server
364
+ system("./bin/private_blockchain.sh")
365
+ end
366
+
367
+ def migrate
368
+ end
369
+
370
+ def console
371
+ system("./bin/attach.sh")
372
+ end
373
+
374
+ def geth_test
375
+ system("./bin/test.sh")
97
376
  end
98
377
 
99
378
  def help
@@ -113,4 +392,14 @@ def parse_command(command)
113
392
  end
114
393
  end
115
394
 
395
+ def run_command!(command)
396
+ command = parse_command(command)
397
+
398
+ if COMMAND_WHITELIST.include?(command)
399
+ send(command)
400
+ else
401
+ help
402
+ end
403
+ end
404
+
116
405
  run_command!(command)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ether-fly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zhang YaNing
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-13 00:00:00.000000000 Z
11
+ date: 2016-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-ethereum
@@ -38,8 +38,9 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '5.8'
41
- description: EtherFly is a Ethereum smart contract test framework in ruby.I provide
42
- test in ruby EVM and geth test two styles.
41
+ description: 'EtherFly is a Ethereum smart contract test framework in ruby.It provides
42
+ two testing environments: testing in ruby EVM and testing in geth.You don''t need
43
+ to understand ruby grammar, just enjoy syntactic sugar.'
43
44
  email:
44
45
  - zhangyaning1985@gmail.com
45
46
  executables: