rubysol 0.1.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: 43d35a08ccf1d5b5dff18647a35cd0080da617acb43e299b3c4a0f477b366dae
4
+ data.tar.gz: 200f1cfe5afb3eb2e90be4c8eb34d31a8c71143d29a1c8d7662be90f9c8d4257
5
+ SHA512:
6
+ metadata.gz: 7f59e4b03ee479d4992aaf8fe49e00a4004574fcfffad96da7bad39d5017a6fd19070a060c981304961ba7c2b34592c6e20792c679a34e5865b9d016eefdc485
7
+ data.tar.gz: e977a4a7df706617c3891e4ec2291c96797e8a4154c37a9c3b514905597f49254faa41119e30473c003e483845319f08a75f273571443608cffd909968587e0e
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ### 0.1.0 / 2023-11-15
2
+
3
+ * Everything is new. First release
data/Manifest.txt ADDED
@@ -0,0 +1,13 @@
1
+ CHANGELOG.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ lib/rubysol.rb
6
+ lib/rubysol/abi_proxy.rb
7
+ lib/rubysol/contract.rb
8
+ lib/rubysol/contract/crypto.rb
9
+ lib/rubysol/contract/runtime.rb
10
+ lib/rubysol/generator.rb
11
+ lib/rubysol/library.rb
12
+ lib/rubysol/runtime.rb
13
+ lib/rubysol/version.rb
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # Rubysol
2
+
3
+ rubysol - ruby for (blockchain) layer 1 (l1) contracts / protocols with "off-chain" indexer; 100% compatible with solidity datatypes and abis
4
+
5
+
6
+ * home :: [github.com/s6ruby/rubidity](https://github.com/s6ruby/rubidity)
7
+ * bugs :: [github.com/s6ruby/rubidity/issues](https://github.com/s6ruby/rubidity/issues)
8
+ * gem :: [rubygems.org/gems/rubysol](https://rubygems.org/gems/rubysol)
9
+ * rdoc :: [rubydoc.info/gems/rubysol](http://rubydoc.info/gems/rubysol)
10
+
11
+
12
+
13
+ ## What's Solidity?! What's Rubidity?! What's Rubysol?!
14
+
15
+ See [**Solidity - Contract Application Binary Interface (ABI) Specification** »](https://docs.soliditylang.org/en/latest/abi-spec.html)
16
+
17
+ See [**Rubidity - Ruby for Layer 1 (L1) Contracts / Protocols with "Off-Chain" Indexer** »](https://github.com/s6ruby/rubidity)
18
+
19
+
20
+
21
+
22
+ ## Usage
23
+
24
+ ### Token Contract - Rubysol "Off-Chain" Example
25
+
26
+ Let's try an "off-chain" token contract with
27
+ the "core" rubysol language.
28
+
29
+
30
+ ``` ruby
31
+ require 'rubysol'
32
+
33
+
34
+ class TestToken < Contract
35
+
36
+ event :Transfer, from: Address,
37
+ to: Address,
38
+ amount: UInt
39
+
40
+ storage name: String,
41
+ symbol: String,
42
+ decimals: UInt,
43
+ totalSupply: UInt,
44
+ balanceOf: mapping( Address, UInt )
45
+
46
+
47
+ sig [String, String, UInt, UInt]
48
+ def constructor(name:,
49
+ symbol:,
50
+ decimals:,
51
+ totalSupply:)
52
+ @name = name
53
+ @symbol = symbol
54
+ @decimals = decimals
55
+ @totalSupply = totalSupply
56
+
57
+ @balanceOf[msg.sender] = totalSupply
58
+ end
59
+
60
+ sig [Address, UInt], returns: Bool
61
+ def transfer( to:, amount: )
62
+ assert @balanceOf[msg.sender] >= amount, 'Insufficient balance'
63
+
64
+ @balanceOf[msg.sender] -= amount
65
+ @balanceOf[to] += amount
66
+
67
+ log Transfer, from: msg.sender, to: to, amount: amount
68
+ true
69
+ end
70
+ end # class TestToken
71
+ ```
72
+
73
+
74
+ and let's test run the contract ....
75
+
76
+ ``` ruby
77
+ pp TestToken.state_variable_definitions
78
+ pp TestToken.events
79
+ pp TestToken.is_abstract_contract
80
+
81
+ abi = TestToken.abi
82
+
83
+ pp TestToken.public_abi
84
+
85
+
86
+ contract = TestToken.new
87
+ pp contract
88
+
89
+
90
+ ## test globals (context)
91
+ pp contract.msg
92
+ pp contract.msg.sender
93
+ contract.msg.sender = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' # a(lice)
94
+ pp contract.msg.sender
95
+
96
+
97
+ pp contract.serialize
98
+ #=> {:name=>"",
99
+ # :symbol=>"",
100
+ # :decimals=>0,
101
+ # :totalSupply=>0,
102
+ # :balanceOf=>{}}
103
+
104
+
105
+ contract.constructor(
106
+ 'My Fun Token',
107
+ 'FUN',
108
+ 18,
109
+ 21000000 )
110
+
111
+ pp contract.serialize
112
+ #=> {:name=>"My Fun Token",
113
+ # :symbol=>"FUN",
114
+ # :decimals=>18,
115
+ # :totalSupply=>21000000,
116
+ # :balanceOf=>{'0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"=>21000000}}
117
+
118
+ pp contract.name
119
+ #=> "My Fun Token"
120
+ pp contract.symbol
121
+ #=> "FUN"
122
+ pp contract.decimals
123
+ #=> 18
124
+ pp contract.totalSupply
125
+ #=> 21000000
126
+
127
+ pp contract.balanceOf( '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
128
+ #=> 21000000
129
+
130
+
131
+ pp contract.transfer( '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
132
+ 10000 )
133
+
134
+ pp contract.serialize
135
+ #=> {:name=>"My Fun Token",
136
+ # :symbol=>"FUN",
137
+ # :decimals=>18,
138
+ # :totalSupply=>21000000,
139
+ # :balanceOf=>
140
+ # {"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"=>20990000,
141
+ # "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"=>10000}}
142
+
143
+ pp contract.balanceOf( '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
144
+ #=> 20990000
145
+ pp contract.balanceOf( '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
146
+ #=> 10000
147
+ ```
148
+
149
+
150
+ And so on. To be continued ...
151
+
152
+
153
+
154
+ ## Bonus - More Blockchain (Crypto) Tools, Libraries & Scripts In Ruby
155
+
156
+ See [**/blockchain**](https://github.com/rubycocos/blockchain)
157
+ at the ruby code commons (rubycocos) org.
158
+
159
+
160
+
161
+
162
+ ## Questions? Comments?
163
+
164
+ Join us in the [Rubidity & Rubysol (community) discord (chat server)](https://discord.gg/3JRnDUap6y). Yes you can.
165
+ Your questions and commentary welcome.
166
+
167
+ Or post them over at the [Help & Support](https://github.com/geraldb/help) page. Thanks.
168
+
169
+
170
+
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'hoe'
2
+ require './lib/rubysol/version.rb'
3
+
4
+
5
+ Hoe.spec 'rubysol' do
6
+
7
+ self.version = Rubysol::Module::Lang::VERSION
8
+
9
+ self.summary = 'rubysol - ruby for (blockchain) layer 1 (l1) contracts / protocols with "off-chain" indexer; 100% compatible with solidity datatypes and abis'
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
+ ['solidity-typed', '>= 0.2.0'],
23
+ ['digest-lite'], ## pulls in keccak256
24
+ ['hexutils'], ## pulls in hex/to_hex (decode/encode_hex)
25
+ ]
26
+
27
+
28
+ self.licenses = ['Public Domain']
29
+
30
+ self.spec_extras = {
31
+ required_ruby_version: '>= 2.3'
32
+ }
33
+ end
@@ -0,0 +1,206 @@
1
+
2
+
3
+ class AbiProxy
4
+
5
+ ## todo: make data private!!!
6
+ ## make read access available via #each !!!
7
+ attr_accessor :contract_class
8
+
9
+ def initialize(contract_class)
10
+ @contract_class = contract_class
11
+ @generated = false
12
+
13
+ parents = contract_class.linearized_parents
14
+ if parents.empty?
15
+ ## do nothing
16
+ else
17
+ _merge_state_variables( parents)
18
+ end
19
+ end
20
+
21
+ ###
22
+ ## keep a list of generated classes (only needed/possible to generatae once)
23
+ def self.contract_classes() @classes ||= []; end
24
+ def _contract_classes() self.class.contract_classes; end
25
+
26
+
27
+ ## generated? - use flag to keep track of code generation (only one time needed/required)
28
+ def generated?() @generated; end
29
+ def generate_functions
30
+ @generated = true
31
+
32
+ puts "==> generate (typed) functions for #{@contract_class.name}"
33
+
34
+ parents = @contract_class.linearized_parents
35
+ ## revesee order - why? why not?
36
+ parents.each do |parent|
37
+ _generate_functions( parent )
38
+ end
39
+ _generate_functions( @contract_class )
40
+ end
41
+
42
+
43
+ def _generate_functions( contract_class )
44
+ ## start with simple (no parents) for now
45
+
46
+ sigs = contract_class.sigs
47
+ puts "#{sigs.size} function signatures in #{contract_class.name}:"
48
+ pp sigs
49
+
50
+ ## note: only generate once for now!!!!
51
+ ## maybe check later if new sigs?? possible? why? why not?
52
+ if _contract_classes.include?( contract_class )
53
+ puts " already generated!"
54
+ else
55
+ ## generate global function (e.g. ERC20() or such)
56
+ Generator.global_function( contract_class )
57
+
58
+ # sigs.each do |name, definition|
59
+ # Generator.typed_function( contract_class, name,
60
+ # inputs: definition[:inputs] )
61
+ # end
62
+ _contract_classes << contract_class
63
+ end
64
+ end
65
+
66
+
67
+ ### todo/check -- where used? check for parent_contracts
68
+ # def parent_contracts
69
+ # contract_class.parent_contracts
70
+ # end
71
+
72
+
73
+
74
+ def _merge_state_variables( parents )
75
+ puts "[debug] AbiProxy#merge_parent_state_variables - #{contract_class}"
76
+ parent_state_variables = parents.map(&:state_variable_definitions).reverse
77
+ vars = parent_state_variables.reduce( {} ) { |mem,h| mem.merge(h) }
78
+ .merge( contract_class.state_variable_definitions)
79
+ puts "[debug] merged state_variables:"
80
+ pp vars
81
+ contract_class.state_variable_definitions = vars
82
+ end
83
+
84
+
85
+
86
+ def public_abi
87
+ ### note: calculate for now on-the-fly - why? why not?
88
+ ## cache results - why? why not?
89
+ contracts = [@contract_class] + @contract_class.linearized_parents
90
+ ## note: use reverse order -
91
+ ## most concreate comes last (for override)
92
+ contracts = contracts.reverse
93
+
94
+
95
+ ## todo/fix: add (contract) klass for source to data??? - why? why not?
96
+ data = {}
97
+
98
+ contracts.each do |klass|
99
+ klass.sigs.each do |name, definition|
100
+ if definition[:options].include?( :public )
101
+ ## check for override
102
+ ## and issue info for now
103
+ if data.has_key?( name )
104
+ ## Solidity lets developers change how a function in the parent contract is implemented
105
+ ## in the derived class. This is known as function overriding.
106
+ ## The function in the parent contract needs to be declared with
107
+ ## the keyword virtual to indicate that it can be overridden
108
+ ## in the deriving contract.
109
+ ##
110
+ ## todo: check for virtual keyword - why? why not?
111
+ if name == :constructor
112
+ puts " overriding constructor in #{klass.name}"
113
+ else
114
+ puts " overriding function #{name} in #{klass.name}"
115
+ end
116
+ end
117
+ data[name] = definition
118
+ else
119
+ puts " skip non-public sig - #{name} #{definition.inspect}"
120
+ end
121
+ end
122
+ end
123
+
124
+ data
125
+ end
126
+ alias_method :public_api, :public_abi ## only use public_abi (not api) - why? why not?
127
+
128
+
129
+ ## rename to to_abi_json or export_abi_json or solidity_abi_json or ??
130
+ def public_abi_as_json
131
+ ##
132
+ ## todo/fix: add events too!!!
133
+ ## todo/fix: add input parameter names too!!!!
134
+
135
+ # json format:
136
+ # Type - defines the nature of the function (receive, fallback, constructor)
137
+ # Name - defines the name of the function
138
+ # Inputs - array of objects with name, type, components
139
+ # Outputs - array of objects similar to inputs
140
+ # stateMutability - defines the mutability of the function (pure, view, non-payable or payable)
141
+ #
142
+ # "type": "constructor",
143
+ # "inputs": [
144
+ # { "type": "string", "name": "symbol" },
145
+ # { "type": "string", "name": "name" }
146
+ # ]
147
+ #
148
+ # "type": "function",
149
+ # "name": "balanceOf",
150
+ # "stateMutability": "view",
151
+ # "inputs": [
152
+ # { "type": "address", "name": "owner"}
153
+ # ],
154
+ # "outputs": [
155
+ # { "type": "uint256"}
156
+ # ]
157
+
158
+ data = []
159
+
160
+ public_abi.each do |name, definition|
161
+ inputs = definition[:inputs].map do |input|
162
+ ## todo: use a _arg1, _arg2,
163
+ ## count or such - why?
164
+ { 'type' => input.to_s,
165
+ 'name' => '_'
166
+ }
167
+ end
168
+
169
+ state_mutability = 'nonpayable' ## default is nonpayable (double check)
170
+ state_mutability = 'view' if definition[:options].include?( :view )
171
+
172
+ if name == :constructor
173
+ data << {
174
+ 'type' => 'constructor',
175
+ 'stateMutability' => state_mutability,
176
+ 'inputs' => inputs
177
+ }
178
+ else
179
+ ## check if outputs is a single entry or array or nil or hash?
180
+ outputs = definition[:outputs].is_a?( Array ) ?
181
+ definition[:outputs] : [definition[:outputs]]
182
+ outputs = outputs.map do |output|
183
+ ## todo: use a _arg1, _arg2,
184
+ ## count or such - why?
185
+ { 'type' => output.to_s,
186
+ 'name' => '_'
187
+ }
188
+ end
189
+
190
+ data << {
191
+ 'type' => 'function',
192
+ 'name' => name.to_s,
193
+ 'stateMutability' => state_mutability,
194
+ 'inputs' => inputs,
195
+ 'outputs' => outputs,
196
+ }
197
+ end
198
+ end
199
+ data
200
+ end
201
+
202
+
203
+ def public_abi_to_json
204
+ JSON.pretty_generate( public_abi_as_json )
205
+ end
206
+ end # class AbiProxy
@@ -0,0 +1,27 @@
1
+
2
+
3
+ ### use a differet name? why? why not?
4
+ ## e.g CryptoFunctons or ...
5
+
6
+ module CryptoHelper
7
+ ###
8
+ # Digest::KeccakLite.new( 256 ).hexdigest( 'abc' ) # or
9
+ # Digest::KeccakLite.hexdigest( 'abc', 256 )
10
+ # #=> "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45"
11
+
12
+
13
+ ## fix-fix-fix - return a bytes32 type!!!!!!
14
+ def keccak256( input )
15
+ ## todo/fix: check if input is binary string
16
+ ## (convert to bytes - why? why not?)
17
+ ## should really always use hex_to_bin !!!
18
+ ## and convert the result in the end only - why? why not??
19
+
20
+ str = Types::String.new( input )
21
+
22
+ ## fix: convert hexdigest to binary
23
+ '0x' + Digest::KeccakLite.hexdigest( str.as_data, 256 )
24
+ end
25
+
26
+
27
+ end # module CryptoHelper
@@ -0,0 +1,56 @@
1
+ ### use a differet name? why? why not?
2
+ ### Runtime__ or Runtime ___ ???
3
+
4
+
5
+ module RuntimeHelper
6
+
7
+ ### keep assert here - why? why not?
8
+ ## use AssertHelper or ErrorHelper or ...
9
+ ##
10
+ ## note: change from require to assert
11
+ ## to avoid confusion with ruby require - why? why not?
12
+ def assert(condition, message='no message')
13
+ unless condition
14
+ # caller_location = caller_locations.detect { |l| l.path.include?('/app/models/contracts') }
15
+ # file = caller_location.path.gsub(%r{.*app/models/contracts/}, '')
16
+ # line = caller_location.lineno
17
+
18
+ puts "!! ASSERT FAILED - #{message}"
19
+
20
+ error_message = "#{message}" ##. (#{file}:#{line})"
21
+ ## todo/fix: change to (built-in) ???Error, ....
22
+ ## check for error to raise for assertion fail??
23
+ raise error_message
24
+ end
25
+ end
26
+
27
+
28
+ ## note: for now this is just the solidity alias/used name
29
+ ## for ruby's self - anything missing - why? why not?
30
+ ## - to get the address - use address( this )
31
+ def this() self; end
32
+
33
+
34
+ ## todo/check: change current_transaction to tx - why? why not?
35
+ def current_transaction() Runtime.current_transaction; end
36
+
37
+ def msg() Runtime.msg; end
38
+ def block() Runtime.block; end
39
+
40
+ def log( event_klass, *args, **kwargs )
41
+
42
+ raise "event class expected; got: >#{event_klass.inspect}<; sorry" unless event_klass.ancestors.include?( Types::Event)
43
+
44
+ rec = if kwargs.size > 0
45
+ event_klass.new( **kwargs )
46
+ else
47
+ event_klass.new( *args )
48
+ end
49
+ data = rec.as_data ## "serialize" to "plain" types
50
+
51
+ current_transaction.log_event( { event: event_klass.name,
52
+ data: data })
53
+ end
54
+
55
+ end # module RuntimeHelper
56
+