rubysol 0.1.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: 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
+