universum 0.1.2 → 0.2.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 +4 -4
- data/Manifest.txt +16 -0
- data/README.md +34 -35
- data/Rakefile +1 -1
- data/lib/universum.rb +19 -0
- data/lib/universum/account.rb +52 -0
- data/lib/universum/address.rb +75 -0
- data/lib/universum/contract.rb +87 -0
- data/lib/universum/function.rb +10 -0
- data/lib/universum/receipt.rb +52 -0
- data/lib/universum/safe_array.rb +69 -0
- data/lib/universum/safe_hash.rb +77 -0
- data/lib/universum/transaction.rb +89 -0
- data/lib/universum/type.rb +49 -0
- data/lib/universum/units_money.rb +64 -0
- data/lib/universum/units_time.rb +51 -0
- data/lib/universum/universum.rb +0 -394
- data/lib/universum/version.rb +2 -2
- data/test/contracts/greeter.rb +12 -13
- data/test/contracts/mytoken.rb +13 -14
- data/test/test_array.rb +52 -0
- data/test/test_greeter.rb +2 -2
- data/test/test_hash.rb +84 -0
- data/test/test_struct.rb +35 -0
- data/test/test_units_money.rb +60 -0
- data/test/test_units_time.rb +75 -0
- metadata +19 -3
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class SafeArray
|
4
|
+
|
5
|
+
## e.g.
|
6
|
+
## Array.of( Address ), Array.of( Integer), etc.
|
7
|
+
|
8
|
+
def self.build_class( klass_value )
|
9
|
+
## note: care for now only about value type / class
|
10
|
+
|
11
|
+
## note: keep a class cache
|
12
|
+
cache = @@cache ||= {}
|
13
|
+
klass = cache[ klass_value ]
|
14
|
+
return klass if klass
|
15
|
+
|
16
|
+
klass = Class.new( SafeArray )
|
17
|
+
klass.class_eval( <<RUBY )
|
18
|
+
def self.klass_value
|
19
|
+
@klass_value ||= #{klass_value}
|
20
|
+
end
|
21
|
+
RUBY
|
22
|
+
## add to cache for later (re)use
|
23
|
+
cache[ klass_value ] = klass
|
24
|
+
klass
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def self.new_zero() new; end
|
29
|
+
def self.zero() @zero ||= new_zero; end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
## todo/check: if array works if value is a (nested/multi-dimensional) array
|
35
|
+
@ary = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def []=(index, value)
|
39
|
+
@ary[index] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](index)
|
43
|
+
item = @ary[ index ]
|
44
|
+
if item.nil?
|
45
|
+
## todo/check:
|
46
|
+
## always return (deep) frozen zero object - why? why not?
|
47
|
+
## let user change the returned zero object - why? why not?
|
48
|
+
if self.class.klass_value.respond_to?( :new_zero )
|
49
|
+
## note: use a new unfrozen copy of the zero object
|
50
|
+
## changes to the object MUST be possible (new "empty" modifable object expected)
|
51
|
+
item = self.class.klass_value.new_zero
|
52
|
+
else # assume value semantics e.g. Integer, Bool, etc. zero values gets replaced
|
53
|
+
## puts "use value semantics"
|
54
|
+
item = self.class.klass_value.zero
|
55
|
+
end
|
56
|
+
end
|
57
|
+
item
|
58
|
+
end
|
59
|
+
|
60
|
+
def push( item )
|
61
|
+
## todo/fix: check if item.is_a? @type
|
62
|
+
## note: Address might be a String too (Address | String)
|
63
|
+
## store Address always as String!!! - why? why not?
|
64
|
+
@ary.push( item )
|
65
|
+
end
|
66
|
+
|
67
|
+
def size() @ary.size; end
|
68
|
+
def length() size; end
|
69
|
+
end # class SafeArray
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
class SafeHash
|
5
|
+
|
6
|
+
## e.g.
|
7
|
+
## Mapping.of( Address => Money )
|
8
|
+
|
9
|
+
## note: need to create new class!! for every mapping
|
10
|
+
## make klass_key class and
|
11
|
+
## klass_value class into class instance variables
|
12
|
+
## that can get used by zero
|
13
|
+
## self.new returns a Hash.new/SafeHash.new like object
|
14
|
+
|
15
|
+
def self.build_class( klass_key, klass_value )
|
16
|
+
## note: care for now only about value type / class
|
17
|
+
|
18
|
+
## note: keep a class cache
|
19
|
+
cache = @@cache ||= {}
|
20
|
+
klass = cache[ klass_value ]
|
21
|
+
return klass if klass
|
22
|
+
|
23
|
+
klass = Class.new( SafeHash )
|
24
|
+
klass.class_eval( <<RUBY )
|
25
|
+
def self.klass_key
|
26
|
+
@klass_key ||= #{klass_key}
|
27
|
+
end
|
28
|
+
def self.klass_value
|
29
|
+
@klass_value ||= #{klass_value}
|
30
|
+
end
|
31
|
+
RUBY
|
32
|
+
## add to cache for later (re)use
|
33
|
+
cache[ klass_value ] = klass
|
34
|
+
klass
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def self.new_zero() new; end
|
39
|
+
def self.zero() @zero ||= new_zero; end
|
40
|
+
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
## todo/check: if hash works if value is a (nested) hash
|
44
|
+
@h = {}
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def []=(key, value)
|
49
|
+
@h[key] = value
|
50
|
+
end
|
51
|
+
|
52
|
+
def [](key)
|
53
|
+
item = @h[ key ]
|
54
|
+
if item.nil?
|
55
|
+
## pp self.class.klass_value
|
56
|
+
## pp self.class.klass_value.zero
|
57
|
+
|
58
|
+
#####
|
59
|
+
# todo/check:
|
60
|
+
# add zero to hash on lookup (increases size/length)
|
61
|
+
# why? why not?
|
62
|
+
|
63
|
+
if self.class.klass_value.respond_to?( :new_zero )
|
64
|
+
## note: use a dup(licated) unfrozen copy of the zero object
|
65
|
+
## changes to the object MUST be possible (new "empty" modifable object expected)
|
66
|
+
item = @h[ key ] = self.class.klass_value.new_zero
|
67
|
+
else # assume value semantics e.g. Integer, Bool, etc. zero values gets replaced
|
68
|
+
## puts "use value semantics"
|
69
|
+
item = @h[ key ] = self.class.klass_value.zero
|
70
|
+
end
|
71
|
+
end
|
72
|
+
item
|
73
|
+
end
|
74
|
+
|
75
|
+
def size() @h.size; end
|
76
|
+
def length() size; end
|
77
|
+
end # class SafeHash
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
class Transaction
|
5
|
+
|
6
|
+
def self.send( **kwargs ) ## convenience helper for Uni.send_transaction
|
7
|
+
Universum.send_transaction( **kwargs )
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
attr_reader :from, :to, :value, :data, :nonce
|
12
|
+
|
13
|
+
def initialize( from:, to:, value:, data:, nonce: nil )
|
14
|
+
## note: from only allows accounts
|
15
|
+
if from.is_a?( Account )
|
16
|
+
account = from
|
17
|
+
else
|
18
|
+
account = Account.at( from ) ## lookup account by address
|
19
|
+
end
|
20
|
+
|
21
|
+
@from = account.address
|
22
|
+
|
23
|
+
if to.is_a?( Contract )
|
24
|
+
@to = "#{to.address} (#{to.class.name})"
|
25
|
+
elsif to.is_a?( Account ) ## note: to allows Contracts AND Accounts
|
26
|
+
@to = to.address
|
27
|
+
else
|
28
|
+
@to = to # might be a contract or account (pass through for now)
|
29
|
+
end
|
30
|
+
|
31
|
+
@value = value
|
32
|
+
@data = data
|
33
|
+
|
34
|
+
if nonce
|
35
|
+
@nonce = nonce
|
36
|
+
else
|
37
|
+
## auto-add nonce (that is, tx counter - auto-increment)
|
38
|
+
@nonce = account.tx ## get transaction (tx) counter (starts with 0)
|
39
|
+
account._auto_inc_tx
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def log_str
|
44
|
+
## for debug add transaction (tx) args (e.g. from, value, etc.)
|
45
|
+
tx_args_str = ""
|
46
|
+
tx_args_str << "from: #{@from} ##{@nonce}"
|
47
|
+
tx_args_str << ", value: #{@value}" if @value > 0
|
48
|
+
|
49
|
+
if @to == '0x0000' ## special case - contract creation transaction
|
50
|
+
klass = @data[0] ## contract class - todo/fix: check if data[] is a contract class!!!
|
51
|
+
call_args = @data[1..-1] ## arguments
|
52
|
+
|
53
|
+
## convert all args to string (with inspect) for debugging
|
54
|
+
## check if pretty_inspect adds trailing newline? why? why not? possible?
|
55
|
+
call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
|
56
|
+
|
57
|
+
"#{tx_args_str} => to: #{@to} create contract #{klass.name}.new( #{call_args_str} )"
|
58
|
+
else
|
59
|
+
if @data.empty? ## assume receive (default method) for now if data empty (no method specified)
|
60
|
+
"#{tx_args_str} => to: #{@to} call default fallback"
|
61
|
+
else
|
62
|
+
m = @data[0] ## method name / signature
|
63
|
+
call_args = @data[1..-1] ## arguments
|
64
|
+
|
65
|
+
## convert all args to string (with inspect) for debugging
|
66
|
+
## check if pretty_inspect adds trailing newline? why? why not? possible?
|
67
|
+
call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
|
68
|
+
|
69
|
+
"#{tx_args_str} => to: #{@to} call #{m}( #{call_args_str} )"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def receipt
|
76
|
+
Receipt.find( self )
|
77
|
+
end
|
78
|
+
|
79
|
+
def contract # convenience helper (quick contract lookup)
|
80
|
+
rec = receipt
|
81
|
+
if rec
|
82
|
+
rec.contract
|
83
|
+
else
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end # class Transaction
|
88
|
+
|
89
|
+
Tx = Transaction ## add some convenience aliases (still undecided what's the most popular :-)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
## add dummy bool class for mapping and (payable) method signature
|
5
|
+
|
6
|
+
class Integer
|
7
|
+
def self.zero() 0; end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Bool
|
11
|
+
def self.zero() false; end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Money
|
15
|
+
def self.zero() 0; end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Void ## only used (reserved) for (payable) method signature now
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
class Mapping
|
23
|
+
|
24
|
+
def self.of( *args )
|
25
|
+
## e.g. gets passed in [{Address=>Integer}]
|
26
|
+
## check for Integer - use Hash.new(0)
|
27
|
+
## check for Bool - use Hash.new(False)
|
28
|
+
if args[0].is_a? Hash
|
29
|
+
arg = args[0].to_a ## convert to array (for easier access)
|
30
|
+
klass_key = arg[0][0]
|
31
|
+
klass_value = arg[0][1]
|
32
|
+
klass = SafeHash.build_class( klass_key, klass_value )
|
33
|
+
klass.new
|
34
|
+
else
|
35
|
+
## todo/fix: throw argument error/exception
|
36
|
+
Hash.new ## that is, "plain" {} with all "standard" defaults
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
class Array
|
43
|
+
## "typed" safe array "constructor"
|
44
|
+
## e.g. Array.of( Address ) or Array.of( Money ) or Array.of( Proposal, size: 2 ) etc.
|
45
|
+
def self.of( klass_value )
|
46
|
+
klass = SafeArray.build_class( klass_value )
|
47
|
+
klass.new ## todo: add klass.new( **kwargs ) for size: 2 etc.
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Integer
|
4
|
+
|
5
|
+
E3 = 10**3 # 1_000
|
6
|
+
E6 = 10**6 # 1_000_000
|
7
|
+
E9 = 10**9 # 1_000_000_000
|
8
|
+
E12 = 10**12 # 1_000_000_000_000
|
9
|
+
E15 = 10**15 # 1_000_000_000_000_000
|
10
|
+
E18 = 10**18 # 1_000_000_000_000_000_000
|
11
|
+
|
12
|
+
def e3() self * E3; end
|
13
|
+
def e6() self * E6; end
|
14
|
+
def e9() self * E9; end
|
15
|
+
def e12() self * E12; end
|
16
|
+
def e15() self * E15; end
|
17
|
+
def e18() self * E18; end
|
18
|
+
|
19
|
+
|
20
|
+
## todo/fix: move ethereum money units to a module and include here
|
21
|
+
|
22
|
+
###########
|
23
|
+
# Ethereum money units
|
24
|
+
#
|
25
|
+
# wei 1 wei | 1
|
26
|
+
# kwei (babbage) 1e3 wei | 1_000
|
27
|
+
# mwei (lovelace | ada) 1e6 wei | 1_000_000
|
28
|
+
# gwei (shannon) 1e9 wei | 1_000_000_000
|
29
|
+
# microether (szabo) 1e12 wei | 1_000_000_000_000
|
30
|
+
# milliether (finney) 1e15 wei | 1_000_000_000_000_000
|
31
|
+
# ether 1e18 wei | 1_000_000_000_000_000_000
|
32
|
+
#
|
33
|
+
# Names in Honor:
|
34
|
+
# wei => Wei Dai
|
35
|
+
# lovelace | ada => Ada Lovelace (1815-1852)
|
36
|
+
# babbage => Charles Babbage (1791-1871)
|
37
|
+
# shannon => Claude Shannon (1916-2001)
|
38
|
+
# szabo => Nick Szabo
|
39
|
+
# finney => Hal Finney (1956-2014)
|
40
|
+
|
41
|
+
def wei() self; end
|
42
|
+
def kwei() self * E3; end
|
43
|
+
def mwei() self * E6; end
|
44
|
+
def gwei() self * E9; end
|
45
|
+
def microether() self * E12; end
|
46
|
+
def milliether() self * E15; end
|
47
|
+
def ether() self * E18; end
|
48
|
+
|
49
|
+
########################################################
|
50
|
+
## alias - use alias or alias_method - why? why not?
|
51
|
+
def babbage() kwei; end
|
52
|
+
def lovelace() mwei; end
|
53
|
+
def ada() mwei; end ## todo/check: in use, really? keep just lovelave- why? why not?
|
54
|
+
def shannon() gwei; end
|
55
|
+
def szabo() microether; end
|
56
|
+
def finney() milliether; end
|
57
|
+
|
58
|
+
def microeth() microether; end
|
59
|
+
def micro() microether; end
|
60
|
+
def millieth() milliether; end
|
61
|
+
def milli() milliether; end
|
62
|
+
def eth() ether; end
|
63
|
+
|
64
|
+
end # class Integer
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
class Integer
|
5
|
+
|
6
|
+
######################################
|
7
|
+
## note: there's NO month (for now)!!!
|
8
|
+
## why? month might be 28,29,30,31 days
|
9
|
+
## use days e.g. 30.days or 31.days etc.
|
10
|
+
|
11
|
+
HOUR_IN_SECONDS = 60 * 60 # 60 minutes * 60 seconds
|
12
|
+
DAY_IN_SECONDS = 24 * HOUR_IN_SECONDS # 24 hours * 60 * 60
|
13
|
+
WEEK_IN_SECONDS = 7 * DAY_IN_SECONDS # 7 days * 24 * 60 * 60
|
14
|
+
FORTNIGHT_IN_SECONDS = 14 * DAY_IN_SECONDS # 14 days * 24 * 60 * 60
|
15
|
+
YEAR_IN_SECONDS = 365 * DAY_IN_SECONDS # 365 days * 24 * 60 * 60
|
16
|
+
|
17
|
+
def second() self; end
|
18
|
+
def minute() self * 60; end
|
19
|
+
def hour() self * HOUR_IN_SECONDS; end
|
20
|
+
def day() self * DAY_IN_SECONDS; end
|
21
|
+
def week() self * WEEK_IN_SECONDS; end
|
22
|
+
def fortnight() self * FORTNIGHT_IN_SECONDS; end
|
23
|
+
def year() self * YEAR_IN_SECONDS; end
|
24
|
+
|
25
|
+
########################################################
|
26
|
+
## alias - use alias or alias_method - why? why not?
|
27
|
+
def seconds() second; end
|
28
|
+
def secs() second; end
|
29
|
+
def sec() second; end
|
30
|
+
def s() second; end
|
31
|
+
|
32
|
+
def minutes() minute; end
|
33
|
+
def mins() minute; end
|
34
|
+
def min() minute; end
|
35
|
+
def m() minute; end
|
36
|
+
|
37
|
+
def hours() hour; end
|
38
|
+
def h() hour; end
|
39
|
+
|
40
|
+
def days() day; end
|
41
|
+
def d() day; end
|
42
|
+
|
43
|
+
def weeks() week; end
|
44
|
+
def w() week; end
|
45
|
+
|
46
|
+
def fortnights() fortnight; end
|
47
|
+
|
48
|
+
def years() year; end
|
49
|
+
def y() year; end
|
50
|
+
|
51
|
+
end # class Integer
|
data/lib/universum/universum.rb
CHANGED
@@ -1,255 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
|
4
|
-
|
5
|
-
def sha256( str )
|
6
|
-
Digest::SHA256.hexdigest( str )
|
7
|
-
end
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
module Address
|
12
|
-
def self.zero() '0x0000'; end
|
13
|
-
|
14
|
-
def address
|
15
|
-
if @address
|
16
|
-
@address
|
17
|
-
else
|
18
|
-
if is_a? Contract
|
19
|
-
## fix/todo: use/lookup proper addr from contract
|
20
|
-
## construct address for now from object_id
|
21
|
-
"0x#{(object_id << 1).to_s(16)}"
|
22
|
-
else ## assume Account
|
23
|
-
'0x0000'
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end # method address
|
27
|
-
|
28
|
-
def transfer( value ) ## @payable @public
|
29
|
-
## todo/fix: throw exception if insufficient funds
|
30
|
-
send( value ) # returns true/false
|
31
|
-
end
|
32
|
-
|
33
|
-
def send( value ) ## @payable @public
|
34
|
-
## todo/fix: assert value > 0
|
35
|
-
## todo/fix: add missing -= part in transfer!!!!
|
36
|
-
|
37
|
-
## use this (current contract) for debit (-) ammount
|
38
|
-
this._sub( value ) # sub(tract) / debit from the sender (current contract)
|
39
|
-
_add( value ) # add / credit to the recipient
|
40
|
-
end
|
41
|
-
|
42
|
-
def balance
|
43
|
-
@balance ||= 0 ## return 0 if undefined
|
44
|
-
end
|
45
|
-
|
46
|
-
### private (internal use only) methods - PLEASE do NOT use (use transfer/send)
|
47
|
-
def _sub( value )
|
48
|
-
@balance ||= 0 ## return 0 if undefined
|
49
|
-
@balance -= value
|
50
|
-
end
|
51
|
-
|
52
|
-
def _add( value )
|
53
|
-
@balance ||= 0 ## return 0 if undefined
|
54
|
-
@balance += value
|
55
|
-
end
|
56
|
-
end # class Address
|
57
|
-
|
58
|
-
|
59
|
-
## add dummy bool class for mapping and (payable) method signature
|
60
|
-
|
61
|
-
class Integer
|
62
|
-
def self.zero() 0; end
|
63
|
-
end
|
64
|
-
|
65
|
-
class Bool
|
66
|
-
def self.zero() false; end
|
67
|
-
end
|
68
|
-
|
69
|
-
class Money
|
70
|
-
def self.zero() 0; end
|
71
|
-
end
|
72
|
-
|
73
|
-
class Void ## only used (reserved) for (payable) method signature now
|
74
|
-
end
|
75
|
-
|
76
|
-
|
77
|
-
class Mapping
|
78
|
-
def self.of( *args )
|
79
|
-
## e.g. gets passed in [{Address=>Integer}]
|
80
|
-
## check for Integer - use Hash.new(0)
|
81
|
-
## check for Bool - use Hash.new(False)
|
82
|
-
if args[0].is_a? Hash
|
83
|
-
arg = args[0].to_a ## convert to array (for easier access)
|
84
|
-
if arg[0][1] == Integer
|
85
|
-
Hash.new( Integer.zero ) ## if key missing returns 0 and NOT nil (the default)
|
86
|
-
elsif arg[0][1] == Money
|
87
|
-
Hash.new( Money.zero ) ## if key missing returns 0 and NOT nil (the default)
|
88
|
-
elsif arg[0][1] == Bool
|
89
|
-
Hash.new( Bool.zero )
|
90
|
-
elsif arg[0][1] == Address
|
91
|
-
Hash.new( Address.zero )
|
92
|
-
else ## assume "standard" defaults
|
93
|
-
## todo: issue warning about unknown type - why? why not?
|
94
|
-
Hash.new
|
95
|
-
end
|
96
|
-
else
|
97
|
-
Hash.new ## that is, "plain" {} with all "standard" defaults
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
class Array
|
106
|
-
def self.of( *args )
|
107
|
-
Array.new ## that is, "plain" [] with all "standard" defaults
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
class String
|
113
|
-
def transfer( value )
|
114
|
-
## check if self is an address
|
115
|
-
if self.start_with?( '0x' )
|
116
|
-
Account[self].transfer( value )
|
117
|
-
else
|
118
|
-
raise "(Auto-)Type Conversion from Address (Hex) String to Account Failed; Expected String Starting with 0x got #{self}; Contract Halted (Stopped)"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def send( value )
|
123
|
-
## check if self is an address
|
124
|
-
if self.start_with?( '0x' )
|
125
|
-
Account[self].send( value )
|
126
|
-
else
|
127
|
-
raise "(Auto-)Type Conversion from Address (Hex) String to Account Failed; Expected String Starting with 0x got #{self}; Contract Halted (Stopped)"
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
|
133
|
-
class Account
|
134
|
-
|
135
|
-
@@directory = {}
|
136
|
-
def self.find_by_address( key )
|
137
|
-
## clean key (allow "embedded" name e.g 0x1111 (Alice))
|
138
|
-
key = key.gsub(/\(.+\)/, '' ).strip
|
139
|
-
@@directory[ key ]
|
140
|
-
end
|
141
|
-
def self.find( key ) find_by_address( key ); end # make find_by_address the default finder
|
142
|
-
def self.at( key) find_by_address( key ); end # another "classic" alias for find_by_address
|
143
|
-
|
144
|
-
def self.[]( key )
|
145
|
-
o = find_by_address( key )
|
146
|
-
if o
|
147
|
-
o
|
148
|
-
else
|
149
|
-
o = new( key )
|
150
|
-
## note: auto-register (new) address in (yellow page) directory
|
151
|
-
@@directory[ key ] = o
|
152
|
-
o
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def self.all() @@directory.values; end
|
157
|
-
|
158
|
-
|
159
|
-
####
|
160
|
-
# account (builtin) services / transaction methods
|
161
|
-
include Address ## includes address + send/transfer/balance
|
162
|
-
|
163
|
-
## note: for now allow write access too!!!
|
164
|
-
def balance=( value )
|
165
|
-
@balance = value
|
166
|
-
end
|
167
|
-
|
168
|
-
attr_reader :tx
|
169
|
-
def _auto_inc_tx() @tx += 1; end ## "internal" method - (auto) increment transaction (tx) counter
|
170
|
-
|
171
|
-
## note: needed by transfer/send
|
172
|
-
def this() Universum.this; end ## returns current contract
|
173
|
-
|
174
|
-
private
|
175
|
-
def initialize( address, balance: 0, tx: 0 )
|
176
|
-
@address = address # type address - (hex) string starts with 0x
|
177
|
-
@balance = balance # uint
|
178
|
-
@tx = tx # transaction (tx) count (used for nonce and replay attack protection)
|
179
|
-
end
|
180
|
-
|
181
|
-
end # class Account
|
182
|
-
|
183
|
-
|
184
|
-
class Contract
|
185
|
-
|
186
|
-
@@directory = {}
|
187
|
-
def self.find_by_address( key )
|
188
|
-
## clean key (allow "embedded" class name e.g 0x4de2ee8 (SatoshiDice))
|
189
|
-
key = key.gsub(/\(.+\)/, '' ).strip
|
190
|
-
@@directory[ key ];
|
191
|
-
end
|
192
|
-
def self.find( key ) find_by_address( key ); end # make find_by_address the default finder
|
193
|
-
def self.at( key) find_by_address( key ); end # another "classic" alias for find_by_address
|
194
|
-
def self.[]( key ) find_by_address( key ); end
|
195
|
-
|
196
|
-
def self.store( key, o ) @@directory.store( key, o ); end ## store (add) new contract object (o) to hash / directory
|
197
|
-
def self.all() @@directory.values; end
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
####
|
202
|
-
# account (builtin) services / transaction methods
|
203
|
-
include Address ## includes address + send/transfer/balance
|
204
|
-
|
205
|
-
## function sig(nature) macro for types (dummy for now)
|
206
|
-
# e.g. use like
|
207
|
-
# payable :process
|
208
|
-
# payable :initialize
|
209
|
-
# payable :bet, Integer
|
210
|
-
# payable :lend_money, Address => Bool ## returns Bool
|
211
|
-
def self.payable( *args ); end
|
212
|
-
|
213
|
-
payable :receive
|
214
|
-
|
215
|
-
####
|
216
|
-
# todo/double check: auto-add payable default fallback - why? why not?
|
217
|
-
def receive ## @payable default fallback - use different name - why? why not? (e.g. handle/process/etc.)
|
218
|
-
end
|
219
|
-
|
220
|
-
|
221
|
-
def assert( condition )
|
222
|
-
if condition == true
|
223
|
-
## do nothing
|
224
|
-
else
|
225
|
-
raise 'Contract Assertion Failed; Contract Halted (Stopped)'
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
|
230
|
-
def this() Universum.this; end ## returns current contract
|
231
|
-
def log( event ) Universum.log( event ); end
|
232
|
-
def msg() Universum.msg; end
|
233
|
-
def block() Universum.block; end
|
234
|
-
def blockhash( number )
|
235
|
-
## todo/fix: only allow going back 255 blocks; check if number is in range!!!
|
236
|
-
Universum.blockhash( number )
|
237
|
-
end
|
238
|
-
|
239
|
-
private
|
240
|
-
def selfdestruct( owner ) ## todo/check: use a different name e.g. destruct/ delete - why? why not?
|
241
|
-
## selfdestruct function (for clean-up on blockchain)
|
242
|
-
owner.send( @balance ) ## send back all funds owned/hold by contract
|
243
|
-
|
244
|
-
## fix: does nothing for now - add some code (e.g. cleanup)
|
245
|
-
## mark as destruct - why? why not?
|
246
|
-
end
|
247
|
-
|
248
|
-
end # class Contract
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
4
|
## blockchain message (msg) context
|
254
5
|
## includes: sender (address)
|
255
6
|
## todo: allow writable attribues e.g. sender - why? why not?
|
@@ -263,7 +14,6 @@ class Msg
|
|
263
14
|
end # class Msg
|
264
15
|
|
265
16
|
|
266
|
-
|
267
17
|
class Block
|
268
18
|
attr_reader :timestamp, :number
|
269
19
|
|
@@ -274,150 +24,6 @@ class Block
|
|
274
24
|
end # class Block
|
275
25
|
|
276
26
|
|
277
|
-
|
278
|
-
class Transaction
|
279
|
-
|
280
|
-
def self.send( **kwargs ) ## convenience helper for Uni.send_transaction
|
281
|
-
Universum.send_transaction( **kwargs )
|
282
|
-
end
|
283
|
-
|
284
|
-
|
285
|
-
attr_reader :from, :to, :value, :data, :nonce
|
286
|
-
|
287
|
-
def initialize( from:, to:, value:, data:, nonce: nil )
|
288
|
-
## note: from only allows accounts
|
289
|
-
if from.is_a?( Account )
|
290
|
-
account = from
|
291
|
-
else
|
292
|
-
account = Account.at( from ) ## lookup account by address
|
293
|
-
end
|
294
|
-
|
295
|
-
@from = account.address
|
296
|
-
|
297
|
-
if to.is_a?( Contract )
|
298
|
-
@to = "#{to.address} (#{to.class.name})"
|
299
|
-
elsif to.is_a?( Account ) ## note: to allows Contracts AND Accounts
|
300
|
-
@to = to.address
|
301
|
-
else
|
302
|
-
@to = to # might be a contract or account (pass through for now)
|
303
|
-
end
|
304
|
-
|
305
|
-
@value = value
|
306
|
-
@data = data
|
307
|
-
|
308
|
-
if nonce
|
309
|
-
@nonce = nonce
|
310
|
-
else
|
311
|
-
## auto-add nonce (that is, tx counter - auto-increment)
|
312
|
-
@nonce = account.tx ## get transaction (tx) counter (starts with 0)
|
313
|
-
account._auto_inc_tx
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
def log_str
|
318
|
-
## for debug add transaction (tx) args (e.g. from, value, etc.)
|
319
|
-
tx_args_str = ""
|
320
|
-
tx_args_str << "from: #{@from} ##{@nonce}"
|
321
|
-
tx_args_str << ", value: #{@value}" if @value > 0
|
322
|
-
|
323
|
-
if @to == '0x0000' ## special case - contract creation transaction
|
324
|
-
klass = @data[0] ## contract class - todo/fix: check if data[] is a contract class!!!
|
325
|
-
call_args = @data[1..-1] ## arguments
|
326
|
-
|
327
|
-
## convert all args to string (with inspect) for debugging
|
328
|
-
## check if pretty_inspect adds trailing newline? why? why not? possible?
|
329
|
-
call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
|
330
|
-
|
331
|
-
"#{tx_args_str} => to: #{@to} create contract #{klass.name}.new( #{call_args_str} )"
|
332
|
-
else
|
333
|
-
if @data.empty? ## assume receive (default method) for now if data empty (no method specified)
|
334
|
-
"#{tx_args_str} => to: #{@to} call default fallback"
|
335
|
-
else
|
336
|
-
m = @data[0] ## method name / signature
|
337
|
-
call_args = @data[1..-1] ## arguments
|
338
|
-
|
339
|
-
## convert all args to string (with inspect) for debugging
|
340
|
-
## check if pretty_inspect adds trailing newline? why? why not? possible?
|
341
|
-
call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
|
342
|
-
|
343
|
-
"#{tx_args_str} => to: #{@to} call #{m}( #{call_args_str} )"
|
344
|
-
end
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
|
349
|
-
def receipt
|
350
|
-
Receipt.find( self )
|
351
|
-
end
|
352
|
-
|
353
|
-
def contract # convenience helper (quick contract lookup)
|
354
|
-
rec = receipt
|
355
|
-
if rec
|
356
|
-
rec.contract
|
357
|
-
else
|
358
|
-
nil
|
359
|
-
end
|
360
|
-
end
|
361
|
-
end # class Transaction
|
362
|
-
|
363
|
-
Tx = Transaction ## add some convenience aliases (still undecided what's the most popular :-)
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
class Receipt ## transaction receipt
|
369
|
-
|
370
|
-
@@directory = {}
|
371
|
-
def self.find( tx )
|
372
|
-
key = "#{tx.from}/#{tx.nonce}"
|
373
|
-
@@directory[ key ];
|
374
|
-
end
|
375
|
-
def self.[]( tx ) find( tx ); end
|
376
|
-
|
377
|
-
def self.store( o )
|
378
|
-
key = "#{o.from}/#{o.nonce}"
|
379
|
-
@@directory.store( key, o ); end ## store (add) new receipt object (o) to hash / directory
|
380
|
-
def self.all() @@directory.values; end
|
381
|
-
|
382
|
-
|
383
|
-
## required attributes / fields
|
384
|
-
attr_reader :nonce, :from, :to, :value,
|
385
|
-
:block_number
|
386
|
-
## optional
|
387
|
-
attr_reader :contract_address
|
388
|
-
|
389
|
-
def initialize( tx:,
|
390
|
-
block:,
|
391
|
-
contract: nil )
|
392
|
-
@nonce = tx.nonce
|
393
|
-
@from = tx.from
|
394
|
-
@to = tx.to
|
395
|
-
@value = tx.value
|
396
|
-
## todo/fix: add data too!!!
|
397
|
-
|
398
|
-
@block_number = block.number
|
399
|
-
## todo/fix: add block_hash
|
400
|
-
|
401
|
-
if contract
|
402
|
-
## note: for easier debugging add class name in () to address (needs to get stripped away in lookup)
|
403
|
-
@contract_address = "#{contract.address} (#{contract.class.name})"
|
404
|
-
else
|
405
|
-
@contract_address = nil
|
406
|
-
end
|
407
|
-
end
|
408
|
-
|
409
|
-
def contract # convenience helper (quick contract lookup)
|
410
|
-
if @contract_address
|
411
|
-
Contract.find( @contract_address )
|
412
|
-
else
|
413
|
-
nil
|
414
|
-
end
|
415
|
-
end
|
416
|
-
end
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
27
|
class Universum ## Uni short for Universum
|
422
28
|
## convenience helpers
|
423
29
|
|