rubidity 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: []