ruby-ethereum 0.9.0

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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +40 -0
  4. data/lib/ethereum.rb +53 -0
  5. data/lib/ethereum/abi.rb +333 -0
  6. data/lib/ethereum/abi/contract_translator.rb +174 -0
  7. data/lib/ethereum/abi/type.rb +117 -0
  8. data/lib/ethereum/account.rb +72 -0
  9. data/lib/ethereum/address.rb +60 -0
  10. data/lib/ethereum/base_convert.rb +53 -0
  11. data/lib/ethereum/block.rb +1311 -0
  12. data/lib/ethereum/block_header.rb +211 -0
  13. data/lib/ethereum/bloom.rb +83 -0
  14. data/lib/ethereum/cached_block.rb +36 -0
  15. data/lib/ethereum/chain.rb +400 -0
  16. data/lib/ethereum/constant.rb +26 -0
  17. data/lib/ethereum/core_ext/array/safe_slice.rb +18 -0
  18. data/lib/ethereum/core_ext/module/lru_cache.rb +20 -0
  19. data/lib/ethereum/core_ext/numeric/denominations.rb +45 -0
  20. data/lib/ethereum/core_ext/object/truth.rb +47 -0
  21. data/lib/ethereum/db.rb +6 -0
  22. data/lib/ethereum/db/base_db.rb +9 -0
  23. data/lib/ethereum/db/ephem_db.rb +63 -0
  24. data/lib/ethereum/db/overlay_db.rb +72 -0
  25. data/lib/ethereum/db/refcount_db.rb +188 -0
  26. data/lib/ethereum/env.rb +64 -0
  27. data/lib/ethereum/ethash.rb +78 -0
  28. data/lib/ethereum/ethash_ruby.rb +38 -0
  29. data/lib/ethereum/ethash_ruby/cache.rb +47 -0
  30. data/lib/ethereum/ethash_ruby/hashimoto.rb +75 -0
  31. data/lib/ethereum/ethash_ruby/utils.rb +53 -0
  32. data/lib/ethereum/exceptions.rb +28 -0
  33. data/lib/ethereum/external_call.rb +173 -0
  34. data/lib/ethereum/fast_rlp.rb +81 -0
  35. data/lib/ethereum/fast_vm.rb +625 -0
  36. data/lib/ethereum/fast_vm/call_data.rb +44 -0
  37. data/lib/ethereum/fast_vm/message.rb +29 -0
  38. data/lib/ethereum/fast_vm/state.rb +25 -0
  39. data/lib/ethereum/ffi/openssl.rb +390 -0
  40. data/lib/ethereum/index.rb +97 -0
  41. data/lib/ethereum/log.rb +43 -0
  42. data/lib/ethereum/miner.rb +84 -0
  43. data/lib/ethereum/opcodes.rb +131 -0
  44. data/lib/ethereum/private_key.rb +92 -0
  45. data/lib/ethereum/pruning_trie.rb +28 -0
  46. data/lib/ethereum/public_key.rb +88 -0
  47. data/lib/ethereum/receipt.rb +53 -0
  48. data/lib/ethereum/secp256k1.rb +58 -0
  49. data/lib/ethereum/secure_trie.rb +49 -0
  50. data/lib/ethereum/sedes.rb +42 -0
  51. data/lib/ethereum/special_contract.rb +95 -0
  52. data/lib/ethereum/spv.rb +79 -0
  53. data/lib/ethereum/spv/proof.rb +31 -0
  54. data/lib/ethereum/spv/proof_constructor.rb +19 -0
  55. data/lib/ethereum/spv/proof_verifier.rb +24 -0
  56. data/lib/ethereum/tester.rb +14 -0
  57. data/lib/ethereum/tester/abi_contract.rb +65 -0
  58. data/lib/ethereum/tester/fixture.rb +31 -0
  59. data/lib/ethereum/tester/language.rb +30 -0
  60. data/lib/ethereum/tester/log_recorder.rb +13 -0
  61. data/lib/ethereum/tester/solidity_wrapper.rb +144 -0
  62. data/lib/ethereum/tester/state.rb +194 -0
  63. data/lib/ethereum/transaction.rb +196 -0
  64. data/lib/ethereum/transient_trie.rb +28 -0
  65. data/lib/ethereum/trie.rb +549 -0
  66. data/lib/ethereum/trie/nibble_key.rb +184 -0
  67. data/lib/ethereum/utils.rb +191 -0
  68. data/lib/ethereum/version.rb +5 -0
  69. data/lib/ethereum/vm.rb +606 -0
  70. data/lib/ethereum/vm/call_data.rb +40 -0
  71. data/lib/ethereum/vm/message.rb +30 -0
  72. data/lib/ethereum/vm/state.rb +25 -0
  73. metadata +284 -0
