ruby-ethereum 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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