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.
- 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
|