@@ -0,0 +1,31 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'set'
4
+
5
+ module Ethereum
6
+ module SPV
7
+ class Proof
8
+
9
+ attr :nodes, :exempts
10
+
11
+ def initialize(nodes: Set.new, exempts: [])
12
+ @nodes = nodes
13
+ @exempts = exempts
14
+ end
15
+
16
+ def decoded_nodes
17
+ nodes.map {|n| RLP.decode(n) }
18
+ end
19
+
20
+ def add_node(node)
21
+ node = FastRLP.encode node
22
+ nodes.add(node) unless exempts.include?(node)
23
+ end
24
+
25
+ def add_exempt(node)
26
+ exempts.add FastRLP.encode(node)
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'set'
4
+
5
+ module Ethereum
6
+ module SPV
7
+ class ProofConstructor < Proof
8
+
9
+ def grabbing(node)
10
+ add_node node.dup
11
+ end
12
+
13
+ def store(node)
14
+ add_exempt node.dup
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'set'
4
+
5
+ module Ethereum
6
+ module SPV
7
+ class ProofVerifier < Proof
8
+
9
+ def initialize(nodes, exempts: [])
10
+ nodes = nodes.map {|n| RLP.encode(n) }.to_set
11
+ super(nodes: nodes, exempts: exempts)
12
+ end
13
+
14
+ def grabbing(node)
15
+ raise InvalidSPVProof unless nodes.include?(FastRLP.encode(node))
16
+ end
17
+
18
+ def store(node)
19
+ add_node node.dup
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'ethereum/tester/abi_contract'
4
+ require 'ethereum/tester/fixture'
5
+ require 'ethereum/tester/solidity_wrapper'
6
+ require 'ethereum/tester/language'
7
+ require 'ethereum/tester/log_recorder'
8
+ require 'ethereum/tester/state'
9
+
10
+ module Ethereum
11
+ module Tester
12
+
13
+ end
14
+ end
@@ -0,0 +1,65 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Ethereum
4
+ module Tester
5
+ class ABIContract
6
+
7
+ attr :address, :abi
8
+
9
+ def initialize(state, abi, address, listen: true, log_listener: nil)
10
+ @state = state
11
+ @abi = abi
12
+ @address = address
13
+
14
+ @translator = ABI::ContractTranslator.new abi
15
+
16
+ if listen
17
+ if log_listener
18
+ listener = lambda do |log|
19
+ r = @translator.listen(log, noprint: true)
20
+ log_listener.call r if r.true?
21
+ end
22
+ else
23
+ listener = ->(log) { @translator.listen(log, noprint: false) }
24
+ end
25
+ end
26
+
27
+ @translator.function_data.each do |f, _|
28
+ generate_function f
29
+ end
30
+ end
31
+
32
+ def listen(x)
33
+ @translator.listen x
34
+ end
35
+
36
+ private
37
+
38
+ def generate_function(f)
39
+ singleton_class.class_eval <<-EOF
40
+ def #{f}(*args, **kwargs)
41
+ sender = kwargs.delete(:sender) || Fixture.keys[0]
42
+ to = @address
43
+ value = kwargs.delete(:value) || 0
44
+ evmdata = @translator.encode('#{f}', args)
45
+ output = kwargs.delete(:output)
46
+
47
+ o = @state._send_tx(sender, to, value, **kwargs.merge(evmdata: evmdata))
48
+
49
+ if output == :raw
50
+ outdata = o[:output]
51
+ elsif o[:output].false?
52
+ outdata = nil
53
+ else
54
+ outdata = @translator.decode '#{f}', o[:output]
55
+ outdata = outdata.size == 1 ? outdata[0] : outdata
56
+ end
57
+
58
+ kwargs[:profiling].true? ? o.merge(outdata: outdata) : outdata
59
+ end
60
+ EOF
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Ethereum
4
+ module Tester
5
+ module Fixture
6
+
7
+ NUM_ACCOUNTS = 10
8
+
9
+ class <<self
10
+ def keys
11
+ @keys ||= NUM_ACCOUNTS.times.map {|i| Utils.keccak256(i.to_s) }
12
+ end
13
+
14
+ def accounts
15
+ @accounts ||= NUM_ACCOUNTS.times.map {|i| PrivateKey.new(keys[i]).to_address }
16
+ end
17
+
18
+ def gas_limit
19
+ @gas_limit ||= 3141592
20
+ end
21
+ attr_writer :gas_limit
22
+
23
+ def gas_price
24
+ @gas_price ||= 1
25
+ end
26
+ attr_writer :gas_price
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Ethereum
4
+ module Tester
5
+ module Language
6
+
7
+ class <<self
8
+ def all
9
+ return @all if @all
10
+
11
+ @all = {
12
+ serpent: Serpent,
13
+ solidity: SolidityWrapper.solc_path && SolidityWrapper
14
+ }
15
+
16
+ @all
17
+ end
18
+
19
+ def get(name)
20
+ all[name]
21
+ end
22
+
23
+ def format_spaces(code)
24
+ code =~ /\A(\s+)/ ? code.gsub(/^#{$1}/, '') : code
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Ethereum
4
+ module Tester
5
+ module LogRecorder
6
+
7
+ def initialize
8
+ @records = []
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,144 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Ethereum
4
+ module Tester
5
+
6
+ module SolidityWrapper
7
+
8
+ class CompileError < StandardError; end
9
+
10
+ class <<self
11
+ def split_contracts(code)
12
+ contracts = []
13
+ contract = nil
14
+
15
+ code.split("\n").each do |line|
16
+ if line =~ /\Acontract /
17
+ contracts.push(contract.join("\n")) if contract
18
+ contract = [line]
19
+ elsif contract
20
+ contract.push line
21
+ end
22
+ end
23
+
24
+ contracts.push(contract.join("\n")) if contract
25
+
26
+ contracts
27
+ end
28
+
29
+ def contract_names(code)
30
+ names = []
31
+
32
+ split_contracts(code).each do |contract|
33
+ keyword, name, _ = contract.split(/\s+/, 3)
34
+ raise AssertError, 'keyword must be contract' unless keyword == 'contract' && !name.empty?
35
+ names.push name
36
+ end
37
+
38
+ names
39
+ end
40
+
41
+ def combined(code)
42
+ out = Tempfile.new 'solc_output_'
43
+
44
+ pipe = IO.popen([solc_path, '--add-std', '--optimize', '--combined-json', 'abi,bin,devdoc,userdoc'], 'w', [:out, :err] => out)
45
+ pipe.write code
46
+ pipe.close_write
47
+ raise CompileError, 'compilation failed' unless $?.success?
48
+
49
+ out.rewind
50
+ contracts = JSON.parse(out.read)['contracts']
51
+
52
+ contracts.each do |name, data|
53
+ data['abi'] = JSON.parse data['abi']
54
+ data['devdoc'] = JSON.parse data['devdoc']
55
+ data['userdoc'] = JSON.parse data['userdoc']
56
+ end
57
+
58
+ names = contract_names code
59
+ raise AssertError unless names.size <= contracts.size
60
+
61
+ names.map {|n| [n, contracts[n]] }
62
+ ensure
63
+ out.close
64
+ end
65
+
66
+ ##
67
+ # Returns binary of last contract in code.
68
+ #
69
+ def compile(code, contract_name='')
70
+ sorted_contracts = combined code
71
+ if contract_name.true?
72
+ idx = sorted_contracts.map(&:first).index(contract_name)
73
+ else
74
+ idx = -1
75
+ end
76
+
77
+ Utils.decode_hex sorted_contracts[idx][1]['bin']
78
+ end
79
+
80
+ ##
81
+ # Returns signature of last contract in code.
82
+ #
83
+ def mk_full_signature(code, contract_name='')
84
+ sorted_contracts = combined code
85
+ if contract_name.true?
86
+ idx = sorted_contracts.map(&:first).index(contract_name)
87
+ else
88
+ idx = -1
89
+ end
90
+
91
+ sorted_contracts[idx][1]['abi']
92
+ end
93
+
94
+ def compiler_version
95
+ output = `#{solc_path} --version`.strip
96
+ output =~ /^Version: ([0-9a-z.-]+)\// ? $1 : nil
97
+ end
98
+
99
+ ##
100
+ # Full format as returned by jsonrpc.
101
+ #
102
+ def compile_rich(code)
103
+ combined(code).map do |(name, contract)|
104
+ [
105
+ name,
106
+ {
107
+ 'code' => "0x#{contract['bin']}",
108
+ 'info' => {
109
+ 'abiDefinition' => contract['abi'],
110
+ 'compilerVersion' => compiler_version,
111
+ 'developerDoc' => contract['devdoc'],
112
+ 'language' => 'Solidity',
113
+ 'languageVersion' => '0',
114
+ 'source' => code,
115
+ 'userDoc' => contract['userdoc']
116
+ }
117
+ }
118
+ ]
119
+ end.to_h
120
+ end
121
+
122
+ def solc_path
123
+ @solc_path ||= which('solc')
124
+ end
125
+
126
+ def solc_path=(v)
127
+ @solc_path = v
128
+ end
129
+
130
+ def which(cmd)
131
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
132
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
133
+ exts.each { |ext|
134
+ exe = File.join(path, "#{cmd}#{ext}")
135
+ return exe if File.executable?(exe) && !File.directory?(exe)
136
+ }
137
+ end
138
+ return nil
139
+ end
140
+ end
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,194 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'fileutils'
4
+
5
+ module Ethereum
6
+ module Tester
7
+ class State
8
+
9
+ TMP_DIR_PREFIX = 'eth-tester-'.freeze
10
+
11
+ attr :block, :blocks
12
+
13
+ def initialize(num_accounts=Fixture::NUM_ACCOUNTS)
14
+ @temp_data_dir = Dir.mktmpdir TMP_DIR_PREFIX
15
+
16
+ @db = DB::EphemDB.new
17
+ @env = Env.new @db
18
+
19
+ @block = Block.genesis @env, start_alloc: get_start_alloc(num_accounts)
20
+ @block.timestamp = 1410973349
21
+ @block.coinbase = Fixture.accounts[0]
22
+ @block.gas_limit = 10**9
23
+
24
+ @blocks = [@block]
25
+ @last_tx = nil
26
+
27
+ ObjectSpace.define_finalizer(self) {|id| FileUtils.rm_rf(@temp_data_dir) }
28
+ end
29
+
30
+ def contract(code, sender: Fixture.keys[0], endowment: 0, language: :serpent, gas: nil)
31
+ code = Language.format_spaces code
32
+ opcodes = Language.get(language).compile(code)
33
+ addr = evm(opcodes, sender: sender, endowment: endowment)
34
+ raise AssertError, "Contract code empty" if @block.get_code(addr).empty?
35
+ addr
36
+ end
37
+
38
+ def abi_contract(code, **kwargs)
39
+ sender = kwargs.delete(:sender) || Fixture.keys[0]
40
+ endowment = kwargs.delete(:endowment) || 0
41
+ language = kwargs.delete(:language) || :serpent
42
+ contract_name = kwargs.delete(:contract_name) || ''
43
+ gas = kwargs.delete(:gas) || nil
44
+ log_listener = kwargs.delete(:log_listener) || nil
45
+ listen = kwargs.delete(:listen) || true
46
+
47
+ code = Language.format_spaces code
48
+
49
+ if contract_name.true?
50
+ raise ArgumentError, "language must be solidity when contract name is given" unless language == :solidity
51
+ cn_args = {contract_name: contract_name}
52
+ else
53
+ cn_args = kwargs
54
+ end
55
+
56
+ lang = Language.get language
57
+ opcodes = lang.compile code, **cn_args
58
+ addr = evm(opcodes, sender: sender, endowment: endowment, gas: gas)
59
+ raise AssertError, "Contract code empty" if @block.get_code(addr).empty?
60
+
61
+ abi = lang.mk_full_signature(code, **cn_args)
62
+ ABIContract.new(self, abi, addr, listen: listen, log_listener: log_listener)
63
+ end
64
+
65
+ def evm(opcodes, sender: Fixture.keys[0], endowment: 0, gas: nil)
66
+ sendnonce = @block.get_nonce PrivateKey.new(sender).to_address
67
+
68
+ tx = Transaction.contract sendnonce, Fixture.gas_price, Fixture.gas_limit, endowment, opcodes
69
+ tx.sign sender
70
+ tx.startgas = gas if gas
71
+
72
+ success, output = @block.apply_transaction tx
73
+ raise ContractCreationFailed if success.false?
74
+
75
+ output
76
+ end
77
+
78
+ def call(*args, **kwargs)
79
+ raise DeprecatedError, "Call deprecated. Please use the abi_contract mechanism or message(sender, to, value, data) directly, using the ABI module to generate data if needed."
80
+ end
81
+
82
+ def send_tx(*args, **kwargs)
83
+ _send_tx(*args, **kwargs)[:output]
84
+ end
85
+
86
+ def _send_tx(sender, to, value, evmdata: '', output: nil, funid: nil, abi: nil, profiling: 0)
87
+ if funid || abi
88
+ raise ArgumentError, "Send with funid+abi is deprecated. Please use the abi_contract mechanism."
89
+ end
90
+
91
+ t1, g1 = Time.now, @block.gas_used
92
+ sendnonce = @block.get_nonce PrivateKey.new(sender).to_address
93
+ tx = Transaction.new(sendnonce, Fixture.gas_price, Fixture.gas_limit, to, value, evmdata)
94
+ @last_tx = tx
95
+ tx.sign(sender)
96
+
97
+ recorder = profiling > 1 ? LogRecorder.new : nil
98
+
99
+ success, output = @block.apply_transaction(tx)
100
+ raise TransactionFailed if success.false?
101
+ out = {output: output}
102
+
103
+ if profiling > 0
104
+ zero_bytes = tx.data.count Constant::BYTE_ZERO
105
+ none_zero_bytes = tx.data.size - zero_bytes
106
+ intrinsic_gas_used = Opcodes::GTXCOST +
107
+ Opcodes::GTXDATAZERO * zero_bytes +
108
+ Opcodes::GTXDATANONZERO * none_zero_bytes
109
+ t2, g2 = Time.now, @block.gas_used
110
+ output[:time] = t2 - t1
111
+ output[:gas] = g2 - g1 - intrinsic_gas_used
112
+ end
113
+
114
+ if profiling > 1
115
+ # TODO: collect all traced ops use LogRecorder
116
+ end
117
+
118
+ out
119
+ end
120
+
121
+ def profile(*args, **kwargs)
122
+ kwargs[:profiling] = true
123
+ _send_tx(*args, **kwargs)
124
+ end
125
+
126
+ def mkspv(sender, to, value, data: [], funid: nil, abi: nil)
127
+ sendnonce = @block.get_nonce PrivateKey.new(sender).to_address
128
+ evmdata = funid ? Serpent.encode_abi(funid, *abi) : Serpent.encode_datalist(*data)
129
+
130
+ tx = Transaction.new(sendnonce, Fixture.gas_price, Fixture.gas_limit, to, value, evmdata)
131
+ @last_tx = tx
132
+ tx.sign(sender)
133
+
134
+ SPV.make_transaction_proof(@block, tx)
135
+ end
136
+
137
+ def verifyspv(sender, to, value, data: [], funid: nil, abi: nil, proof: [])
138
+ sendnonce = @block.get_nonce PrivateKey.new(sender).to_address
139
+ evmdata = funid ? Serpent.encode_abi(funid, *abi) : Serpent.encode_datalist(*data)
140
+
141
+ tx = Transaction.new(sendnonce, Fixture.gas_price, Fixture.gas_limit, to, value, evmdata)
142
+ @last_tx = tx
143
+ tx.sign(sender)
144
+
145
+ SPV.verify_transaction_proof(@block, tx, proof)
146
+ end
147
+
148
+ def trace(sender, to, value, data=[])
149
+ recorder = LogRecorder.new
150
+ send_tx sender, to, value, data
151
+ recorder.pop_records # TODO: implement recorder
152
+ end
153
+
154
+ def mine(n=1, coinbase: Fixture.accounts[0])
155
+ n.times do |i|
156
+ @block.finalize
157
+ @block.commit_state
158
+
159
+ @db.put @block.full_hash, RLP.encode(@block)
160
+
161
+ t = @block.timestamp + 6 + rand(12)
162
+ x = Block.build_from_parent @block, coinbase, timestamp: t
163
+ @block = x
164
+
165
+ @blocks.push @block
166
+ end
167
+ end
168
+
169
+ def snapshot
170
+ RLP.encode @block
171
+ end
172
+
173
+ def revert(data)
174
+ @block = RLP.decode data, sedes: Block, env: @env
175
+
176
+ @block.make_mutable!
177
+ @block._cached_rlp = nil
178
+
179
+ @block.header.make_mutable!
180
+ @block.header._cached_rlp = nil
181
+ end
182
+
183
+ private
184
+
185
+ def get_start_alloc(num_accounts)
186
+ o = {}
187
+ num_accounts.times {|i| o[Fixture.accounts[i]] = {wei: 10**24} }
188
+ (1...5).each {|i| o[Utils.int_to_addr(i)] = {wei: 1} }
189
+ o
190
+ end
191
+
192
+ end
193
+ end
194
+ end