universum 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13cb94b578f32502fa462ae5244745a0acaed5fa
4
- data.tar.gz: 70e43de209bc06bd7f9d2e8dd3ae4e53ba237b9a
3
+ metadata.gz: 47cb21b47f820960440d6b734508522afd0669f5
4
+ data.tar.gz: f95c32cd1cc6fc27aae94a40dabd0638215b9886
5
5
  SHA512:
6
- metadata.gz: c51e13fbc51f64d5f56dd62e2b731986674124a309df6eb2fe7f3f8c68a423363610a64d40df8e03b9f5bd091a5c2bec89b7d71156c5a32f594d2f095a447d42
7
- data.tar.gz: 4bcd6d2df8a752af20d92883ce309d4796161341be7927cdaa027af1bde78b45fd545050bb296599ad58aff7f3233f6b64d70d41f15f7423d1da91b1b37d8421
6
+ metadata.gz: 431ac7abd03c738136fc014cf36e8646d29f4a1ed07dc10d69701e0d6317660224ec579d75f5ee38675ee51bc17050682b5c507cb7dfa07ef1146d595d4dc13f
7
+ data.tar.gz: b9d01fa01a6d3b4c3ab569748052477faf9fcdfe1bebb06fcf2284d6e4e9186d5907c1b520079c95adb28d0cee657774b0f0714d85bd63a660edc01ce16bb713
File without changes
@@ -0,0 +1,116 @@
1
+ CC0 1.0 Universal
2
+
3
+ Statement of Purpose
4
+
5
+ The laws of most jurisdictions throughout the world automatically confer
6
+ exclusive Copyright and Related Rights (defined below) upon the creator and
7
+ subsequent owner(s) (each and all, an "owner") of an original work of
8
+ authorship and/or a database (each, a "Work").
9
+
10
+ Certain owners wish to permanently relinquish those rights to a Work for the
11
+ purpose of contributing to a commons of creative, cultural and scientific
12
+ works ("Commons") that the public can reliably and without fear of later
13
+ claims of infringement build upon, modify, incorporate in other works, reuse
14
+ and redistribute as freely as possible in any form whatsoever and for any
15
+ purposes, including without limitation commercial purposes. These owners may
16
+ contribute to the Commons to promote the ideal of a free culture and the
17
+ further production of creative, cultural and scientific works, or to gain
18
+ reputation or greater distribution for their Work in part through the use and
19
+ efforts of others.
20
+
21
+ For these and/or other purposes and motivations, and without any expectation
22
+ of additional consideration or compensation, the person associating CC0 with a
23
+ Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24
+ and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25
+ and publicly distribute the Work under its terms, with knowledge of his or her
26
+ Copyright and Related Rights in the Work and the meaning and intended legal
27
+ effect of CC0 on those rights.
28
+
29
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
30
+ protected by copyright and related or neighboring rights ("Copyright and
31
+ Related Rights"). Copyright and Related Rights include, but are not limited
32
+ to, the following:
33
+
34
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
35
+ and translate a Work;
36
+
37
+ ii. moral rights retained by the original author(s) and/or performer(s);
38
+
39
+ iii. publicity and privacy rights pertaining to a person's image or likeness
40
+ depicted in a Work;
41
+
42
+ iv. rights protecting against unfair competition in regards to a Work,
43
+ subject to the limitations in paragraph 4(a), below;
44
+
45
+ v. rights protecting the extraction, dissemination, use and reuse of data in
46
+ a Work;
47
+
48
+ vi. database rights (such as those arising under Directive 96/9/EC of the
49
+ European Parliament and of the Council of 11 March 1996 on the legal
50
+ protection of databases, and under any national implementation thereof,
51
+ including any amended or successor version of such directive); and
52
+
53
+ vii. other similar, equivalent or corresponding rights throughout the world
54
+ based on applicable law or treaty, and any national implementations thereof.
55
+
56
+ 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59
+ and Related Rights and associated claims and causes of action, whether now
60
+ known or unknown (including existing as well as future claims and causes of
61
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
62
+ duration provided by applicable law or treaty (including future time
63
+ extensions), (iii) in any current or future medium and for any number of
64
+ copies, and (iv) for any purpose whatsoever, including without limitation
65
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66
+ the Waiver for the benefit of each member of the public at large and to the
67
+ detriment of Affirmer's heirs and successors, fully intending that such Waiver
68
+ shall not be subject to revocation, rescission, cancellation, termination, or
69
+ any other legal or equitable action to disrupt the quiet enjoyment of the Work
70
+ by the public as contemplated by Affirmer's express Statement of Purpose.
71
+
72
+ 3. Public License Fallback. Should any part of the Waiver for any reason be
73
+ judged legally invalid or ineffective under applicable law, then the Waiver
74
+ shall be preserved to the maximum extent permitted taking into account
75
+ Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76
+ is so judged Affirmer hereby grants to each affected person a royalty-free,
77
+ non transferable, non sublicensable, non exclusive, irrevocable and
78
+ unconditional license to exercise Affirmer's Copyright and Related Rights in
79
+ the Work (i) in all territories worldwide, (ii) for the maximum duration
80
+ provided by applicable law or treaty (including future time extensions), (iii)
81
+ in any current or future medium and for any number of copies, and (iv) for any
82
+ purpose whatsoever, including without limitation commercial, advertising or
83
+ promotional purposes (the "License"). The License shall be deemed effective as
84
+ of the date CC0 was applied by Affirmer to the Work. Should any part of the
85
+ License for any reason be judged legally invalid or ineffective under
86
+ applicable law, such partial invalidity or ineffectiveness shall not
87
+ invalidate the remainder of the License, and in such case Affirmer hereby
88
+ affirms that he or she will not (i) exercise any of his or her remaining
89
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
90
+ and causes of action with respect to the Work, in either case contrary to
91
+ Affirmer's express Statement of Purpose.
92
+
93
+ 4. Limitations and Disclaimers.
94
+
95
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
96
+ surrendered, licensed or otherwise affected by this document.
97
+
98
+ b. Affirmer offers the Work as-is and makes no representations or warranties
99
+ of any kind concerning the Work, express, implied, statutory or otherwise,
100
+ including without limitation warranties of title, merchantability, fitness
101
+ for a particular purpose, non infringement, or the absence of latent or
102
+ other defects, accuracy, or the present or absence of errors, whether or not
103
+ discoverable, all to the greatest extent permissible under applicable law.
104
+
105
+ c. Affirmer disclaims responsibility for clearing rights of other persons
106
+ that may apply to the Work or any use thereof, including without limitation
107
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
108
+ disclaims responsibility for obtaining any necessary consents, permissions
109
+ or other rights required for any use of the Work.
110
+
111
+ d. Affirmer understands and acknowledges that Creative Commons is not a
112
+ party to this document and has no duty or obligation with respect to this
113
+ CC0 or use of the Work.
114
+
115
+ For more information, please see
116
+ <http://creativecommons.org/publicdomain/zero/1.0/
@@ -1,6 +1,13 @@
1
- HISTORY.md
1
+ CHANGELOG.md
2
+ LICENSE.md
2
3
  Manifest.txt
3
4
  README.md
4
5
  Rakefile
5
6
  lib/universum.rb
7
+ lib/universum/universum.rb
6
8
  lib/universum/version.rb
9
+ test/contracts/greeter.rb
10
+ test/contracts/mytoken.rb
11
+ test/helper.rb
12
+ test/test_greeter.rb
13
+ test/test_version.rb
data/README.md CHANGED
@@ -1,13 +1,178 @@
1
- # Universum
1
+ New to Universum? See the [Universum (World Computer) White Paper](https://github.com/openblockchains/universum/blob/master/WHITEPAPER.md)!
2
+
3
+
4
+
5
+ # Universum (Ruby Edition)
2
6
 
3
7
  next generation ethereum 2.0 world computer runtime - run contract scripts / transactions in plain vanilla / standard ruby on the blockchain - update the (contract-protected / isolated) world states with plain vanilla / standard SQL
4
8
 
5
9
 
10
+ * home :: [github.com/openblockchains/universum](https://github.com/openblockchains/universum)
11
+ * bugs :: [github.com/openblockchains/universum/issues](https://github.com/openblockchains/universum/issues)
12
+ * gem :: [rubygems.org/gems/universum](https://rubygems.org/gems/universum)
13
+ * rdoc :: [rubydoc.info/gems/universum](http://rubydoc.info/gems/universum)
14
+
6
15
 
7
16
  ## Usage
8
17
 
9
- to be done
10
18
 
19
+ ### The Greeter - Your Digital Pal Who's Fun to Be With
20
+
21
+ Let's use the ["Create a digital greeter"](https://www.ethereum.org/greeter) starter example from Ethereum
22
+ to compare contracts in Solidity with contracts in Ruby:
23
+
24
+
25
+
26
+ ``` solidity
27
+ pragma solidity >=0.4.22 <0.6.0;
28
+
29
+ contract Mortal {
30
+ /* Define variable owner of the type address */
31
+ address owner;
32
+
33
+ /* This constructor is executed at initialization and sets the owner of the contract */
34
+ constructor() public { owner = msg.sender; }
35
+
36
+ /* Function to recover the funds on the contract */
37
+ function kill() public { if (msg.sender == owner) selfdestruct(msg.sender); }
38
+ }
39
+
40
+ contract Greeter is Mortal {
41
+ /* Define variable greeting of the type string */
42
+ string greeting;
43
+
44
+ /* This runs when the contract is executed */
45
+ constructor(string memory _greeting) public {
46
+ greeting = _greeting;
47
+ }
48
+
49
+ /* Main function */
50
+ function greet() public view returns (string memory) {
51
+ return greeting;
52
+ }
53
+ }
54
+ ```
55
+
56
+ and
57
+
58
+ ``` ruby
59
+ class Greeter < Contract
60
+
61
+ def initialize( greeting )
62
+ @owner = msg.sender
63
+ @greeting = greeting
64
+ end
65
+
66
+ def greet
67
+ @greeting
68
+ end
69
+
70
+ def kill
71
+ selfdestruct( msg.sender ) if msg.sender == @owner
72
+ end
73
+ end # class Greeter
74
+ ```
75
+
76
+ (Source: [`contracts/greeter.rb`](test/contracts/greeter.rb))
77
+
78
+
79
+
80
+ And let's run the greeter with Universum (Uni):
81
+
82
+ ``` ruby
83
+ require 'universum'
84
+
85
+ require_relative 'greeter'
86
+
87
+
88
+ Account['0x1111'] ## setup a test account
89
+
90
+ tx = Uni.send_transaction( from: '0x1111', data: [Greeter, 'Hello World!'] )
91
+ greeter = tx.receipt.contract
92
+
93
+ puts greeter.greet
94
+ #=> Hello World!
95
+
96
+ tx = Uni.send_transaction( from: '0x1111', data: [Greeter, '¡Hola, mundo!'] )
97
+ greeter_es = Receipt[ tx ].contract
98
+
99
+ puts greeter_es.greet
100
+ #=> ¡Hola, mundo!
101
+
102
+ puts greeter.greet
103
+ #=> Hello World!
104
+ puts greeter_es.greet
105
+ #=> ¡Hola, mundo!
106
+ ...
107
+ ```
108
+
109
+
110
+
111
+ ### The Coin - Minimum Viable Token
112
+
113
+ What's next? Let's use the ["Create your own crypto-currency"](https://www.ethereum.org/token) example from Ethereum
114
+ to compare contracts in Solidity with contracts in Ruby again:
115
+
116
+ ``` solidity
117
+ pragma solidity >=0.4.22 <0.6.0;
118
+
119
+ contract MyToken {
120
+ /* This creates an array with all balances */
121
+ mapping (address => uint256) public balanceOf;
122
+
123
+ /* Initializes contract with initial supply tokens to the creator of the contract */
124
+ constructor(
125
+ uint256 initialSupply
126
+ ) public {
127
+ balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens
128
+ }
129
+
130
+ /* Send coins */
131
+ function transfer(address _to, uint256 _value) public returns (bool success) {
132
+ require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
133
+ require(balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows
134
+ balanceOf[msg.sender] -= _value; // Subtract from the sender
135
+ balanceOf[_to] += _value; // Add the same to the recipient
136
+ return true;
137
+ }
138
+ }
139
+ ```
140
+
141
+ and
142
+
143
+ ``` ruby
144
+ class MyToken < Contract
145
+
146
+ def initialize( initial_supply )
147
+ @balance_of = Mapping.of( Address => Money )
148
+ @balance_of[ msg.sender] = initial_supply
149
+ end
150
+
151
+ def transfer( to, value )
152
+ assert( @balance_of[ msg.sender ] >= value )
153
+ assert( @balance_of[ to ] + value >= @balance_of[ to ] )
154
+
155
+ @balance_of[ msg.sender ] -= value
156
+ @balance_of[ to ] += value
157
+
158
+ return true
159
+ end
160
+ end # class MyToken
161
+ ```
162
+
163
+ (Source: [`contracts/mytoken.rb`](test/contracts/mytoken.rb))
164
+
165
+
166
+
167
+
168
+ ## More Contract Samples
169
+
170
+ See the [`/universum-contracts`](https://github.com/openblockchains/universum-contracts) collection.
171
+
172
+
173
+ ## More Documentation / Articles / Contracts
174
+
175
+ [Programming Crypto Blockchain Contracts Step-by-Step Book / Guide](https://github.com/openblockchains/programming-cryptocontracts) - Let's Start with Ponzi & Pyramid Schemes. Run Your Own Lotteries, Gambling Casinos and more on the Blockchain World Computer...
11
176
 
12
177
 
13
178
 
@@ -15,7 +180,9 @@ to be done
15
180
 
16
181
  Just install the gem:
17
182
 
18
- $ gem install universum
183
+ ```
184
+ $ gem install universum
185
+ ```
19
186
 
20
187
 
21
188
  ## License
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ Hoe.spec 'universum' do
15
15
 
16
16
  # switch extension to .markdown for gihub formatting
17
17
  self.readme_file = 'README.md'
18
- self.history_file = 'HISTORY.md'
18
+ self.history_file = 'CHANGELOG.md'
19
19
 
20
20
  self.extra_deps = [
21
21
  ]
@@ -1,11 +1,10 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'pp'
4
-
5
3
 
6
4
  ## our own code
7
5
  require 'universum/version' # note: let version always go first
8
6
 
7
+ require 'universum/universum'
8
+
9
9
 
10
- pp Universum.banner
11
- pp Universum.root
10
+ puts Universum.banner ## say hello
@@ -0,0 +1,588 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ ## stdlibs
5
+ require 'pp' ## pretty print (pp)
6
+ require 'digest'
7
+
8
+
9
+ def sha256( str )
10
+ Digest::SHA256.hexdigest( str )
11
+ end
12
+
13
+
14
+
15
+ module Address
16
+ def self.zero() '0x0000'; end
17
+
18
+ def address
19
+ if @address
20
+ @address
21
+ else
22
+ if is_a? Contract
23
+ ## fix/todo: use/lookup proper addr from contract
24
+ ## construct address for now from object_id
25
+ "0x#{(object_id << 1).to_s(16)}"
26
+ else ## assume Account
27
+ '0x0000'
28
+ end
29
+ end
30
+ end # method address
31
+
32
+ def transfer( value ) ## @payable @public
33
+ ## todo/fix: throw exception if insufficient funds
34
+ send( value ) # returns true/false
35
+ end
36
+
37
+ def send( value ) ## @payable @public
38
+ ## todo/fix: assert value > 0
39
+ ## todo/fix: add missing -= part in transfer!!!!
40
+
41
+ ## use this (current contract) for debit (-) ammount
42
+ this._sub( value ) # sub(tract) / debit from the sender (current contract)
43
+ _add( value ) # add / credit to the recipient
44
+ end
45
+
46
+ def balance
47
+ @balance ||= 0 ## return 0 if undefined
48
+ end
49
+
50
+ ### private (internal use only) methods - PLEASE do NOT use (use transfer/send)
51
+ def _sub( value )
52
+ @balance ||= 0 ## return 0 if undefined
53
+ @balance -= value
54
+ end
55
+
56
+ def _add( value )
57
+ @balance ||= 0 ## return 0 if undefined
58
+ @balance += value
59
+ end
60
+ end # class Address
61
+
62
+
63
+ ## add dummy bool class for mapping and (payable) method signature
64
+
65
+ class Integer
66
+ def self.zero() 0; end
67
+ end
68
+
69
+ class Bool
70
+ def self.zero() false; end
71
+ end
72
+
73
+ class Money
74
+ def self.zero() 0; end
75
+ end
76
+
77
+ class Void ## only used (reserved) for (payable) method signature now
78
+ end
79
+
80
+
81
+ class Mapping
82
+ def self.of( *args )
83
+ ## e.g. gets passed in [{Address=>Integer}]
84
+ ## check for Integer - use Hash.new(0)
85
+ ## check for Bool - use Hash.new(False)
86
+ if args[0].is_a? Hash
87
+ arg = args[0].to_a ## convert to array (for easier access)
88
+ if arg[0][1] == Integer
89
+ Hash.new( Integer.zero ) ## if key missing returns 0 and NOT nil (the default)
90
+ elsif arg[0][1] == Money
91
+ Hash.new( Money.zero ) ## if key missing returns 0 and NOT nil (the default)
92
+ elsif arg[0][1] == Bool
93
+ Hash.new( Bool.zero )
94
+ elsif arg[0][1] == Address
95
+ Hash.new( Address.zero )
96
+ else ## assume "standard" defaults
97
+ ## todo: issue warning about unknown type - why? why not?
98
+ Hash.new
99
+ end
100
+ else
101
+ Hash.new ## that is, "plain" {} with all "standard" defaults
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+
108
+
109
+ class Array
110
+ def self.of( *args )
111
+ Array.new ## that is, "plain" [] with all "standard" defaults
112
+ end
113
+ end
114
+
115
+
116
+ class String
117
+ def transfer( value )
118
+ ## check if self is an address
119
+ if self.start_with?( '0x' )
120
+ Account[self].transfer( value )
121
+ else
122
+ raise "(Auto-)Type Conversion from Address (Hex) String to Account Failed; Expected String Starting with 0x got #{self}; Contract Halted (Stopped)"
123
+ end
124
+ end
125
+
126
+ def send( value )
127
+ ## check if self is an address
128
+ if self.start_with?( '0x' )
129
+ Account[self].send( value )
130
+ else
131
+ raise "(Auto-)Type Conversion from Address (Hex) String to Account Failed; Expected String Starting with 0x got #{self}; Contract Halted (Stopped)"
132
+ end
133
+ end
134
+ end
135
+
136
+
137
+ class Account
138
+
139
+ @@directory = {}
140
+ def self.find_by_address( key )
141
+ ## clean key (allow "embedded" name e.g 0x1111 (Alice))
142
+ key = key.gsub(/\(.+\)/, '' ).strip
143
+ @@directory[ key ]
144
+ end
145
+ def self.find( key ) find_by_address( key ); end # make find_by_address the default finder
146
+ def self.at( key) find_by_address( key ); end # another "classic" alias for find_by_address
147
+
148
+ def self.[]( key )
149
+ o = find_by_address( key )
150
+ if o
151
+ o
152
+ else
153
+ o = new( key )
154
+ ## note: auto-register (new) address in (yellow page) directory
155
+ @@directory[ key ] = o
156
+ o
157
+ end
158
+ end
159
+
160
+ def self.all() @@directory.values; end
161
+
162
+
163
+ ####
164
+ # account (builtin) services / transaction methods
165
+ include Address ## includes address + send/transfer/balance
166
+
167
+ ## note: for now allow write access too!!!
168
+ def balance=( value )
169
+ @balance = value
170
+ end
171
+
172
+ attr_reader :tx
173
+ def _auto_inc_tx() @tx += 1; end ## "internal" method - (auto) increment transaction (tx) counter
174
+
175
+ ## note: needed by transfer/send
176
+ def this() Universum.this; end ## returns current contract
177
+
178
+ private
179
+ def initialize( address, balance: 0, tx: 0 )
180
+ @address = address # type address - (hex) string starts with 0x
181
+ @balance = balance # uint
182
+ @tx = tx # transaction (tx) count (used for nonce and replay attack protection)
183
+ end
184
+
185
+ end # class Account
186
+
187
+
188
+ class Contract
189
+
190
+ @@directory = {}
191
+ def self.find_by_address( key )
192
+ ## clean key (allow "embedded" class name e.g 0x4de2ee8 (SatoshiDice))
193
+ key = key.gsub(/\(.+\)/, '' ).strip
194
+ @@directory[ key ];
195
+ end
196
+ def self.find( key ) find_by_address( key ); end # make find_by_address the default finder
197
+ def self.at( key) find_by_address( key ); end # another "classic" alias for find_by_address
198
+ def self.[]( key ) find_by_address( key ); end
199
+
200
+ def self.store( key, o ) @@directory.store( key, o ); end ## store (add) new contract object (o) to hash / directory
201
+ def self.all() @@directory.values; end
202
+
203
+
204
+
205
+ ####
206
+ # account (builtin) services / transaction methods
207
+ include Address ## includes address + send/transfer/balance
208
+
209
+ ## function sig(nature) macro for types (dummy for now)
210
+ # e.g. use like
211
+ # payable :process
212
+ # payable :initialize
213
+ # payable :bet, Integer
214
+ # payable :lend_money, Address => Bool ## returns Bool
215
+ def self.payable( *args ); end
216
+
217
+ payable :receive
218
+
219
+ ####
220
+ # todo/double check: auto-add payable default fallback - why? why not?
221
+ def receive ## @payable default fallback - use different name - why? why not? (e.g. handle/process/etc.)
222
+ end
223
+
224
+
225
+ def assert( condition )
226
+ if condition == true
227
+ ## do nothing
228
+ else
229
+ raise 'Contract Assertion Failed; Contract Halted (Stopped)'
230
+ end
231
+ end
232
+
233
+
234
+ def this() Universum.this; end ## returns current contract
235
+ def log( event ) Universum.log( event ); end
236
+ def msg() Universum.msg; end
237
+ def block() Universum.block; end
238
+ def blockhash( number )
239
+ ## todo/fix: only allow going back 255 blocks; check if number is in range!!!
240
+ Universum.blockhash( number )
241
+ end
242
+
243
+ private
244
+ def selfdestruct( owner ) ## todo/check: use a different name e.g. destruct/ delete - why? why not?
245
+ ## selfdestruct function (for clean-up on blockchain)
246
+ owner.send( @balance ) ## send back all funds owned/hold by contract
247
+
248
+ ## fix: does nothing for now - add some code (e.g. cleanup)
249
+ ## mark as destruct - why? why not?
250
+ end
251
+
252
+ end # class Contract
253
+
254
+
255
+
256
+
257
+ ## blockchain message (msg) context
258
+ ## includes: sender (address)
259
+ ## todo: allow writable attribues e.g. sender - why? why not?
260
+ class Msg
261
+ attr_reader :sender, :value
262
+
263
+ def initialize( sender: '0x0000', value: 0 )
264
+ @sender = sender
265
+ @value = value
266
+ end
267
+ end # class Msg
268
+
269
+
270
+
271
+ class Block
272
+ attr_reader :timestamp, :number
273
+
274
+ def initialize( timestamp: Time.now.to_i, number: 0 )
275
+ @timestamp = timestamp # unix epoch time (in seconds since 1970)
276
+ @number = number # block height (start with 0 - genesis block)
277
+ end
278
+ end # class Block
279
+
280
+
281
+
282
+ class Transaction
283
+ attr_reader :from, :to, :value, :data, :nonce
284
+
285
+ def initialize( from:, to:, value:, data:, nonce: nil )
286
+ ## note: from only allows accounts
287
+ if from.is_a?( Account )
288
+ account = from
289
+ else
290
+ account = Account.at( from ) ## lookup account by address
291
+ end
292
+
293
+ @from = account.address
294
+
295
+ if to.is_a?( Contract )
296
+ @to = "#{to.address} (#{to.class.name})"
297
+ elsif to.is_a?( Account ) ## note: to allows Contracts AND Accounts
298
+ @to = to.address
299
+ else
300
+ @to = to # might be a contract or account (pass through for now)
301
+ end
302
+
303
+ @value = value
304
+ @data = data
305
+
306
+ if nonce
307
+ @nonce = nonce
308
+ else
309
+ ## auto-add nonce (that is, tx counter - auto-increment)
310
+ @nonce = account.tx ## get transaction (tx) counter (starts with 0)
311
+ account._auto_inc_tx
312
+ end
313
+ end
314
+
315
+ def log_str
316
+ ## for debug add transaction (tx) args (e.g. from, value, etc.)
317
+ tx_args_str = ""
318
+ tx_args_str << "from: #{@from} ##{@nonce}"
319
+ tx_args_str << ", value: #{@value}" if @value > 0
320
+
321
+ if @to == '0x0000' ## special case - contract creation transaction
322
+ klass = @data[0] ## contract class - todo/fix: check if data[] is a contract class!!!
323
+ call_args = @data[1..-1] ## arguments
324
+
325
+ ## convert all args to string (with inspect) for debugging
326
+ ## check if pretty_inspect adds trailing newline? why? why not? possible?
327
+ call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
328
+
329
+ "#{tx_args_str} => to: #{@to} create contract #{klass.name}.new( #{call_args_str} )"
330
+ else
331
+ if @data.empty? ## assume receive (default method) for now if data empty (no method specified)
332
+ "#{tx_args_str} => to: #{@to} call default fallback"
333
+ else
334
+ m = @data[0] ## method name / signature
335
+ call_args = @data[1..-1] ## arguments
336
+
337
+ ## convert all args to string (with inspect) for debugging
338
+ ## check if pretty_inspect adds trailing newline? why? why not? possible?
339
+ call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
340
+
341
+ "#{tx_args_str} => to: #{@to} call #{m}( #{call_args_str} )"
342
+ end
343
+ end
344
+ end
345
+
346
+
347
+ def receipt
348
+ Receipt.find( self )
349
+ end
350
+
351
+ def contract # convenience helper (quick contract lookup)
352
+ rec = receipt
353
+ if rec
354
+ rec.contract
355
+ else
356
+ nil
357
+ end
358
+ end
359
+ end # class Transaction
360
+
361
+ Tx = Transaction ## add some convenience aliases (still undecided what's the most popular :-)
362
+
363
+
364
+
365
+
366
+ class Receipt ## transaction receipt
367
+
368
+ @@directory = {}
369
+ def self.find( tx )
370
+ key = "#{tx.from}/#{tx.nonce}"
371
+ @@directory[ key ];
372
+ end
373
+ def self.[]( tx ) find( tx ); end
374
+
375
+ def self.store( o )
376
+ key = "#{o.from}/#{o.nonce}"
377
+ @@directory.store( key, o ); end ## store (add) new receipt object (o) to hash / directory
378
+ def self.all() @@directory.values; end
379
+
380
+
381
+ ## required attributes / fields
382
+ attr_reader :nonce, :from, :to, :value,
383
+ :block_number
384
+ ## optional
385
+ attr_reader :contract_address
386
+
387
+ def initialize( tx:,
388
+ block:,
389
+ contract: nil )
390
+ @nonce = tx.nonce
391
+ @from = tx.from
392
+ @to = tx.to
393
+ @value = tx.value
394
+ ## todo/fix: add data too!!!
395
+
396
+ @block_number = block.number
397
+ ## todo/fix: add block_hash
398
+
399
+ if contract
400
+ ## note: for easier debugging add class name in () to address (needs to get stripped away in lookup)
401
+ @contract_address = "#{contract.address} (#{contract.class.name})"
402
+ else
403
+ @contract_address = nil
404
+ end
405
+ end
406
+
407
+ def contract # convenience helper (quick contract lookup)
408
+ if @contract_address
409
+ Contract.find( @contract_address )
410
+ else
411
+ nil
412
+ end
413
+ end
414
+ end
415
+
416
+
417
+ ## base class for events
418
+ class Event
419
+ ## return a new Struct-like read-only class
420
+ def self.build_class( *fields )
421
+ klass = Class.new( Event ) do
422
+ define_method( :initialize ) do |*args|
423
+ fields.zip( args ).each do |field, arg|
424
+ instance_variable_set( "@#{field}", arg )
425
+ end
426
+ end
427
+
428
+ fields.each do |field|
429
+ define_method( field ) do
430
+ instance_variable_get( "@#{field}" )
431
+ end
432
+ end
433
+ end
434
+
435
+ ## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
436
+ klass.define_singleton_method( :new ) do |*args|
437
+ old_new( *args )
438
+ end
439
+
440
+ klass
441
+ end
442
+
443
+ class << self
444
+ alias_method :old_new, :new # note: store "old" orginal version of new
445
+ alias_method :new, :build_class # replace original version with create
446
+ end
447
+ end # class Event
448
+
449
+
450
+ class Universum ## Uni short for Universum
451
+ ## convenience helpers
452
+
453
+ def self.send_transaction( from:, to: '0x0000', value: 0, data: [] )
454
+ counter = @@counter ||= 0 ## total tx counter for debugging (start with 0)
455
+ @@counter += 1
456
+
457
+ ## note: always lookup (use) account for now
458
+ if from.is_a?( Account )
459
+ account = from
460
+ else
461
+ account = Account.at( from )
462
+ end
463
+
464
+ ## setup contract msg context
465
+ self.msg = { sender: account.address, value: value }
466
+
467
+
468
+ ## allow shortcut for Class (without ctor arguments) - no need to wrap in array
469
+ data = [data] if data.is_a? Class
470
+
471
+ tx = Transaction.new( from: account, to: to, value: value, data: data )
472
+
473
+
474
+ ## special case - contract creation transaction
475
+ if to == '0x0000'
476
+ klass = data[0] ## contract class - todo/fix: check if data[] is a contract class!!!
477
+ args = data[1..-1] ## arguments
478
+
479
+ puts "** tx ##{counter} (block ##{block.number}): #{tx.log_str}"
480
+ contract = klass.new( *args ) ## note: balance and this (and msg.send/transfer) NOT available/possible !!!!
481
+
482
+ if value > 0
483
+ ## move value to msg (todo/fix: restore if exception)
484
+ account._sub( value ) # (sub)tract / debit from the sender (account)
485
+ contract._add( value ) # add / credit to the recipient
486
+ end
487
+
488
+ puts " new #{contract.class.name} contract adddress: #{contract.address.inspect}"
489
+ ## add new contract to (lookup) directory
490
+ Contract.store( contract.address, contract )
491
+
492
+ ## issue (mined) transaction receipt
493
+ receipt = Receipt.new( tx: tx,
494
+ block: block,
495
+ contract: contract )
496
+ else
497
+ if value > 0
498
+ ## move value to msg (todo/fix: restore if exception)
499
+ account._sub( value ) # (sub)tract / debit from the sender (account)
500
+ to._add( value ) # add / credit to the recipient
501
+ end
502
+
503
+ self.this = to ## assumes for now that to is always a contract (and NOT an account)!!!
504
+
505
+ data = [:receive] if data.empty? ## assume receive (default method) for now if data empty (no method specified)
506
+
507
+ m = data[0] ## method name / signature
508
+ args = data[1..-1] ## arguments
509
+
510
+ puts "** tx ##{counter} (block ##{block.number}): #{tx.log_str}"
511
+
512
+ to.__send__( m, *args ) ## note: use __send__ to avoid clash with send( value ) for sending payments!!!
513
+
514
+ ## issue (mined) transaction receipt
515
+ receipt = Receipt.new( tx: tx,
516
+ block: block )
517
+ end
518
+
519
+ Receipt.store( receipt )
520
+ tx # return transaction
521
+ end
522
+
523
+
524
+ def self.accounts() Account.all; end
525
+ def self.contracts() Contract.all; end
526
+
527
+
528
+ def self.msg
529
+ @@msg ||= Msg.new
530
+ end
531
+
532
+ def self.msg=( value )
533
+ if value.is_a? Hash
534
+ kwargs = value
535
+ @@msg = Msg.new( kwargs )
536
+ else ## assume Msg class/type
537
+ @@msg = value
538
+ end
539
+ end
540
+
541
+
542
+ def self.blockhash( number )
543
+ ## for now return "dummy" blockhash
544
+ sha256( "blockhash #{number}" )
545
+ end
546
+
547
+ def self.block
548
+ @@block ||= Block.new
549
+ end
550
+
551
+ def self.block=( value )
552
+ if value.is_a? Hash
553
+ kwargs = value
554
+ @@block = Block.new( kwargs )
555
+ else ## assume Block class/type
556
+ @@block = value
557
+ end
558
+ end
559
+
560
+ def self.this ## returns current contract
561
+ @@this
562
+ end
563
+
564
+ def self.this=(value)
565
+ ## todo/fix: check that value is a contract
566
+ @@this = value
567
+ end
568
+
569
+
570
+ def self.handlers ## use listeners/observers/subscribers/... - why? why not?
571
+ @@handlers ||= []
572
+ end
573
+
574
+ def self.log( event )
575
+ handlers.each { |h| h.call( event ) }
576
+ end
577
+ end ## class Universum
578
+
579
+
580
+
581
+ Uni = Universum ## add some convenience aliases (still undecided what's the most popular :-)
582
+ UNI = Universum
583
+ UN = Universum
584
+ U = Universum
585
+
586
+
587
+ ## (auto-)add (debug) log handler for now - pretty print (pp) events to console
588
+ Uni.handlers << (->(event) { puts "** event: #{event.pretty_inspect}" })
@@ -1,11 +1,14 @@
1
1
  # encoding: utf-8
2
2
 
3
3
 
4
- module Universum
4
+ ##
5
+ ## note: Universum is a class (and NOT a module for now)
6
+
7
+ class Universum
5
8
 
6
9
  MAJOR = 0
7
- MINOR = 0
8
- PATCH = 1
10
+ MINOR = 1
11
+ PATCH = 0
9
12
  VERSION = [MAJOR,MINOR,PATCH].join('.')
10
13
 
11
14
  def self.version
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ class Greeter < Contract
5
+
6
+ def initialize( greeting )
7
+ @owner = msg.sender
8
+ @greeting = greeting
9
+ end
10
+
11
+ def greet
12
+ @greeting
13
+ end
14
+
15
+ def kill
16
+ selfdestruct( msg.sender ) if msg.sender == @owner
17
+ end
18
+ end # class Greeter
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ class MyToken < Contract
5
+
6
+ def initialize( initial_supply )
7
+ @balance_of = Mapping.of( Address => Money )
8
+ @balance_of[ msg.sender] = initial_supply
9
+ end
10
+
11
+ def transfer( to, value )
12
+ assert( @balance_of[ msg.sender ] >= value )
13
+ assert( @balance_of[ to ] + value >= @balance_of[ to ] )
14
+
15
+ @balance_of[ msg.sender ] -= value
16
+ @balance_of[ to ] += value
17
+
18
+ return true
19
+ end
20
+ end # class MyToken
@@ -0,0 +1,8 @@
1
+ ## minitest setup
2
+
3
+ require 'minitest/autorun'
4
+
5
+
6
+ ## our own code
7
+
8
+ require 'universum'
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ ###
4
+ # to run use
5
+ # ruby -I ./lib -I ./test test/test_greeter.rb
6
+
7
+
8
+ require 'helper'
9
+
10
+
11
+ require 'contracts/greeter' ## use require_relative - why? why not?
12
+
13
+
14
+ class TestGreeter < MiniTest::Test
15
+
16
+ def test_run
17
+ account = Account['0x1111']
18
+
19
+ tx = Uni.send_transaction( from: '0x1111', data: [Greeter, 'Hello World!'] )
20
+ pp tx
21
+ pp tx.receipt
22
+
23
+ greeter = tx.receipt.contract
24
+
25
+ assert 'Hello World!', greeter.greet
26
+ assert 'Hello World!', greeter.greet
27
+
28
+ tx = Uni.send_transaction( from: '0x1111', data: [Greeter, '¡Hola, mundo!'] )
29
+
30
+ greeter_es = Receipt[ tx ].contract
31
+
32
+ assert '¡Hola, mundo!', greeter_es.greet
33
+
34
+ assert 'Hello World!', greeter.greet
35
+ assert '¡Hola, mundo!', greeter_es.greet
36
+ end
37
+
38
+ end # class TestGreeter
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ ###
4
+ # to run use
5
+ # ruby -I ./lib -I ./test test/test_version.rb
6
+
7
+
8
+ require 'helper'
9
+
10
+
11
+ class TestVersion < MiniTest::Test
12
+
13
+ def test_version
14
+ pp Universum.version
15
+ pp Universum.banner
16
+ pp Universum.root
17
+
18
+ assert true ## (for now) everything ok if we get here
19
+ end
20
+
21
+ end # class TestVersion
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: universum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-29 00:00:00.000000000 Z
11
+ date: 2019-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc
@@ -46,16 +46,24 @@ email: wwwmake@googlegroups.com
46
46
  executables: []
47
47
  extensions: []
48
48
  extra_rdoc_files:
49
- - HISTORY.md
49
+ - CHANGELOG.md
50
+ - LICENSE.md
50
51
  - Manifest.txt
51
52
  - README.md
52
53
  files:
53
- - HISTORY.md
54
+ - CHANGELOG.md
55
+ - LICENSE.md
54
56
  - Manifest.txt
55
57
  - README.md
56
58
  - Rakefile
57
59
  - lib/universum.rb
60
+ - lib/universum/universum.rb
58
61
  - lib/universum/version.rb
62
+ - test/contracts/greeter.rb
63
+ - test/contracts/mytoken.rb
64
+ - test/helper.rb
65
+ - test/test_greeter.rb
66
+ - test/test_version.rb
59
67
  homepage: https://github.com/openblockchains/universum
60
68
  licenses:
61
69
  - Public Domain