ruby-ethereum 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/lib/ethereum.rb +53 -0
- data/lib/ethereum/abi.rb +333 -0
- data/lib/ethereum/abi/contract_translator.rb +174 -0
- data/lib/ethereum/abi/type.rb +117 -0
- data/lib/ethereum/account.rb +72 -0
- data/lib/ethereum/address.rb +60 -0
- data/lib/ethereum/base_convert.rb +53 -0
- data/lib/ethereum/block.rb +1311 -0
- data/lib/ethereum/block_header.rb +211 -0
- data/lib/ethereum/bloom.rb +83 -0
- data/lib/ethereum/cached_block.rb +36 -0
- data/lib/ethereum/chain.rb +400 -0
- data/lib/ethereum/constant.rb +26 -0
- data/lib/ethereum/core_ext/array/safe_slice.rb +18 -0
- data/lib/ethereum/core_ext/module/lru_cache.rb +20 -0
- data/lib/ethereum/core_ext/numeric/denominations.rb +45 -0
- data/lib/ethereum/core_ext/object/truth.rb +47 -0
- data/lib/ethereum/db.rb +6 -0
- data/lib/ethereum/db/base_db.rb +9 -0
- data/lib/ethereum/db/ephem_db.rb +63 -0
- data/lib/ethereum/db/overlay_db.rb +72 -0
- data/lib/ethereum/db/refcount_db.rb +188 -0
- data/lib/ethereum/env.rb +64 -0
- data/lib/ethereum/ethash.rb +78 -0
- data/lib/ethereum/ethash_ruby.rb +38 -0
- data/lib/ethereum/ethash_ruby/cache.rb +47 -0
- data/lib/ethereum/ethash_ruby/hashimoto.rb +75 -0
- data/lib/ethereum/ethash_ruby/utils.rb +53 -0
- data/lib/ethereum/exceptions.rb +28 -0
- data/lib/ethereum/external_call.rb +173 -0
- data/lib/ethereum/fast_rlp.rb +81 -0
- data/lib/ethereum/fast_vm.rb +625 -0
- data/lib/ethereum/fast_vm/call_data.rb +44 -0
- data/lib/ethereum/fast_vm/message.rb +29 -0
- data/lib/ethereum/fast_vm/state.rb +25 -0
- data/lib/ethereum/ffi/openssl.rb +390 -0
- data/lib/ethereum/index.rb +97 -0
- data/lib/ethereum/log.rb +43 -0
- data/lib/ethereum/miner.rb +84 -0
- data/lib/ethereum/opcodes.rb +131 -0
- data/lib/ethereum/private_key.rb +92 -0
- data/lib/ethereum/pruning_trie.rb +28 -0
- data/lib/ethereum/public_key.rb +88 -0
- data/lib/ethereum/receipt.rb +53 -0
- data/lib/ethereum/secp256k1.rb +58 -0
- data/lib/ethereum/secure_trie.rb +49 -0
- data/lib/ethereum/sedes.rb +42 -0
- data/lib/ethereum/special_contract.rb +95 -0
- data/lib/ethereum/spv.rb +79 -0
- data/lib/ethereum/spv/proof.rb +31 -0
- data/lib/ethereum/spv/proof_constructor.rb +19 -0
- data/lib/ethereum/spv/proof_verifier.rb +24 -0
- data/lib/ethereum/tester.rb +14 -0
- data/lib/ethereum/tester/abi_contract.rb +65 -0
- data/lib/ethereum/tester/fixture.rb +31 -0
- data/lib/ethereum/tester/language.rb +30 -0
- data/lib/ethereum/tester/log_recorder.rb +13 -0
- data/lib/ethereum/tester/solidity_wrapper.rb +144 -0
- data/lib/ethereum/tester/state.rb +194 -0
- data/lib/ethereum/transaction.rb +196 -0
- data/lib/ethereum/transient_trie.rb +28 -0
- data/lib/ethereum/trie.rb +549 -0
- data/lib/ethereum/trie/nibble_key.rb +184 -0
- data/lib/ethereum/utils.rb +191 -0
- data/lib/ethereum/version.rb +5 -0
- data/lib/ethereum/vm.rb +606 -0
- data/lib/ethereum/vm/call_data.rb +40 -0
- data/lib/ethereum/vm/message.rb +30 -0
- data/lib/ethereum/vm/state.rb +25 -0
- metadata +284 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Ethereum
|
6
|
+
module ABI
|
7
|
+
class ContractTranslator
|
8
|
+
|
9
|
+
def initialize(full_signature)
|
10
|
+
@v = {
|
11
|
+
function_data: {},
|
12
|
+
event_data: {}
|
13
|
+
}
|
14
|
+
|
15
|
+
if full_signature.instance_of?(String)
|
16
|
+
full_signature = JSON.parse full_signature
|
17
|
+
end
|
18
|
+
|
19
|
+
full_signature.each do |sig_item|
|
20
|
+
next if sig_item['type'] == 'constructor'
|
21
|
+
|
22
|
+
encode_types = sig_item['inputs'].map {|f| f['type'] }
|
23
|
+
signature = sig_item['inputs'].map {|f| [f['type'], f['name']] }
|
24
|
+
name = sig_item['name']
|
25
|
+
|
26
|
+
if name =~ /\(/
|
27
|
+
name = name[0, name.index('(')]
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: removable?
|
31
|
+
#if @v.has_key?(name.to_sym)
|
32
|
+
# i = 2
|
33
|
+
# i += 1 while @v.has_key?(:"#{name}#{i}")
|
34
|
+
# name += i.to_s
|
35
|
+
|
36
|
+
# logger.warn "multiple methods with the same name. Use #{name} to call #{sig_item['name']} with types #{encode_types}"
|
37
|
+
#end
|
38
|
+
|
39
|
+
if sig_item['type'] == 'function'
|
40
|
+
decode_types = sig_item['outputs'].map {|f| f['type'] }
|
41
|
+
is_unknown_type = sig_item['outputs'].size.true? && sig_item['outputs'][0]['name'] == 'unknown_out'
|
42
|
+
function_data[name.to_sym] = {
|
43
|
+
prefix: method_id(name, encode_types),
|
44
|
+
encode_types: encode_types,
|
45
|
+
decode_types: decode_types,
|
46
|
+
is_unknown_type: is_unknown_type,
|
47
|
+
is_constant: sig_item.fetch('constant', false),
|
48
|
+
signature: signature
|
49
|
+
}
|
50
|
+
elsif sig_item['type'] == 'event'
|
51
|
+
indexed = sig_item['inputs'].map {|f| f['indexed'] }
|
52
|
+
names = sig_item['inputs'].map {|f| f['name'] }
|
53
|
+
|
54
|
+
event_data[event_id(name, encode_types)] = {
|
55
|
+
types: encode_types,
|
56
|
+
name: name,
|
57
|
+
names: names,
|
58
|
+
indexed: indexed,
|
59
|
+
anonymous: sig_item.fetch('anonymous', false)
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def encode(name, args)
|
66
|
+
fdata = function_data[name.to_sym]
|
67
|
+
id = Utils.zpad(Utils.encode_int(fdata[:prefix]), 4)
|
68
|
+
calldata = ABI.encode_abi fdata[:encode_types], args
|
69
|
+
"#{id}#{calldata}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def decode(name, data)
|
73
|
+
fdata = function_data[name.to_sym]
|
74
|
+
|
75
|
+
if fdata[:is_unknown_type]
|
76
|
+
i = 0
|
77
|
+
o = []
|
78
|
+
|
79
|
+
while i < data.size
|
80
|
+
o.push Utils.to_signed(Utils.big_endian_to_int(data[i,32]))
|
81
|
+
i += 32
|
82
|
+
end
|
83
|
+
|
84
|
+
return 0 if o.empty?
|
85
|
+
o.size == 1 ? o[0] : o
|
86
|
+
else
|
87
|
+
ABI.decode_abi fdata[:decode_types], data
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def function_data
|
92
|
+
@v[:function_data]
|
93
|
+
end
|
94
|
+
|
95
|
+
def event_data
|
96
|
+
@v[:event_data]
|
97
|
+
end
|
98
|
+
|
99
|
+
def function(name)
|
100
|
+
function_data[name.to_sym]
|
101
|
+
end
|
102
|
+
|
103
|
+
def event(name, encode_types)
|
104
|
+
event_data[event_id(name, encode_types)]
|
105
|
+
end
|
106
|
+
|
107
|
+
def is_unknown_type(name)
|
108
|
+
function_data[name.to_sym][:is_unknown_type]
|
109
|
+
end
|
110
|
+
|
111
|
+
def listen(log, noprint=false)
|
112
|
+
return if log.topics.size == 0 || !event_data.has_key?(log.topics[0])
|
113
|
+
|
114
|
+
data = event_data[log.topics[0]]
|
115
|
+
types = data[:types]
|
116
|
+
name = data[:name]
|
117
|
+
names = data[:names]
|
118
|
+
indexed = data[:indexed]
|
119
|
+
indexed_types = types.zip(indexed).select {|(t, i)| i.true? }.map(&:first)
|
120
|
+
unindexed_types = types.zip(indexed).select {|(t, i)| i.false? }.map(&:first)
|
121
|
+
|
122
|
+
deserialized_args = ABI.decode_abi unindexed_types, log.data
|
123
|
+
|
124
|
+
o = {}
|
125
|
+
c1, c2 = 0, 0
|
126
|
+
names.each_with_index do |n, i|
|
127
|
+
if indexed[i].true?
|
128
|
+
topic_bytes = Utils.zpad_int log.topics[c1+1]
|
129
|
+
o[n] = ABI.decode_primitive_type ABI::Type.parse(indexed_types[c1]), topic_bytes
|
130
|
+
c1 += 1
|
131
|
+
else
|
132
|
+
o[n] = deserialized_args[c2]
|
133
|
+
c2 += 1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
o['_event_type'] = name
|
138
|
+
p o unless noprint
|
139
|
+
|
140
|
+
o
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def logger
|
146
|
+
@logger ||= Logger.new 'eth.abi.contract_translator'
|
147
|
+
end
|
148
|
+
|
149
|
+
def method_id(name, encode_types)
|
150
|
+
Utils.big_endian_to_int Utils.keccak256(get_sig(name, encode_types))[0,4]
|
151
|
+
end
|
152
|
+
|
153
|
+
def event_id(name, encode_types)
|
154
|
+
Utils.big_endian_to_int Utils.keccak256(get_sig(name, encode_types))
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_sig(name, encode_types)
|
158
|
+
"#{name}(#{encode_types.map {|x| canonical_name(x) }.join(',')})"
|
159
|
+
end
|
160
|
+
|
161
|
+
def canonical_name(x)
|
162
|
+
case x
|
163
|
+
when /\A(uint|int)(\[.*\])?\z/
|
164
|
+
"#{$1}256#{$2}"
|
165
|
+
when /\A(real|ureal|fixed|ufixed)(\[.*\])?\z/
|
166
|
+
"#{$1}128x128#{$2}"
|
167
|
+
else
|
168
|
+
x
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
module ABI
|
5
|
+
class Type
|
6
|
+
|
7
|
+
class ParseError < StandardError; end
|
8
|
+
|
9
|
+
class <<self
|
10
|
+
##
|
11
|
+
# Crazy regexp to seperate out base type component (eg. uint), size (eg.
|
12
|
+
# 256, 128x128, nil), array component (eg. [], [45], nil)
|
13
|
+
#
|
14
|
+
def parse(type)
|
15
|
+
_, base, sub, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a
|
16
|
+
|
17
|
+
dims = dimension.scan(/\[[0-9]*\]/)
|
18
|
+
raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
|
19
|
+
|
20
|
+
case base
|
21
|
+
when 'string'
|
22
|
+
raise ParseError, "String type must have no suffix or numerical suffix" unless sub.empty?
|
23
|
+
when 'bytes'
|
24
|
+
raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub.empty? || sub.to_i <= 32
|
25
|
+
when 'uint', 'int'
|
26
|
+
raise ParseError, "Integer type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
|
27
|
+
|
28
|
+
size = sub.to_i
|
29
|
+
raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
|
30
|
+
raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
|
31
|
+
when 'ureal', 'real', 'fixed', 'ufixed'
|
32
|
+
raise ParseError, "Real type must have suffix of form <high>x<low>, e.g. 128x128" unless sub =~ /\A[0-9]+x[0-9]+\z/
|
33
|
+
|
34
|
+
high, low = sub.split('x').map(&:to_i)
|
35
|
+
total = high + low
|
36
|
+
|
37
|
+
raise ParseError, "Real size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
|
38
|
+
raise ParseError, "Real high/low sizes must be multiples of 8" unless high % 8 == 0 && low % 8 == 0
|
39
|
+
when 'hash'
|
40
|
+
raise ParseError, "Hash type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
|
41
|
+
when 'address'
|
42
|
+
raise ParseError, "Address cannot have suffix" unless sub.empty?
|
43
|
+
when 'bool'
|
44
|
+
raise ParseError, "Bool cannot have suffix" unless sub.empty?
|
45
|
+
else
|
46
|
+
raise ParseError, "Unrecognized type base: #{base}"
|
47
|
+
end
|
48
|
+
|
49
|
+
new(base, sub, dims.map {|x| x[1...-1].to_i })
|
50
|
+
end
|
51
|
+
|
52
|
+
def size_type
|
53
|
+
@size_type ||= new('uint', 256, [])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr :base, :sub, :dims
|
58
|
+
|
59
|
+
##
|
60
|
+
# @param base [String] base name of type, e.g. uint for uint256[4]
|
61
|
+
# @param sub [String] subscript of type, e.g. 256 for uint256[4]
|
62
|
+
# @param dims [Array[Integer]] dimensions of array type, e.g. [1,2,0]
|
63
|
+
# for uint256[1][2][], [] for non-array type
|
64
|
+
#
|
65
|
+
def initialize(base, sub, dims)
|
66
|
+
@base = base
|
67
|
+
@sub = sub
|
68
|
+
@dims = dims
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(another_type)
|
72
|
+
base == another_type.base &&
|
73
|
+
sub == another_type.sub &&
|
74
|
+
dims == another_type.dims
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Get the static size of a type, or nil if dynamic.
|
79
|
+
#
|
80
|
+
# @return [Integer, NilClass] size of static type, or nil for dynamic
|
81
|
+
# type
|
82
|
+
#
|
83
|
+
def size
|
84
|
+
@size ||= if dims.empty?
|
85
|
+
if %w(string bytes).include?(base) && sub.empty?
|
86
|
+
nil
|
87
|
+
else
|
88
|
+
32
|
89
|
+
end
|
90
|
+
else
|
91
|
+
if dims.last == 0 # 0 for dynamic array []
|
92
|
+
nil
|
93
|
+
else
|
94
|
+
subtype.dynamic? ? nil : dims.last * subtype.size
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def dynamic?
|
100
|
+
size.nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Type with one dimension lesser.
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# Type.parse("uint256[2][]").subtype # => Type.new('uint', 256, [2])
|
108
|
+
#
|
109
|
+
# @return [Ethereum::ABI::Type]
|
110
|
+
#
|
111
|
+
def subtype
|
112
|
+
@subtype ||= self.class.new(base, sub, dims[0...-1])
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
|
5
|
+
##
|
6
|
+
# An Ethereum account.
|
7
|
+
#
|
8
|
+
# * `@nonce`: the account's nonce (the number of transactions sent by the
|
9
|
+
# account)
|
10
|
+
# * `@balance`: the account's balance in wei
|
11
|
+
# * `@storage`: the root of the account's storage trie
|
12
|
+
# * `@code_hash`: the SHA3 hash of the code associated with the account
|
13
|
+
# * `@db`: the database in which the account's code is stored
|
14
|
+
#
|
15
|
+
class Account
|
16
|
+
include RLP::Sedes::Serializable
|
17
|
+
|
18
|
+
set_serializable_fields(
|
19
|
+
nonce: Sedes.big_endian_int,
|
20
|
+
balance: Sedes.big_endian_int,
|
21
|
+
storage: Sedes.trie_root,
|
22
|
+
code_hash: Sedes.hash32
|
23
|
+
)
|
24
|
+
|
25
|
+
class <<self
|
26
|
+
##
|
27
|
+
# Create a blank account.
|
28
|
+
#
|
29
|
+
# The returned account will have zero nonce and balance, a blank storage
|
30
|
+
# trie and empty code.
|
31
|
+
#
|
32
|
+
# @param db [BaseDB] the db in which the account will store its code
|
33
|
+
#
|
34
|
+
# @return [Account] the created blank account
|
35
|
+
#
|
36
|
+
def build_blank(db, initial_nonce=0)
|
37
|
+
code_hash = Utils.keccak256 Constant::BYTE_EMPTY
|
38
|
+
db.put code_hash, Constant::BYTE_EMPTY
|
39
|
+
|
40
|
+
new initial_nonce, 0, Trie::BLANK_ROOT, code_hash, db
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(*args)
|
45
|
+
@db = args.pop if args.size == 5 # (nonce, balance, storage, code_hash, db)
|
46
|
+
@db = args.last.delete(:db) if args.last.is_a?(Hash)
|
47
|
+
raise ArgumentError, "No database object given" unless @db.is_a?(DB::BaseDB)
|
48
|
+
|
49
|
+
super(*args)
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# The EVM code of the account.
|
54
|
+
#
|
55
|
+
# This property will be read from or written to the db at each access, with
|
56
|
+
# `code_hash` used as key.
|
57
|
+
#
|
58
|
+
def code
|
59
|
+
@db.get code_hash
|
60
|
+
end
|
61
|
+
|
62
|
+
def code=(value)
|
63
|
+
self.code_hash = Utils.keccak256 value
|
64
|
+
|
65
|
+
# Technically a db storage leak, but doesn't really matter; the only
|
66
|
+
# thing that fails to get garbage collected is when code disappears due
|
67
|
+
# to a suicide.
|
68
|
+
@db.inc_refcount(code_hash, value)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
class Address
|
5
|
+
|
6
|
+
BLANK = ''.freeze
|
7
|
+
ZERO = ("\x00"*20).freeze
|
8
|
+
|
9
|
+
CREATE_CONTRACT = BLANK
|
10
|
+
|
11
|
+
def initialize(s)
|
12
|
+
@bytes = parse s
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_bytes(extended=false)
|
16
|
+
extended ? "#{@bytes}#{checksum}" : @bytes
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hex(extended=false)
|
20
|
+
Utils.encode_hex to_bytes(extended)
|
21
|
+
end
|
22
|
+
|
23
|
+
def checksum(bytes=nil)
|
24
|
+
Utils.keccak256(bytes||@bytes)[0,4]
|
25
|
+
end
|
26
|
+
|
27
|
+
def blank?
|
28
|
+
@bytes == BLANK
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse(s)
|
34
|
+
case s.size
|
35
|
+
when 0
|
36
|
+
s
|
37
|
+
when 40
|
38
|
+
Utils.decode_hex s
|
39
|
+
when 42
|
40
|
+
raise FormatError, "Invalid address format!" unless s[0,2] == '0x'
|
41
|
+
parse s[2..-1]
|
42
|
+
when 48
|
43
|
+
bytes = Utils.decode_hex s
|
44
|
+
parse bytes
|
45
|
+
when 50
|
46
|
+
raise FormatError, "Invalid address format!" unless s[0,2] == '0x'
|
47
|
+
parse s[2..-1]
|
48
|
+
when 20
|
49
|
+
s
|
50
|
+
when 24
|
51
|
+
bytes = s[0...-4]
|
52
|
+
raise ChecksumError, "Invalid address checksum!" unless s[-4..-1] == checksum(bytes)
|
53
|
+
bytes
|
54
|
+
else
|
55
|
+
raise FormatError, "Invalid address format!"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
module BaseConvert
|
5
|
+
|
6
|
+
BaseSymbols = {
|
7
|
+
2 => '01'.freeze,
|
8
|
+
10 => '0123456789'.freeze,
|
9
|
+
16 => '0123456789abcdef'.freeze,
|
10
|
+
32 => 'abcdefghijklmnopqrstuvwxyz234567'.freeze,
|
11
|
+
58 => '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.freeze,
|
12
|
+
256 => 256.times.map {|i| i.chr }.join.freeze
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
extend self
|
16
|
+
|
17
|
+
def symbols(base)
|
18
|
+
BaseSymbols[base] or raise ArgumentError, "invalid base!"
|
19
|
+
end
|
20
|
+
|
21
|
+
def convert(s, from, to, minlen=0)
|
22
|
+
return Utils.lpad(s, symbols(from)[0], minlen) if from == to
|
23
|
+
encode decode(s, from), to, minlen
|
24
|
+
end
|
25
|
+
|
26
|
+
def encode(v, base, minlen)
|
27
|
+
syms = symbols(base)
|
28
|
+
|
29
|
+
result = ''
|
30
|
+
while v > 0
|
31
|
+
result = syms[v % base] + result
|
32
|
+
v /= base
|
33
|
+
end
|
34
|
+
|
35
|
+
Utils.lpad result, syms[0], minlen
|
36
|
+
end
|
37
|
+
|
38
|
+
def decode(s, base)
|
39
|
+
syms = symbols(base)
|
40
|
+
s = s.downcase if base == 16
|
41
|
+
|
42
|
+
result = 0
|
43
|
+
while s.size > 0
|
44
|
+
result *= base
|
45
|
+
result += syms.index(s[0])
|
46
|
+
s = s[1..-1]
|
47
|
+
end
|
48
|
+
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|