rubidity 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e4839b48c7fbebac1bbc15722701b60bc26494cba550ddea5385c8c2d9e5b76d
4
+ data.tar.gz: 5e8ca8ed8c4865aa857f86554574af2b58297038551587d004ef2104f523dfc0
5
+ SHA512:
6
+ metadata.gz: b3e6ba134973d9e02957a4c8232e102dba2f47bba5da411deda393b24273b066cdd079ec15c2c5f874de84557b623f2117f33bdfb4fbd2692674b7658ccf0f97
7
+ data.tar.gz: 2b186987456561a83229bc20a1e50316ce218c1207d17d8bc1a5267c3fcfb41d7ef97315a06fdaad82e630858d1d2ee5122f389c4ab16b275b964ea0d198a3c9
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ### 0.9.0 / 2023-11-16
2
+
3
+ * Everything is new. First release
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ CHANGELOG.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ lib/rubidity.rb
6
+ lib/rubidity/builder.rb
7
+ lib/rubidity/codegen.rb
8
+ lib/rubidity/proc.rb
9
+ lib/rubidity/version.rb
data/README.md ADDED
@@ -0,0 +1,603 @@
1
+ # Rubidity "Classic / O.G." Contract Builder
2
+
3
+ **DISCLAIMER: the rubidity gem version is different
4
+ from the rubidity built into the facet vm / app and i (Gerald Bauer)
5
+ am NOT affiliated with facet computing inc. (middlemarch et al) or paid to work on the rubidity gem.**
6
+
7
+
8
+ rubidity gem - rubidity "classic / o.g." contract builder; trying the impossible and square the circle, that is, a rubidity "classic / o.g." dsl builder generating rubysol "more ruby-ish" contract classes.
9
+
10
+
11
+ * home :: [github.com/s6ruby/rubidity](https://github.com/s6ruby/rubidity)
12
+ * bugs :: [github.com/s6ruby/rubidity/issues](https://github.com/s6ruby/rubidity/issues)
13
+ * gem :: [rubygems.org/gems/rubidity](https://rubygems.org/gems/rubidity)
14
+ * rdoc :: [rubydoc.info/gems/rubidity](http://rubydoc.info/gems/rubidity)
15
+
16
+
17
+
18
+ ## What's Solidity?! What's Rubidity?! What's Rubysol?!
19
+
20
+ See [**Solidity - Contract Application Binary Interface (ABI) Specification** »](https://docs.soliditylang.org/en/latest/abi-spec.html)
21
+
22
+ See [**Rubidity - Ruby for Layer 1 (L1) Contracts / Protocols with "Off-Chain" Indexer** »](https://github.com/s6ruby/rubidity)
23
+
24
+ See [**Rubysol - Ruby for Layer 1 (L1) Contracts / Protocols with "Off-Chain" Indexer** »](https://github.com/s6ruby/rubidity/tree/master/rubysol)
25
+
26
+
27
+
28
+
29
+
30
+ ## Usage
31
+
32
+ [Contract Sample №1 - HelloWorld](#contract-sample-1---helloworld) •
33
+ [Contract Sample №2 - PublicMintERC20](#contract-sample-2---publicminterc20) •
34
+ [Contract Sample №3 - SupplyChain](#contract-sample-3---supplychain)
35
+
36
+
37
+ ### Contract Sample №1 - HelloWorld
38
+
39
+ Let's try the HelloWorld contract.
40
+
41
+ [HelloWorld](contracts/HelloWorld.rb) - main contract in rubidity classic / o.g. style
42
+
43
+ ``` ruby
44
+ pragma :rubidity, "1.0.0"
45
+
46
+ contract :HelloWorld do
47
+ function :getHelloWorld, {}, :public, :pure, returns: :string do
48
+ return "Hello, world!"
49
+ end
50
+ end
51
+ ```
52
+
53
+ Now let's square the circle and try the impossible.
54
+ Let's run the HelloWorld contract.
55
+
56
+
57
+ ``` ruby
58
+ require 'rubidity'
59
+
60
+ # load (parse) and generate contract classes
61
+ Contract.load( 'HelloWorld' )
62
+
63
+
64
+ # try out contract classes
65
+ pp HelloWorld
66
+ pp HelloWorld.name
67
+
68
+ contract = HelloWorld.new
69
+ pp contract
70
+
71
+ pp contract.getHelloWorld
72
+ #=> "Hello, world!"
73
+ ```
74
+
75
+
76
+
77
+
78
+ ### Contract Sample №2 - PublicMintERC20
79
+
80
+ Let's try the PublicMintERC20 contract.
81
+
82
+
83
+ [ERC20](contracts/ERC20.rb) - base contract in rubidity classic / o.g. style
84
+
85
+ ``` ruby
86
+ pragma :rubidity, "1.0.0"
87
+
88
+ contract :ERC20, abstract: true do
89
+ event :Transfer, { from: :address, to: :address, amount: :uint256 }
90
+ event :Approval, { owner: :address, spender: :address, amount: :uint256 }
91
+
92
+ string :public, :name
93
+ string :public, :symbol
94
+ uint8 :public, :decimals
95
+
96
+ uint256 :public, :totalSupply
97
+
98
+ mapping ({ address: :uint256 }), :public, :balanceOf
99
+ mapping ({ address: mapping(address: :uint256) }), :public, :allowance
100
+
101
+
102
+ constructor(name: :string, symbol: :string, decimals: :uint8) do
103
+ s.name = name
104
+ s.symbol = symbol
105
+ s.decimals = decimals
106
+ end
107
+
108
+
109
+ function :approve, { spender: :address, amount: :uint256 }, :public, :virtual, returns: :bool do
110
+ s.allowance[msg.sender][spender] = amount
111
+
112
+ emit :Approval, owner: msg.sender, spender: spender, amount: amount
113
+
114
+ return true
115
+ end
116
+
117
+ function :transfer, { to: :address, amount: :uint256 }, :public, :virtual, returns: :bool do
118
+ require(s.balanceOf[msg.sender] >= amount, 'Insufficient balance')
119
+
120
+ s.balanceOf[msg.sender] -= amount
121
+ s.balanceOf[to] += amount
122
+
123
+ emit :Transfer, from: msg.sender, to: to, amount: amount
124
+
125
+ return true
126
+ end
127
+
128
+ function :transferFrom, {
129
+ from: :address,
130
+ to: :address,
131
+ amount: :uint256
132
+ }, :public, :virtual, returns: :bool do
133
+ allowed = s.allowance[from][msg.sender]
134
+
135
+ require(s.balanceOf[from] >= amount, 'Insufficient balance')
136
+ require(allowed >= amount, 'Insufficient allowance')
137
+
138
+ s.allowance[from][msg.sender] = allowed - amount
139
+
140
+ s.balanceOf[from] -= amount
141
+ s.balanceOf[to] += amount
142
+
143
+ emit :Transfer, from: from, to: to, amount: amount
144
+
145
+ return true
146
+ end
147
+
148
+ function :_mint, { to: :address, amount: :uint256 }, :internal, :virtual do
149
+ s.totalSupply += amount
150
+ s.balanceOf[to] += amount
151
+
152
+ emit :Transfer, from: address(0), to: to, amount: amount
153
+ end
154
+
155
+ function :_burn, { from: :address, amount: :uint256 }, :internal, :virtual do
156
+ s.balanceOf[from] -= amount
157
+ s.totalSupply -= amount
158
+
159
+ emit :Transfer, from: from, to: address(0), amount: amount
160
+ end
161
+ end
162
+ ```
163
+
164
+
165
+ [PublicMintERC20](contracts/PublicMintERC20.rb) - main contract in rubidity classic / o.g. style
166
+
167
+ ``` ruby
168
+ pragma :rubidity, "1.0.0"
169
+
170
+ import './ERC20'
171
+
172
+ contract :PublicMintERC20, is: :ERC20 do
173
+ uint256 :public, :maxSupply
174
+ uint256 :public, :perMintLimit
175
+
176
+ constructor(
177
+ name: :string,
178
+ symbol: :string,
179
+ maxSupply: :uint256,
180
+ perMintLimit: :uint256,
181
+ decimals: :uint8
182
+ ) do
183
+ super( name: name, symbol: symbol, decimals: decimals )
184
+ s.maxSupply = maxSupply
185
+ s.perMintLimit = perMintLimit
186
+ end
187
+
188
+ function :mint, { amount: :uint256 }, :public do
189
+ require(amount > 0, 'Amount must be positive')
190
+ require(amount <= s.perMintLimit, 'Exceeded mint limit')
191
+
192
+ require(s.totalSupply + amount <= s.maxSupply, 'Exceeded max supply')
193
+
194
+ _mint(to: msg.sender, amount: amount)
195
+ end
196
+
197
+ function :airdrop, { to: :address, amount: :uint256 }, :public do
198
+ require(amount > 0, 'Amount must be positive')
199
+ require(amount <= s.perMintLimit, 'Exceeded mint limit')
200
+
201
+ require(s.totalSupply + amount <= s.maxSupply, 'Exceeded max supply')
202
+
203
+ _mint(to: to, amount: amount)
204
+ end
205
+ end
206
+ ```
207
+
208
+
209
+
210
+ Now let's square the circle and try the impossible.
211
+ Let's run the PublicMintERC20 contract.
212
+
213
+
214
+ ``` ruby
215
+ require 'rubidity'
216
+
217
+ # load (parse) and generate contract classes
218
+ Contract.load( 'PublicMintERC20' )
219
+
220
+
221
+ # try out contract classes
222
+ pp ERC20
223
+ pp PublicMintERC20
224
+
225
+ pp ERC20.name
226
+ pp PublicMintERC20.name
227
+
228
+ pp ERC20::Transfer # "scoped" (typed) Event class
229
+ pp ERC20::Approval # "scoped" (typed) Event class
230
+ pp ERC20::Transfer.name
231
+ pp ERC20::Approval.name
232
+
233
+ pp ERC20::Transfer.new( from: address(0), to: address(0), amount: 0)
234
+ pp ERC20::Approval.new( owner: address(0), spender: address(0), amount: 0)
235
+
236
+
237
+ contract = ERC20.new
238
+ pp contract
239
+ contract.constructor( name: 'My Fun Token',
240
+ symbol: 'FUN',
241
+ decimals: 18 )
242
+
243
+ pp contract
244
+
245
+
246
+ contract = PublicMintERC20.new
247
+ pp contract
248
+ contract.constructor( name: 'My Fun Token',
249
+ symbol: 'FUN',
250
+ maxSupply: 21000000,
251
+ perMintLimit: 10000,
252
+ decimals: 18 )
253
+ pp contract
254
+ pp contract.serialize
255
+
256
+
257
+ # test drive with alice, bob & charlie address
258
+
259
+ alice = '0x'+'a'*40 # e.g. '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
260
+ bob = '0x'+'b'*40 # e.g. '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
261
+ charlie = '0x'+'c'*40 # e.g. '0xcccccccccccccccccccccccccccccccccccccccc'
262
+
263
+ pp alice
264
+ pp bob
265
+ pp charlie
266
+
267
+
268
+ # try mint
269
+ Runtime.msg.sender = alice
270
+
271
+ contract.mint( 1000 )
272
+ contract.mint( 100 )
273
+
274
+ pp contract.serialize
275
+
276
+
277
+
278
+ Runtime.msg.sender = bob
279
+
280
+ contract.mint( 500 )
281
+ contract.mint( 10 )
282
+
283
+ pp contract.serialize
284
+
285
+
286
+ pp contract.totalSupply
287
+ pp contract.balanceOf( alice )
288
+ pp contract.balanceOf( bob )
289
+ pp contract.serialize
290
+
291
+
292
+
293
+ # try transfer
294
+ Runtime.msg.sender = alice
295
+
296
+ contract.transfer( to: bob, amount: 111 )
297
+ contract.transfer( to: charlie, amount: 11 )
298
+
299
+
300
+ Runtime.msg.sender = bob
301
+
302
+ contract.transfer( to: alice, amount: 11 )
303
+ contract.transfer( to: charlie, amount: 111 )
304
+
305
+
306
+
307
+ pp contract.totalSupply
308
+ pp contract.balanceOf( alice )
309
+ pp contract.balanceOf( bob )
310
+ pp contract.balanceOf( charlie )
311
+
312
+ pp contract.serialize
313
+ #=> {:name=>"My Fun Token",
314
+ # :symbol=>"FUN",
315
+ # :decimals=>18,
316
+ # :totalSupply=>1610,
317
+ # :balanceOf=>
318
+ # {"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"=>989,
319
+ # "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"=>499,
320
+ # "0xcccccccccccccccccccccccccccccccccccccccc"=>122},
321
+ # :allowance=>{},
322
+ # :maxSupply=>21000000,
323
+ # :perMintLimit=>10000}
324
+
325
+ # and so on.
326
+ ```
327
+
328
+ that's it.
329
+
330
+
331
+
332
+ ### Contract Sample №3 - SupplyChain
333
+
334
+ Let's try the SupplyChain contract.
335
+
336
+ [SupplyChain](contracts/SupplyChain.rb) - main contract in rubidity classic / o.g. style
337
+
338
+
339
+ ``` ruby
340
+ pragma :rubidity, "1.0.0"
341
+
342
+ contract :SupplyChain do
343
+
344
+ event :Transfer, { productId: :uint32 }
345
+
346
+ struct :Product, { modelNumber: :string,
347
+ partNumber: :string,
348
+ serialNumber: :string,
349
+ productOwner: :address,
350
+ cost: :uint32,
351
+ mfgTimestamp: :timestamp }
352
+
353
+ struct :Participant, { userName: :string,
354
+ password: :string,
355
+ participantType: :string,
356
+ participantAddress: :address }
357
+
358
+ struct :Registration, { productId: :uint32,
359
+ ownerId: :uint32,
360
+ productOwner: :address,
361
+ trxTimestamp: :timestamp }
362
+
363
+ uint32 :public, :p_id # (last) product id
364
+ uint32 :public, :u_id # (last) participant id
365
+ uint32 :public, :r_id # (last) registration id
366
+
367
+ mapping ({ uint32: :Product }), :public, :products
368
+ mapping ({ uint32: :Participant }), :public, :participants
369
+ mapping ({ uint32: :Registration }), :public, :registrations
370
+
371
+ # movement track for a product
372
+ mapping ({ uint32: array( :uint32 ) }), :public, :productTrack
373
+
374
+
375
+ function :createParticipant, { name: :string,
376
+ pass: :string,
377
+ addr: :address,
378
+ type: :string }, :public, returns: :uint32 do
379
+ userId = s.u_id += 1
380
+ s.participants[ userId ].userName = name
381
+ s.participants[ userId ].password = pass
382
+ s.participants[ userId ].participantAddress = addr
383
+ s.participants[ userId ].participantType = type
384
+ return userId
385
+ end
386
+
387
+ function :getParticipantDetails, { id: :uint32 }, :public, :view, returns: [:string,:address,:string] do
388
+ return [s.participants[ id ].userName,
389
+ s.participants[ id ].participantAddress,
390
+ s.participants[ id ].participantType]
391
+ end
392
+
393
+ function :createProduct, { ownerId: :uint32,
394
+ modelNumber: :string,
395
+ partNumber: :string,
396
+ serialNumber: :string,
397
+ productCost: :uint32 }, :public, returns: :uint32 do
398
+ require( s.participants[ ownerId ].participantType == "manufacturer", "must be manufacturer" )
399
+
400
+ productId = s.p_id += 1
401
+ s.products[ productId ].modelNumber = modelNumber
402
+ s.products[ productId ].partNumber = partNumber
403
+ s.products[ productId ].serialNumber = serialNumber
404
+ s.products[ productId ].cost = productCost
405
+ s.products[ productId ].productOwner = s.participants[ownerId].participantAddress
406
+ s.products[ productId ].mfgTimestamp = block.timestamp
407
+ return productId
408
+ end
409
+
410
+ function :getProductDetails, { id: :uint32 }, :public, :view, returns: [:string,:string,:string,:uint32,:address,:timestamp] do
411
+ return [s.products[ id ].modelNumber,
412
+ s.products[ id ].partNumber,
413
+ s.products[ id ].serialNumber,
414
+ s.products[ id ].cost,
415
+ s.products[ id ].productOwner,
416
+ s.products[ id ].mfgTimestamp]
417
+ end
418
+
419
+ function :transferToOwner, { user1Id: :uint32,
420
+ user2Id: :uint32,
421
+ prodId: :uint32 }, :public, returns: :bool do
422
+ require( msg.sender == s.products[prodId].productOwner, "only owner" )
423
+
424
+ p1 = s.participants[ user1Id ]
425
+ p2 = s.participants[ user2Id ]
426
+ registrationId = s.r_id += 1
427
+
428
+ s.registrations[ registrationId ].productId = prodId
429
+ s.registrations[ registrationId ].productOwner = p2.participantAddress
430
+ s.registrations[ registrationId ].ownerId = user2Id
431
+ s.registrations[ registrationId ].trxTimestamp = block.timestamp
432
+
433
+ s.products[ prodId ].productOwner = p2.participantAddress
434
+ s.productTrack[ prodId ].push( registrationId )
435
+
436
+ emit :Transfer, prodId
437
+
438
+ return true
439
+ end
440
+
441
+ function :getProductTrack, { prodId: :uint32 }, :public, :view, returns: array(:uint32) do
442
+ return s.productTrack[ prodId ]
443
+ end
444
+
445
+ function :getRegistrationDetails, { regId: :uint32 }, :public, :view, returns: [:uint32, :uint32, :address, :timestamp] do
446
+ r = s.registrations[ regId ]
447
+
448
+ return [r.productId,
449
+ r.ownerId,
450
+ r.productOwner,
451
+ r.trxTimestamp]
452
+ end
453
+ end
454
+ ```
455
+
456
+ Now let's square the circle and try the impossible.
457
+ Let's run the SupplyChain contract.
458
+
459
+
460
+ ``` ruby
461
+ require 'rubidity'
462
+
463
+ # load (parse) and generate contract classes
464
+ Contract.load( 'SupplyChain' )
465
+
466
+ # try out contract classes
467
+ pp SupplyChain
468
+ pp SupplyChain.name
469
+
470
+ pp SupplyChain::Transfer
471
+ pp SupplyChain::Transfer.name
472
+
473
+ pp SupplyChain::Transfer.new( productId: 111 )
474
+
475
+ pp SupplyChain::Product
476
+ pp SupplyChain::Participant
477
+ pp SupplyChain::Registration
478
+ pp SupplyChain::Product.name
479
+ pp SupplyChain::Participant.name
480
+ pp SupplyChain::Registration.name
481
+
482
+
483
+ alice = '0x'+'a'*40 # e.g. '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
484
+ bob = '0x'+'b'*40 # e.g. '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
485
+ charlie = '0x'+'c'*40 # e.g. '0xcccccccccccccccccccccccccccccccccccccccc'
486
+
487
+ pp alice
488
+ pp bob
489
+ pp charlie
490
+
491
+ pp SupplyChain::Product.new( modelNumber: 'model1',
492
+ partNumber: 'part1',
493
+ serialNumber: 'serial1',
494
+ productOwner: alice,
495
+ cost: 1000,
496
+ mfgTimestamp: 1698756267 )
497
+
498
+ pp SupplyChain::Participant.new( userName: 'alice',
499
+ password: 'passa',
500
+ participantType: 'manufacturer',
501
+ participantAddress: alice )
502
+
503
+ pp SupplyChain::Registration.new( productId: 1,
504
+ ownerId: 1,
505
+ productOwner: alice,
506
+ trxTimestamp: 1698756267 )
507
+
508
+
509
+ contract = SupplyChain.new
510
+ pp contract
511
+
512
+ pp contract.p_id
513
+ pp contract.u_id
514
+ pp contract.r_id
515
+
516
+ pp contract.serialize
517
+ #=>
518
+ # {:p_id=>0, :u_id=>0, :r_id=>0,
519
+ # :products=>{},
520
+ # :participants=>{},
521
+ # :registrations=>{},
522
+ # :productTrack=>{}}
523
+
524
+
525
+ pp contract.createParticipant( name: 'alice',
526
+ pass: 'passa',
527
+ addr: alice,
528
+ type: 'manufacturer' )
529
+
530
+ pp contract.createParticipant( name: 'bob',
531
+ pass: 'passb',
532
+ addr: bob,
533
+ type: 'supplier' )
534
+
535
+ pp contract.createParticipant( name: 'charlie',
536
+ pass: 'passc',
537
+ addr: charlie,
538
+ type: 'consumer' )
539
+
540
+ pp contract.getParticipantDetails( 1 )
541
+ pp contract.getParticipantDetails( 2 )
542
+ pp contract.getParticipantDetails( 3 )
543
+
544
+
545
+
546
+ Runtime.block.timestamp = 1698846375
547
+
548
+ pp contract.createProduct( ownerId: 1,
549
+ modelNumber: 'prod1',
550
+ partNumber: '100',
551
+ serialNumber: '123',
552
+ productCost: 11 )
553
+
554
+ pp contract.getProductDetails( 1 )
555
+
556
+
557
+ Runtime.msg.sender = alice
558
+ Runtime.block.timestamp = 1698847541
559
+
560
+ pp contract.transferToOwner( user1Id: 1,
561
+ user2Id: 2,
562
+ prodId: 1 )
563
+
564
+ Runtime.msg.sender = bob
565
+ Runtime.block.timestamp = 1698848314
566
+
567
+ pp contract.transferToOwner( user1Id: 2,
568
+ user2Id: 3,
569
+ prodId: 1 )
570
+
571
+
572
+ pp contract.getRegistrationDetails( 1 )
573
+ pp contract.getRegistrationDetails( 2 )
574
+
575
+ pp contract.getProductTrack( 1 )
576
+
577
+ pp contract.serialize
578
+ #=> {:p_id=>1,
579
+ # :u_id=>3,
580
+ # :r_id=>2,
581
+ # :products=>{1=>["prod1", "100", "123", "0xcccccccccccccccccccccccccccccccccccccccc", 11, 1698846375]},
582
+ # :participants=>
583
+ # {1=>["alice", "passa", "manufacturer", "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
584
+ # 2=>["bob", "passb", "supplier", "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
585
+ # 3=>["charlie", "passc", "consumer", "0xcccccccccccccccccccccccccccccccccccccccc"]},
586
+ # :registrations=>
587
+ # {1=>[1, 2, "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 1698847541],
588
+ # 2=>[1, 3, "0xcccccccccccccccccccccccccccccccccccccccc", 1698848314]},
589
+ # :productTrack=>{1=>[1, 2]}}
590
+ ```
591
+
592
+ that's it.
593
+
594
+
595
+
596
+
597
+ ## Questions? Comments?
598
+
599
+ Join us in the [Rubidity & Rubysol (community) discord (chat server)](https://discord.gg/3JRnDUap6y). Yes you can.
600
+ Your questions and commentary welcome.
601
+
602
+ Or post them over at the [Help & Support](https://github.com/geraldb/help) page. Thanks.
603
+
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'hoe'
2
+ require './lib/rubidity/version.rb'
3
+
4
+
5
+ Hoe.spec 'rubidity' do
6
+
7
+ self.version = Rubysol::Module::Rubidity::VERSION
8
+
9
+ self.summary = 'rubidity gem - rubidity "classic / o.g." contract builder; trying the impossible and square the circle, that is, a rubidity "classic / o.g." dsl builder generating rubysol "more ruby-ish" contract classes'
10
+ self.description = summary
11
+
12
+ self.urls = { home: 'https://github.com/s6ruby/rubidity' }
13
+
14
+ self.author = 'Gerald Bauer'
15
+ self.email = 'gerald.bauer@gmail.com'
16
+
17
+ # switch extension to .markdown for gihub formatting
18
+ self.readme_file = 'README.md'
19
+ self.history_file = 'CHANGELOG.md'
20
+
21
+ self.extra_deps = [
22
+ ['rubysol', '>= 0.1.0'],
23
+ ]
24
+
25
+ self.licenses = ['Public Domain']
26
+
27
+ self.spec_extras = {
28
+ required_ruby_version: '>= 2.3'
29
+ }
30
+ end
@@ -0,0 +1,215 @@
1
+ ######
2
+ # reading rubidity classic/o.g. "dsl"
3
+
4
+
5
+ class Builder
6
+ ## auto-add extension and import path for now - why? why not?
7
+ def self.load_file( path )
8
+ code = File.open( "contracts/#{path}.rb", 'r:utf-8' ) { |f| f.read }
9
+ basename = File.basename( path )
10
+ lineno = 1
11
+ load( code, basename, lineno )
12
+ end
13
+
14
+ ## note: try adding filename and lineno to get source for block!!!
15
+ def self.load( code, filename, lineno )
16
+
17
+ ## todo/fix: add filename to @paths too (to avoid possible recursive loading!!)
18
+
19
+ builder = Builder.new
20
+ builder.instance_eval( code, filename, lineno )
21
+ builder
22
+ end
23
+
24
+ def initialize
25
+ @source = Source.new
26
+ ## only load path once; keep track of loaded imports
27
+ ## change paths to contracts or such - why? why not?
28
+ @paths = []
29
+ end
30
+
31
+
32
+ attr_reader :source
33
+
34
+
35
+ ## examples:
36
+ ## pragma :rubidity, "1.0.0"
37
+ def pragma( ... )
38
+ ## ignore for now
39
+ end
40
+
41
+ ## examples:
42
+ ## import './ERC20'
43
+ def import( path )
44
+ puts "==> importing >#{path}<..."
45
+ code = File.open( "contracts/#{path}.rb", 'r:utf-8' ) { |f| f.read }
46
+
47
+ basename = File.basename( path )
48
+
49
+ if @paths.include?( basename )
50
+ puts " skipping import; already loaded"
51
+ else
52
+ @paths << basename
53
+ lineno = 1 ## note: starting at line 1 (NOT 0!!!)
54
+ instance_eval( code, basename, lineno )
55
+ end
56
+ end
57
+
58
+
59
+
60
+ def contract( name, is: [], abstract: false, &body )
61
+ contract = ContractDef.new( name, is: is, abstract: abstract )
62
+ contract.instance_eval( &body )
63
+
64
+ @source.contracts << contract
65
+ end
66
+ end # class Builder
67
+
68
+
69
+
70
+ class Source ## rename to unit (source unit) or service or ?? - why? why not?
71
+
72
+ attr_reader :contracts
73
+
74
+ def initialize
75
+ @contracts = []
76
+ end
77
+
78
+ def generate
79
+ CodegenClassic.generate( self )
80
+ end
81
+ end # class Source
82
+
83
+
84
+
85
+ class ContractDef
86
+ attr_reader :name, :is, :abstract,
87
+ :events, :structs,
88
+ :storage, :functions
89
+ def initialize( name, is: [], abstract: false )
90
+ @name = name
91
+ @is = if is.is_a?( Symbol )
92
+ [is]
93
+ elsif is.is_a?( Array )
94
+ is
95
+ else
96
+ raise ArgumentError, "symbol or array expected; got #{is.inspect}"
97
+ end
98
+ @abstract = abstract
99
+ @events = {}
100
+ @structs = {}
101
+ @storage = {}
102
+ @functions = {}
103
+ end
104
+
105
+
106
+ def event( name, args )
107
+ @events[name] = args
108
+ end
109
+
110
+ def struct( name, args )
111
+ @structs[name] = args
112
+ end
113
+
114
+
115
+ [:string,
116
+ :uint8, :uint32, :uint112, :uint256,
117
+ :address
118
+ ].each do |type|
119
+ define_method(type) do |*args|
120
+
121
+ if args.size == 0
122
+ ## assume type helper to convert :string to string,
123
+ ## :address to address, and such
124
+ puts " turn :#{type} into #{type}"
125
+ return type
126
+ end
127
+
128
+
129
+ type = type
130
+ name = args.pop.to_sym
131
+ @storage[ name ] = { type: type, args: args }
132
+ end
133
+ end
134
+
135
+ def mapping( *args )
136
+ key_type, value_type = args.shift.first
137
+
138
+ if args.last.is_a?( Symbol )
139
+ name = args.pop
140
+ @storage[ name ] = { type: :mapping,
141
+ key_type: key_type,
142
+ value_type: value_type,
143
+ args: args }
144
+ else
145
+ { type: :mapping,
146
+ key_type: key_type,
147
+ value_type: value_type,
148
+ args: args }
149
+ end
150
+ end
151
+
152
+ def array( *args )
153
+ sub_type = args.shift
154
+
155
+ if args.last.is_a?( Symbol )
156
+ name = args.pop
157
+ @storage[ name ] = { type: :array,
158
+ sub_type: sub_type,
159
+ args: args }
160
+ else
161
+ { type: :array,
162
+ sub_type: sub_type,
163
+ args: args }
164
+ end
165
+ end
166
+
167
+
168
+ def function(name, args, *options, returns: nil, &body )
169
+ ## check if access to source is possible?
170
+ puts
171
+ # puts " function body: #{body.class.name}"
172
+ #=> Proc
173
+ # body.soure
174
+ #=> ["(eval)", 17] - source filename and line number
175
+ pp body
176
+ pp body.parameters
177
+ pp body.source_location
178
+ puts
179
+ puts body.source
180
+
181
+
182
+ ## note: for now strip names/keys if returns is a hash - why? why not?
183
+ ## e.g. function :getReserves, {}, :public, :view, returns: {
184
+ ## _reserve0: :uint112,
185
+ ## _reserve1: :uint112,
186
+ ## _blockTimestampLast: :uint32 }
187
+
188
+ returns = returns.values if returns.is_a?( Hash )
189
+
190
+ ###
191
+ ## note: turn returns into an array - empty if nil, etc.
192
+ ## always wrap into array
193
+ returns = if returns.nil?
194
+ []
195
+ elsif returns.is_a?( Array )
196
+ returns
197
+ else ## assume single type
198
+ [returns]
199
+ end
200
+
201
+ @functions[ name ] = { args: args,
202
+ options: options,
203
+ returns: returns,
204
+ body: body.source }
205
+ end
206
+
207
+ def constructor(args = {}, *options, &body)
208
+ function(:constructor, args, *options, returns: nil, &body )
209
+ end
210
+ end # class ContractDef
211
+
212
+
213
+
214
+
215
+
@@ -0,0 +1,203 @@
1
+ #####
2
+ # codegen - code generator
3
+
4
+ ##
5
+ ## require(s.balanceOf[msg.sender] >= amount, 'Insufficient balance')
6
+ ##
7
+ ## s.balanceOf[msg.sender] -= amount
8
+ ## s.balanceOf[to] += amount
9
+ ##
10
+ ## emit :Transfer, from: msg.sender, to: to, amount: amount
11
+
12
+ def patch( src )
13
+ ## change require() to assert
14
+ src = src.gsub( /\brequire\b/, 'assert' )
15
+
16
+ ## s. to ivars (@)
17
+ src = src.gsub( /\bs\./, '@' )
18
+
19
+ ## change emit : to log
20
+ src = src.gsub( /\bemit[ ]+:/, 'log ' )
21
+ src
22
+ end
23
+
24
+
25
+
26
+ ## "built-in" types by "classic" symbol lookup
27
+
28
+ ## storage decimals{:type=>:uint8, :args=>[:public]}
29
+ ## storage totalSupply{:type=>:uint256, :args=>[:public]}
30
+ ## storage balanceOf{:type=>:mapping, :key_type=>:address, :value_type=>:uint256, :args=>[:public]}
31
+ ## storage allowance{:type=>:mapping,
32
+ ## :key_type=>:address,
33
+ ## :value_type=>{:type=>:mapping, :key_type=>:address, :value_type=>:uint256, :args=>[]},
34
+
35
+ def spec_to_type( spec, structs: {} )
36
+ type = spec.is_a?( Symbol ) ? spec : spec[:type]
37
+
38
+ ## check structs first
39
+ struct_class = structs[ type ]
40
+ if struct_class
41
+ puts " found struct class >#{type}<:"
42
+ pp struct_class
43
+ return struct_class
44
+ end
45
+
46
+ case type
47
+ when :uint8, :uint32, :uint112, :uint224, :uint256 then Types::UInt
48
+ when :address then Types::Address
49
+ when :string then Types::String
50
+ when :bytes then Types::Bytes
51
+ when :timestamp then Types::Timestamp
52
+ when :bool then Bool ## note: Bool is always "global" - why? why not?
53
+ when :mapping then mapping( spec_to_type( spec[:key_type], structs: structs ),
54
+ spec_to_type( spec[:value_type], structs: structs ) )
55
+ when :array then array( spec_to_type( spec[:sub_type], structs: structs ))
56
+ else
57
+ raise ArgumentError, "unknown type - #{type}"
58
+ end
59
+ end
60
+
61
+
62
+
63
+ ##
64
+ # use static methods for now - why? why not?
65
+ class CodegenClassic
66
+
67
+
68
+ def self.generate( source )
69
+ pp source
70
+
71
+ contract_classes = {} ## lookup by name
72
+ struct_classes = {} ## lookup by name
73
+
74
+ source.contracts.each do |contract|
75
+ puts "==> generate contract #{contract.name}..."
76
+ base = contract.is.empty? ? Contract : contract_classes[contract.is[0]]
77
+ puts " base: #{base}"
78
+ pp contract.is
79
+
80
+ contract_class = Class.new( base )
81
+
82
+ ## Use Kernel / global ?? scope for now
83
+ scope = Object
84
+ scope.const_set( contract.name, contract_class )
85
+ contract_classes[ contract.name ] = contract_class
86
+
87
+ ## add events if any
88
+ ##
89
+ ## event :Transfer, { from: :address, to: :address, amount: :uint256 }
90
+ ## event :Approval, { owner: :address, spender: :address, amount: :uint256 }
91
+ ##
92
+ ## event Transfer {:from=>:address, :to=>:address, :amount=>:uint256}
93
+ ## event Approval {:owner=>:address, :spender=>:address, :amount=>:uint256}
94
+ contract.events.each do |event_name, event_args|
95
+ print "event #{event_name}"
96
+ pp event_args
97
+
98
+ attributes = event_args.map { |name, type| [name, spec_to_type(type)] }.to_h
99
+ pp attributes
100
+ contract_class.event( event_name, **attributes )
101
+ end
102
+
103
+ ## add structs if any
104
+ contract.structs.each do |struct_name, struct_args|
105
+ print "struct #{struct_name}"
106
+ pp struct_args
107
+
108
+ attributes = struct_args.map { |name, type| [name, spec_to_type(type)] }.to_h
109
+ pp attributes
110
+ struct_class = contract_class.struct( struct_name, **attributes )
111
+
112
+ struct_classes[ struct_name ] = struct_class
113
+ end
114
+
115
+
116
+ ## add storage/state if any
117
+ ##
118
+ ## storage name{:type=>:string, :args=>[:public]}
119
+ ## storage symbol{:type=>:string, :args=>[:public]}
120
+ ## storage decimals{:type=>:uint8, :args=>[:public]}
121
+ ## storage totalSupply{:type=>:uint256, :args=>[:public]}
122
+ ## storage balanceOf{:type=>:mapping, :key_type=>:address, :value_type=>:uint256, :args=>[:public]}
123
+ ## storage allowance{:type=>:mapping,
124
+ ## :key_type=>:address,
125
+ ## :value_type=>{:type=>:mapping, :key_type=>:address, :value_type=>:uint256, :args=>[]},
126
+ ## :args=>[:public]}
127
+ contract.storage.each do |storage_name, storage_args|
128
+ print "storage #{storage_name}"
129
+ pp storage_args
130
+
131
+ kwargs = {
132
+ storage_name => spec_to_type( storage_args, structs: struct_classes )
133
+ }
134
+ contract_class.storage( **kwargs )
135
+ end
136
+
137
+
138
+ ## add functions if any
139
+ ## constructor:
140
+ ## {:args=>{:name=>:string, :symbol=>:string, :decimals=>:uint8},
141
+ ## :options=>[],
142
+ ## :body=>"..."}
143
+ ## function approve:
144
+ ## {:args=>{:spender=>:address, :amount=>:uint256},
145
+ ## :options=>[:public, :virtual],
146
+ ## :returns=>[:bool],
147
+ ## :body=>"..."}
148
+ ## function transfer:
149
+ ## {:args=>{:to=>:address, :amount=>:uint256},
150
+ ## :options=>[:public, :virtual],
151
+ ## :returns=>[:bool],
152
+ ## :body=>"..."}
153
+ contract.functions.each do |function_name, function_args|
154
+ print "function #{function_name}"
155
+ pp function_args
156
+
157
+ input_types = function_args[:args].values.map { |type| spec_to_type(type) }
158
+
159
+ kwargs = function_args[:args].keys.map {|arg| "#{arg}:" }.join( ', ' )
160
+ puts " kwargs: #{kwargs}"
161
+ body = function_args[:body]
162
+ body = patch( body ) ## use ruby-ish conventions
163
+
164
+
165
+ if function_name == :constructor
166
+ puts "==> add constructor..."
167
+ contract_class.sig input_types
168
+ ## add unsafe method
169
+
170
+ code =<<RUBY
171
+ def constructor( #{kwargs} )
172
+ puts "==> calling #{contract_class.name}.constructor..."
173
+ puts " self: \#{self.class.name}"
174
+ #{body}
175
+ end
176
+ RUBY
177
+ puts " code:"
178
+ puts code
179
+ contract_class.class_eval( code )
180
+ else
181
+ output_types = function_args[:returns].map { |type| spec_to_type(type) }
182
+
183
+ puts "==> add #{function_name}..."
184
+ contract_class.sig input_types, returns: output_types
185
+ ## add unsafe method
186
+
187
+ code =<<RUBY
188
+ def #{function_name}( #{kwargs} )
189
+ puts "==> calling #{contract_class.name}.#{function_name}..."
190
+ puts " self: \#{self.class.name}"
191
+ #{body}
192
+ end
193
+ RUBY
194
+ puts " code:"
195
+ puts code
196
+ contract_class.class_eval( code )
197
+ end
198
+ end
199
+ end
200
+ end # method self.generate
201
+
202
+ end # class CodegenClassic
203
+
@@ -0,0 +1,42 @@
1
+
2
+
3
+ class Proc
4
+ ## add quick & dirty support for getting source!!!
5
+
6
+ ## use non-greed .*? to "slurp"-up everything to the end
7
+ BLOCK_RX = /do
8
+ .*?
9
+ \n[ ]{0,2}end
10
+ /xm
11
+
12
+ def source ## fix: change to contract_source - why? why not?
13
+ filename, lineno = self.source_location
14
+ lines = File.open( "contracts/#{filename}.rb", 'r:utf-8' ) { |f| f.readlines }
15
+ ## up to 10 for now
16
+ ## note: lineno is starting counting at 1 (use -1 for offset in ary)
17
+
18
+ ## for now assume no method longer than 100 lines
19
+ ## note: lines INCLUDEs newlines e.g.
20
+ ## [" constructor(name: :string, symbol: :string, decimals: :uint8) do |name, symbol, decimals|\n",
21
+ ## " puts \"ERC20.constructor\"\n",
22
+ ## " @name = name\n",
23
+ ## " @symbol = symbol\n",
24
+ ## " @decimals = decimals\n",
25
+ ## " end\n",
26
+
27
+ pastie = lines[lineno-1, 100].join
28
+ ## pp pastie
29
+
30
+ ## use regex quick hack
31
+ ## to extract use first do
32
+ ## and end on its own line (with max indent of two spaces!!)
33
+ m = BLOCK_RX.match( pastie )
34
+ if m
35
+ m[0][2..-1][0..-4] ## return matched code - cut of do/end wrapper
36
+ else
37
+ raise "sorry; no do-end match for code block source"
38
+ end
39
+ end
40
+ end
41
+
42
+
@@ -0,0 +1,23 @@
1
+ module Rubysol
2
+ module Module
3
+ module Rubidity
4
+ MAJOR = 0
5
+ MINOR = 9
6
+ PATCH = 0
7
+ VERSION = [MAJOR,MINOR,PATCH].join('.')
8
+
9
+ def self.version
10
+ VERSION
11
+ end
12
+
13
+ def self.banner
14
+ "rubidity/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
15
+ end
16
+
17
+ def self.root
18
+ File.expand_path( File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) )
19
+ end
20
+
21
+ end # module Rubidity
22
+ end # module Module
23
+ end # module Rubysol
data/lib/rubidity.rb ADDED
@@ -0,0 +1,42 @@
1
+
2
+ require 'solidity/typed'
3
+ require 'rubysol'
4
+
5
+
6
+ ## our own code
7
+ require_relative 'rubidity/version' # let version always go first
8
+ require_relative 'rubidity/proc'
9
+ require_relative 'rubidity/builder'
10
+ require_relative 'rubidity/codegen'
11
+
12
+
13
+
14
+ ###
15
+ # for easy use / convenience
16
+ # pack everything into Contract.load( path/name )
17
+
18
+ class Contract
19
+ def self.load( path, generate: true )
20
+
21
+ source = Builder.load_file( path ).source
22
+ pp source
23
+ pp source.contracts
24
+
25
+ puts " #{source.contracts.size} contract(s) in source (unit):"
26
+ source.contracts.each do |contract|
27
+ print " #{contract.name}"
28
+ print " is #{contract.is.inspect}" unless contract.is.empty?
29
+ print "\n"
30
+ end
31
+
32
+ ####################
33
+ ### generate contract classes
34
+ source.generate if generate
35
+
36
+ source ## return source (unit) - why? why not?
37
+ end
38
+ end # class Contract
39
+
40
+
41
+
42
+ puts Rubysol::Module::Rubidity.banner ## say hello
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubidity
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Gerald Bauer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-11-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubysol
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rdoc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '7'
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '4.0'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '7'
47
+ - !ruby/object:Gem::Dependency
48
+ name: hoe
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '4.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '4.0'
61
+ description: rubidity gem - rubidity "classic / o.g." contract builder; trying the
62
+ impossible and square the circle, that is, a rubidity "classic / o.g." dsl builder
63
+ generating rubysol "more ruby-ish" contract classes
64
+ email: gerald.bauer@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files:
68
+ - CHANGELOG.md
69
+ - Manifest.txt
70
+ - README.md
71
+ files:
72
+ - CHANGELOG.md
73
+ - Manifest.txt
74
+ - README.md
75
+ - Rakefile
76
+ - lib/rubidity.rb
77
+ - lib/rubidity/builder.rb
78
+ - lib/rubidity/codegen.rb
79
+ - lib/rubidity/proc.rb
80
+ - lib/rubidity/version.rb
81
+ homepage: https://github.com/s6ruby/rubidity
82
+ licenses:
83
+ - Public Domain
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options:
87
+ - "--main"
88
+ - README.md
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '2.3'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.4.10
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: rubidity gem - rubidity "classic / o.g." contract builder; trying the impossible
106
+ and square the circle, that is, a rubidity "classic / o.g." dsl builder generating
107
+ rubysol "more ruby-ish" contract classes
108
+ test_files: []