rubysol-contracts 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: 93eaae4a1c50deb7cd758c3d4722586ae4db0043658ff5f67c4e3511f1c39067
4
+ data.tar.gz: 5abc50f516b527db09d6b88d8d1b2ecdcb7813f60149bea0eacf674e627b767d
5
+ SHA512:
6
+ metadata.gz: ed4147e02ef81e836a395d62d3bdebf85badb296b51015588141c1f0f0ad8206fa70c689856c5ecce70d8d8abc0ed656d33307a878ef5986f1e3f72124e2d57f
7
+ data.tar.gz: a9772883af9fb960c6771711274ae79d94715609c4bdf2ed8926cdde93330e10b9bfee5adbd629a883480ca05bb204043104e58361d6f98f2eb02c5f0c77b348
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ### 0.1.0 / 2023-11-16
2
+
3
+ * Everything is new. First release
data/Manifest.txt ADDED
@@ -0,0 +1,14 @@
1
+ CHANGELOG.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ lib/rubysol/contracts.rb
6
+ lib/rubysol/contracts/erc20.rb
7
+ lib/rubysol/contracts/erc20_liquidity_pool.rb
8
+ lib/rubysol/contracts/erc721.rb
9
+ lib/rubysol/contracts/ether_erc20_bridge.rb
10
+ lib/rubysol/contracts/ethscription_erc20_bridge.rb
11
+ lib/rubysol/contracts/generative_erc721.rb
12
+ lib/rubysol/contracts/open_edition_erc721.rb
13
+ lib/rubysol/contracts/public_mint_erc20.rb
14
+ lib/rubysol/contracts/version.rb
data/README.md ADDED
@@ -0,0 +1,315 @@
1
+ # Rubysol Contracts
2
+
3
+ rubysol-contracts - standard contracts (incl. erc20, erc721, etc) for ruby for layer 1 (l1) with "off-chain" indexer
4
+
5
+ * home :: [github.com/s6ruby/rubidity](https://github.com/s6ruby/rubidity)
6
+ * bugs :: [github.com/s6ruby/rubidity/issues](https://github.com/s6ruby/rubidity/issues)
7
+ * gem :: [rubygems.org/gems/rubysol-contracts](https://rubygems.org/gems/rubysol-contracts)
8
+ * rdoc :: [rubydoc.info/gems/rubysol-contracts](http://rubydoc.info/gems/rubysol-contracts)
9
+
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
+ See [**Rubysol - Ruby for Layer 1 (L1) Contracts / Protocols with "Off-Chain" Indexer** »](https://github.com/s6ruby/rubidity/tree/master/rubysol)
20
+
21
+
22
+
23
+
24
+ ## Usage
25
+
26
+ Let's try the PublicMintERC20 contract...
27
+
28
+ <details>
29
+ <summary markdown="1">Show Source</summary>
30
+
31
+ [contracts/public_mint_erc20.rb](lib/rubysol/contracts/public_mint_erc20.rb):
32
+
33
+ ```ruby
34
+ class PublicMintERC20 < ERC20
35
+
36
+ storage maxSupply: UInt,
37
+ perMintLimit: UInt
38
+
39
+ sig [String, String, UInt, UInt, UInt]
40
+ def constructor(
41
+ name:,
42
+ symbol:,
43
+ maxSupply:,
44
+ perMintLimit:,
45
+ decimals:
46
+ )
47
+ super( name: name,
48
+ symbol: symbol,
49
+ decimals: decimals)
50
+
51
+ @maxSupply = maxSupply
52
+ @perMintLimit = perMintLimit
53
+ end
54
+
55
+
56
+ sig [UInt]
57
+ def mint( amount: )
58
+ assert(amount > 0, 'Amount must be positive')
59
+ assert(amount <= @perMintLimit, 'Exceeded mint limit')
60
+
61
+ assert( @totalSupply + amount <= @maxSupply, 'Exceeded max supply')
62
+
63
+ _mint(to: msg.sender, amount: amount)
64
+ end
65
+
66
+ sig [Address, UInt]
67
+ def airdrop( to:, amount: )
68
+ assert(amount > 0, 'Amount must be positive')
69
+ assert(amount <= @perMintLimit, 'Exceeded mint limit')
70
+
71
+ assert(@totalSupply + amount <= @maxSupply, 'Exceeded max supply')
72
+
73
+ _mint(to: to, amount: amount)
74
+ end
75
+ end
76
+ ```
77
+
78
+ </details>
79
+
80
+ that builds on the ERC20 (base) contract.
81
+
82
+ <details>
83
+ <summary markdown="1">Show Source</summary>
84
+
85
+ [contracts/erc20.rb](lib/rubysol/contracts/erc20.rb):
86
+
87
+ ```ruby
88
+ class ERC20 < Contract
89
+
90
+ event :Transfer, from: Address,
91
+ to: Address,
92
+ amount: UInt
93
+ event :Approval, owner: Address,
94
+ spender: Address,
95
+ amount: UInt
96
+
97
+ storage name: String,
98
+ symbol: String,
99
+ decimals: UInt,
100
+ totalSupply: UInt,
101
+ balanceOf: mapping( Address, UInt ),
102
+ allowance: mapping( Address, mapping( Address, UInt ))
103
+
104
+
105
+ sig [String, String, UInt]
106
+ def constructor(name:,
107
+ symbol:,
108
+ decimals:)
109
+ @name = name
110
+ @symbol = symbol
111
+ @decimals = decimals
112
+ end
113
+
114
+
115
+ sig [Address, UInt], returns: Bool
116
+ def approve( spender:,
117
+ amount: )
118
+ @allowance[msg.sender][spender] = amount
119
+
120
+ log Approval, owner: msg.sender, spender: spender, amount: amount
121
+
122
+ true
123
+ end
124
+
125
+
126
+ sig [Address, UInt], returns: Bool
127
+ def decreaseAllowanceUntilZero( spender:,
128
+ difference: )
129
+ allowed = @allowance[msg.sender][spender]
130
+
131
+ newAllowed = allowed > difference ? allowed - difference : 0
132
+
133
+ approve(spender: spender, amount: newAllowed)
134
+
135
+ true
136
+ end
137
+
138
+
139
+ sig [Address, UInt], returns: Bool
140
+ def transfer( to:,
141
+ amount: )
142
+ assert @balanceOf[msg.sender] >= amount, 'Insufficient balance'
143
+
144
+ @balanceOf[msg.sender] -= amount
145
+ @balanceOf[to] += amount
146
+
147
+ log Transfer, from: msg.sender, to: to, amount: amount
148
+
149
+ true
150
+ end
151
+
152
+ sig [Address, Address, UInt], returns: Bool
153
+ def transferFrom(
154
+ from:,
155
+ to:,
156
+ amount:)
157
+ allowed = @allowance[from][msg.sender]
158
+
159
+ assert @balanceOf[from] >= amount, 'Insufficient balance'
160
+ assert allowed >= amount, 'Insufficient allowance'
161
+
162
+ @allowance[from][msg.sender] = allowed - amount
163
+
164
+ @balanceOf[from] -= amount
165
+ @balanceOf[to] += amount
166
+
167
+ log Transfer, from: from, to: to, amount: amount
168
+
169
+ true
170
+ end
171
+
172
+ sig [Address, UInt]
173
+ def _mint( to:,
174
+ amount: )
175
+ @totalSupply += amount
176
+ @balanceOf[to] += amount
177
+
178
+ log Transfer, from: address(0), to: to, amount: amount
179
+ end
180
+
181
+ sig [Address, UInt]
182
+ def _burn( from:,
183
+ amount: )
184
+ @balanceOf[from] -= amount
185
+ @totalSupply -= amount
186
+
187
+ log Transfer, from: from, to: address(0), amount: amount
188
+ end
189
+ end
190
+ ```
191
+
192
+ </details>
193
+
194
+
195
+
196
+ Let's go.
197
+
198
+ ``` ruby
199
+ require 'rubysol/contracts'
200
+
201
+ contract = PublicMintERC20.construct(
202
+ name: 'My Fun Token', # String,
203
+ symbol: 'FUN', # String,
204
+ maxSupply: 21000000, # UInt,
205
+ perMintLimit: 1000, # UInt,
206
+ decimals: 18, # UInt
207
+ )
208
+
209
+
210
+ contract.serialize
211
+ # {:name=>"My Fun Token",
212
+ # :symbol=>"FUN",
213
+ # :decimals=>18,
214
+ # :totalSupply=>0,
215
+ # :balanceOf=>{},
216
+ # :allowance=>{},
217
+ # :maxSupply=>21000000,
218
+ # :perMintLimit=>1000}
219
+
220
+ alice = '0x'+'a'*40 # e.g. '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
221
+ bob = '0x'+'b'*40 # e.g. '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
222
+ charlie = '0x'+'c'*40 # e.g. '0xcccccccccccccccccccccccccccccccccccccccc'
223
+
224
+
225
+ # sig [UInt]
226
+ # def mint( amount: )
227
+ contract.msg.sender = alice
228
+
229
+ contract.mint( 100 )
230
+ contract.mint( 200 )
231
+
232
+ contract.msg.sender = bob
233
+
234
+ contract.mint( 300 )
235
+ contract.mint( 400 )
236
+
237
+ # sig [Address, UInt]
238
+ # def airdrop( to:, amount: )
239
+ contract.airdrop( alice, 500 )
240
+ contract.airdrop( charlie, 600 )
241
+
242
+ contract.serialize
243
+ # {:name=>"My Fun Token",
244
+ # :symbol=>"FUN",
245
+ # :decimals=>18,
246
+ # :totalSupply=>2100,
247
+ # :balanceOf=>
248
+ # {"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"=>800,
249
+ # "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"=>700,
250
+ # "0xcccccccccccccccccccccccccccccccccccccccc"=>600},
251
+ # :allowance=>{},
252
+ # :maxSupply=>21000000,
253
+ # :perMintLimit=>1000}
254
+
255
+
256
+ # sig [Address, UInt], returns: Bool
257
+ # def transfer( to:, amount: )
258
+ contract.transfer( alice, 1 )
259
+ contract.transfer( charlie, 2 )
260
+
261
+ # sig [Address, UInt], returns: Bool
262
+ # def approve( spender:, amount: )
263
+ contract.approve( alice, 11 )
264
+ contract.approve( charlie, 22 )
265
+
266
+
267
+ # sig [Address, Address, UInt], returns: Bool
268
+ # def transferFrom( from:, to:, amount:)
269
+ contract.msg.sender = alice
270
+
271
+ contract.approve( bob, 33 )
272
+
273
+ contract.transferFrom( bob, charlie, 3 )
274
+ contract.transferFrom( bob, alice, 4 )
275
+
276
+ contract.serialize
277
+ # {:name=>"My Fun Token",
278
+ # :symbol=>"FUN",
279
+ # :decimals=>18,
280
+ # :totalSupply=>2100,
281
+ # :balanceOf=>
282
+ # {"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"=>805,
283
+ # "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"=>690,
284
+ # "0xcccccccccccccccccccccccccccccccccccccccc"=>605},
285
+ # :allowance=>
286
+ # {"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"=> {
287
+ # "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"=>4,
288
+ # "0xcccccccccccccccccccccccccccccccccccccccc"=>22},
289
+ # "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"=> {
290
+ # "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"=>33}},
291
+ # :maxSupply=>21000000,
292
+ # :perMintLimit=>1000}
293
+ ```
294
+
295
+ And so on. That's it for now.
296
+
297
+
298
+
299
+ ## Bonus - More Blockchain (Crypto) Tools, Libraries & Scripts In Ruby
300
+
301
+ See [**/blockchain**](https://github.com/rubycocos/blockchain)
302
+ at the ruby code commons (rubycocos) org.
303
+
304
+
305
+
306
+
307
+
308
+ ## Questions? Comments?
309
+
310
+ Join us in the [Rubidity & Rubysol (community) discord (chat server)](https://discord.gg/3JRnDUap6y). Yes you can.
311
+ Your questions and commentary welcome.
312
+
313
+ Or post them over at the [Help & Support](https://github.com/geraldb/help) page. Thanks.
314
+
315
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'hoe'
2
+ require './lib/rubysol/contracts/version.rb'
3
+
4
+ Hoe.spec 'rubysol-contracts' do
5
+
6
+ self.version = Rubysol::Module::Contracts::VERSION
7
+
8
+ self.summary = 'rubysol-contracts - standard contracts (incl. erc20, erc721, etc) for ruby for layer 1 (l1) with "off-chain" indexer'
9
+ self.description = summary
10
+
11
+ self.urls = { home: 'https://github.com/s6ruby/rubidity' }
12
+
13
+ self.author = 'Gerald Bauer'
14
+ self.email = 'gerald.bauer@gmail.com'
15
+
16
+ # switch extension to .markdown for gihub formatting
17
+ self.readme_file = 'README.md'
18
+ self.history_file = 'CHANGELOG.md'
19
+
20
+ self.extra_deps = [
21
+ ['rubysol', '>= 0.1.0'],
22
+ ]
23
+
24
+ self.licenses = ['Public Domain']
25
+
26
+ self.spec_extras = {
27
+ required_ruby_version: '>= 2.3'
28
+ }
29
+ end
@@ -0,0 +1,77 @@
1
+ class ERC20 < Contract # abstract: true
2
+
3
+ event :Transfer, from: Address,
4
+ to: Address,
5
+ amount: UInt
6
+ event :Approval, owner: Address,
7
+ spender: Address,
8
+ amount: UInt
9
+
10
+ storage name: String,
11
+ symbol: String,
12
+ decimals: UInt,
13
+ totalSupply: UInt,
14
+ balanceOf: mapping( Address, UInt),
15
+ allowance: mapping( Address, mapping( Address, UInt))
16
+
17
+ sig [String, String, UInt]
18
+ def constructor(name:, symbol:, decimals:)
19
+ @name = name
20
+ @symbol = symbol
21
+ @decimals = decimals
22
+ end
23
+
24
+ sig [Address, UInt], returns: Bool
25
+ def approve( spender:, amount: )
26
+ @allowance[msg.sender][spender] = amount
27
+
28
+ log Approval, owner: msg.sender, spender: spender, amount: amount
29
+
30
+ true
31
+ end
32
+
33
+ sig [Address, UInt], returns: Bool
34
+ def transfer( to:, amount: )
35
+ assert @balanceOf[msg.sender] >= amount, "Insufficient balance"
36
+
37
+ @balanceOf[msg.sender] -= amount
38
+ @balanceOf[to] += amount
39
+
40
+ log Transfer, from: msg.sender, to: to, amount: amount
41
+
42
+ true
43
+ end
44
+
45
+ sig [Address, Address, UInt], returns: Bool
46
+ def transferFrom( from:, to:, amount: )
47
+ allowed = @allowance[from][msg.sender]
48
+
49
+ assert @balanceOf[from] >= amount, "Insufficient balance"
50
+ assert allowed >= amount, "Insufficient allowance"
51
+
52
+ @allowance[from][msg.sender] = allowed - amount
53
+
54
+ @balanceOf[from] -= amount
55
+ @balanceOf[to] += amount
56
+
57
+ log Transfer, from: from, to: to, amount: amount
58
+
59
+ true
60
+ end
61
+
62
+ sig [Address, UInt]
63
+ def _mint( to:, amount: )
64
+ @totalSupply += amount
65
+ @balanceOf[to] += amount
66
+
67
+ log Transfer, from: address(0), to: to, amount: amount
68
+ end
69
+
70
+ sig [Address, UInt]
71
+ def _burn( from:, amount: )
72
+ @balanceOf[from] -= amount
73
+ @totalSupply -= amount
74
+
75
+ log Transfer, from: from, to: address(0), amount: amount
76
+ end
77
+ end # class ERC20
@@ -0,0 +1,84 @@
1
+ class ERC20LiquidityPool < Contract
2
+
3
+ storage token0: Address,
4
+ token1: Address
5
+
6
+ sig [Address, Address]
7
+ def constructor( token0:,
8
+ token1: )
9
+ @token0 = token0
10
+ @token1 = token1
11
+ end
12
+
13
+ sig [UInt, UInt]
14
+ def addLiquidity( token0Amount:,
15
+ token1Amount: )
16
+ ERC20(@token0).transferFrom(
17
+ from: msg.sender,
18
+ to: address( this ), ## note: add support for this (alias for self ??)
19
+ amount: token0Amount
20
+ )
21
+
22
+ ERC20(@token1).transferFrom(
23
+ from: msg.sender,
24
+ to: address(this),
25
+ amount: token1Amount
26
+ )
27
+ end
28
+
29
+
30
+ sig [], :view ### fix- support multiple, returns: [UInt, UInt]
31
+ def reserves
32
+ {
33
+ token0: ERC20(@token0).balanceOf(address(this)),
34
+ token1: ERC20(@token1).balanceOf(address(this))
35
+ }
36
+ end
37
+
38
+
39
+
40
+ sig [Address, Address, UInt], :view, returns: UInt
41
+ def calculateOutputAmount( inputToken:,
42
+ outputToken:,
43
+ inputAmount: )
44
+ inputReserve = ERC20(inputToken).balanceOf(address(this))
45
+ outputReserve = ERC20(outputToken).balanceOf(address(this))
46
+
47
+ ((inputAmount * outputReserve) / (inputReserve + inputAmount)).to_i
48
+ end
49
+
50
+
51
+ sig [Address, Address, UInt], returns: UInt
52
+ def swap(
53
+ inputToken:,
54
+ outputToken:,
55
+ inputAmount: )
56
+ assert [@token0, @token1].include?(inputToken), "Invalid input token"
57
+ assert [@token0, @token1].include?(outputToken), "Invalid output token"
58
+
59
+ assert inputToken != outputToken, "Input and output tokens can't be the same"
60
+
61
+ outputAmount = calculateOutputAmount(
62
+ inputToken: inputToken,
63
+ outputToken: outputToken,
64
+ inputAmount: inputAmount
65
+ )
66
+
67
+ outputReserve = ERC20(outputToken).balanceOf( address(this))
68
+
69
+ assert outputAmount <= outputReserve, "Insufficient output reserve"
70
+
71
+ ERC20(inputToken).transferFrom(
72
+ from: msg.sender,
73
+ to: dumbContractId(this),
74
+ amount: inputAmount
75
+ )
76
+
77
+ ERC20(outputToken).transfer(
78
+ msg.sender,
79
+ outputAmount
80
+ )
81
+
82
+ outputAmount
83
+ end
84
+ end
@@ -0,0 +1,121 @@
1
+ class ERC721 < Contract
2
+
3
+ event :Transfer, from: Address,
4
+ to: Address,
5
+ id: UInt
6
+ event :Approval, owner: Address,
7
+ spender: Address,
8
+ id: UInt
9
+ event :ApprovalForAll, owner: Address,
10
+ operator: Address,
11
+ approved: Bool
12
+
13
+ storage name: String,
14
+ symbol: String,
15
+ _ownerOf: mapping( UInt, Address ),
16
+ _balanceOf: mapping( Address, UInt ),
17
+ getApproved: mapping( UInt, Address ),
18
+ isApprovedForAll: mapping( Address, mapping( Address, Bool ))
19
+
20
+ sig [String, String]
21
+ def constructor( name:,
22
+ symbol: )
23
+ @name = name
24
+ @symbol = symbol
25
+ end
26
+
27
+
28
+ sig [ UInt ], :view, returns: Address
29
+ def ownerOf( id )
30
+ owner = @_ownerOf[id]
31
+ assert owner != address(0), "ERC721: owner query for nonexistent token"
32
+
33
+ owner
34
+ end
35
+
36
+ sig [ Address ], :view, returns: UInt
37
+ def balanceOf( owner: )
38
+ assert owner != address(0), "ERC721: balance query for nonexistent owner"
39
+
40
+ @_balanceOf[owner]
41
+ end
42
+
43
+ sig [ Address, UInt]
44
+ def approve( spender:,
45
+ id: )
46
+ owner = @_ownerOf[id]
47
+
48
+ assert msg.sender == owner || @isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"
49
+
50
+ @getApproved[id] = spender;
51
+
52
+ log Approval, owner: owner, spender: spender, id: id
53
+ end
54
+
55
+ sig [Address, Bool]
56
+ def setApprovalForAll( operator:,
57
+ approved: )
58
+ @isApprovedForAll[msg.sender][operator] = approved;
59
+
60
+ log ApprovalForAll, owner: msg.sender, operator: operator, approved: approved
61
+ end
62
+
63
+ sig [ Address, Address, UInt]
64
+ def transferFrom( from:,
65
+ to:,
66
+ id: )
67
+ assert from == @_ownerOf[id], "ERC721: transfer of token that is not own"
68
+ assert to != address(0), "ERC721: transfer to the zero address"
69
+
70
+ assert(
71
+ msg.sender == from ||
72
+ @getApproved[id] == msg.sender ||
73
+ @isApprovedForAll[from][msg.sender],
74
+ "NOT_AUTHORIZED"
75
+ )
76
+
77
+ @_balanceOf[from] -= 1
78
+ @_balanceOf[to] += 1
79
+
80
+ @_ownerOf[id] = to
81
+
82
+ @getApproved[id] = address(0)
83
+ end
84
+
85
+ sig [UInt]
86
+ def _exists( id )
87
+ @_ownerOf[id] != address(0)
88
+ end
89
+
90
+ sig [Address, UInt]
91
+ def _mint( to:,
92
+ id: )
93
+ assert to != address(0), "ERC721: mint to the zero address"
94
+ assert @_ownerOf[id] == address(0), "ERC721: token already minted"
95
+
96
+ @_balanceOf[to] += 1
97
+
98
+ @_ownerOf[id] = to
99
+
100
+ log Transfer, from: address(0), to: to, id: id
101
+ end
102
+
103
+ sig [UInt]
104
+ def _burn( id: )
105
+ owner = @_ownerOf[id]
106
+
107
+ assert owner != address(0), "ERC721: burn of nonexistent token"
108
+
109
+ @_balanceOf[owner] -= 1
110
+
111
+ @_ownerOf[id] = address(0)
112
+
113
+ @getApproved[id] = address(0)
114
+
115
+ log Transfer, from: owner, to: address(0), id: id
116
+ end
117
+
118
+ sig [UInt], :view, returns: String
119
+ def tokenURI( id: )
120
+ end
121
+ end
@@ -0,0 +1,54 @@
1
+ class EtherERC20Bridge < ERC20
2
+
3
+ event :InitiateWithdrawal, from: Address, amount: UInt
4
+ event :WithdrawalComplete, to: Address, amount: UInt
5
+
6
+ storage trustedSmartContract: Address,
7
+ pendingWithdrawals: mapping( Address, UInt )
8
+
9
+ sig [String, String, Address]
10
+ def constructor(
11
+ name:,
12
+ symbol:,
13
+ trustedSmartContract:)
14
+ super(name: name, symbol: symbol, decimals: 18)
15
+
16
+ @trustedSmartContract = trustedSmartContract
17
+ end
18
+
19
+ sig [Address, UInt]
20
+ def bridgeIn( to:, amount: )
21
+ assert(
22
+ address(msg.sender) == @trustedSmartContract,
23
+ "Only the trusted smart contract can bridge in tokens"
24
+ )
25
+
26
+ _mint(to: to, amount: amount)
27
+ end
28
+
29
+ sig [UInt]
30
+ def bridgeOut( amount: )
31
+ _burn(from: msg.sender, amount: amount)
32
+
33
+ @pendingWithdrawals[address(msg.sender)] += amount
34
+
35
+ log InitiateWithdrawal, from: address(msg.sender), amount: amount
36
+ end
37
+
38
+ sig [Address, UInt]
39
+ def markWithdrawalComplete( to:, amount: )
40
+ assert(
41
+ address(msg.sender) == @trustedSmartContract,
42
+ 'Only the trusted smart contract can mark withdrawals as complete'
43
+ )
44
+
45
+ assert(
46
+ @pendingWithdrawals[to] >= amount,
47
+ 'Insufficient pending withdrawal'
48
+ )
49
+
50
+ @pendingWithdrawals[to] -= amount
51
+
52
+ log WithdrawalComplete, to: to, amount: amount
53
+ end
54
+ end
@@ -0,0 +1,119 @@
1
+ class EthscriptionERC20Bridge < ERC20
2
+
3
+ event :InitiateWithdrawal, from: Address, escrowedId: InscriptionId
4
+ event :WithdrawalComplete, to: Address, escrowedId: InscriptionId
5
+
6
+ storage ethscriptionsTicker: String,
7
+ ethscriptionMintAmount: UInt,
8
+ ethscriptionMaxSupply: UInt,
9
+ ethscriptionDeployId: InscriptionId,
10
+ trustedSmartContract: Address,
11
+ pendingWithdrawalEthscriptionToOwner: mapping( InscriptionId, Address ),
12
+ bridgedEthscriptionToOwner: mapping( InscriptionId, Address )
13
+
14
+ sig [String, String, Address, InscriptionId]
15
+ def constructor(
16
+ name:,
17
+ symbol:,
18
+ trustedSmartContract:,
19
+ ethscriptionDeployId:)
20
+ super(name: name, symbol: symbol, decimals: 18)
21
+
22
+ @trustedSmartContract = trustedSmartContract
23
+ @ethscriptionDeployId = ethscriptionDeployId
24
+
25
+ deploy = esc.findEthscriptionById( ethscriptionDeployId )
26
+ uri = deploy.contentUri
27
+ parsed = JSON.parse(uri.split("data:,").last)
28
+
29
+ assert(parsed['op'] == 'deploy', "Invalid ethscription deploy id")
30
+ assert(parsed['p'] == 'erc-20', "Invalid protocol")
31
+
32
+ @ethscriptionsTicker = parsed['tick']
33
+ @ethscriptionMintAmount = parsed['lim']
34
+ @ethscriptionMaxSupply = parsed['max']
35
+ end
36
+
37
+
38
+ sig [Address, InscriptionId]
39
+ def bridgeIn( to:, escrowedId: )
40
+ assert(
41
+ address(msg.sender) == @trustedSmartContract,
42
+ "Only the trusted smart contract can bridge in tokens"
43
+ )
44
+
45
+ assert(
46
+ @bridgedEthscriptionToOwner[escrowedId] == address(0),
47
+ "Ethscription already bridged in"
48
+ )
49
+
50
+ assert(
51
+ @pendingWithdrawalEthscriptionToOwner[escrowedId] == address(0),
52
+ "Ethscription withdrawal initiated"
53
+ )
54
+
55
+ ethscription = esc.findEthscriptionById(escrowedId)
56
+ uri = ethscription.contentUri
57
+
58
+ match_data = uri.match(/data:,{"p":"erc-20","op":"mint","tick":"([a-z]+)","id":"([1-9]+\d*)","amt":"([1-9]+\d*)"}/)
59
+
60
+ assert(match_data.present?, "Invalid ethscription content uri")
61
+
62
+ tick, id, amt = match_data.captures
63
+
64
+ tick = tick.cast(:string)
65
+ id = id.cast(:uint256)
66
+ amt = amt.cast(:uint256)
67
+
68
+ assert(tick == @ethscriptionsTicker, "Invalid ethscription ticker")
69
+ assert(amt == @ethscriptionMintAmount, "Invalid ethscription mint amount")
70
+
71
+ maxId = @ethscriptionMaxSupply / @ethscriptionMintAmount
72
+
73
+ assert(id > 0 && id <= maxId, "Invalid token id")
74
+
75
+ assert(
76
+ ethscription.currentOwner == @trustedSmartContract,
77
+ "Ethscription not owned by recipient. Observed owner: #{ethscription.currentOwner}, expected owner: #{@trustedSmartContract}"
78
+ )
79
+
80
+ assert(
81
+ ethscription.previousOwner == to,
82
+ "Ethscription not previously owned by to. Observed previous owner: #{ethscription.previousOwner}, expected previous owner: #{to}"
83
+ )
84
+
85
+ @bridgedEthscriptionToOwner[escrowedId] = to
86
+ _mint(to: to, amount: @ethscriptionMintAmount * (10 ** decimals))
87
+ end
88
+
89
+
90
+ sig [InscriptionId]
91
+ def bridgeOut( escrowedId )
92
+ assert( @bridgedEthscriptionToOwner[escrowedId] == address(msg.sender), "Ethscription not owned by sender")
93
+
94
+ _burn(from: msg.sender, amount: @ethscriptionMintAmount * (10 ** decimals))
95
+
96
+ @bridgedEthscriptionToOwner[escrowedId] = address(0)
97
+ @pendingWithdrawalEthscriptionToOwner[escrowedId] = address(msg.sender)
98
+
99
+ log InitiateWithdrawal, from: address(msg.sender), escrowedId: :ethscriptionId
100
+ end
101
+
102
+
103
+ sig [Address, InscriptionId]
104
+ def markWithdrawalComplete( to:, escrowedId: )
105
+ assert(
106
+ address(msg.sender) == @trustedSmartContract,
107
+ 'Only the trusted smart contract can mark withdrawals as complete'
108
+ )
109
+
110
+ assert(
111
+ @pendingWithdrawalEthscriptionToOwner[escrowedId] == to,
112
+ "Withdrawal not initiated"
113
+ )
114
+
115
+ @pendingWithdrawalEthscriptionToOwner[escrowedId] = address(0)
116
+
117
+ log WithdrawalComplete, to: to, escrowedId: :ethscriptionId
118
+ end
119
+ end
@@ -0,0 +1,93 @@
1
+ class GenerativeERC721 < ERC721
2
+
3
+ storage generativeScript: String,
4
+ tokenIdToSeed: mapping( UInt, UInt ),
5
+ totalSupply: UInt,
6
+ maxSupply: UInt,
7
+ maxPerAddress: UInt,
8
+ description: String
9
+
10
+ sig [ String, String, String, UInt, String, UInt]
11
+ def constructor(
12
+ name:,
13
+ symbol:,
14
+ generativeScript:,
15
+ maxSupply:,
16
+ description:,
17
+ maxPerAddress: )
18
+ super(name: name, symbol: symbol)
19
+
20
+ @maxSupply = maxSupply
21
+ @maxPerAddress = maxPerAddress
22
+ @description = description
23
+ @generativeScript = generativeScript
24
+ end
25
+
26
+ sig [UInt]
27
+ def mint( amount )
28
+ assert(amount > 0, 'Amount must be positive')
29
+ assert(amount + @_balanceOf[msg.sender] <= @maxPerAddress, 'Exceeded mint limit')
30
+ assert(amount + @totalSupply <= @maxSupply, 'Exceeded max supply')
31
+
32
+ hash = block.blockhash(block.number).cast(:uint256) % (2 ** 48)
33
+
34
+ amount.times do |id|
35
+ tokenId = @totalSupply + id
36
+ seed = hash + tokenId
37
+
38
+ @tokenIdToSeed[tokenId] = seed
39
+
40
+ _mint(to: msg.sender, id: tokenId)
41
+ end
42
+
43
+ @totalSupply += amount
44
+ end
45
+
46
+ sig [UInt], :view, returns: String
47
+ def tokenURI( id )
48
+ assert( _exists(id: id), 'ERC721Metadata: URI query for nonexistent token')
49
+
50
+ html = getHTML(seed: @tokenIdToSeed[id])
51
+
52
+ html_as_base_64_data_uri = "data:text/html;base64,#{Base64.strict_encode64(html)}"
53
+
54
+ json_data = {
55
+ name: "#{@name} ##{string(id)}",
56
+ description: @description,
57
+ animation_url: html_as_base_64_data_uri,
58
+ }.to_json
59
+
60
+ "data:application/json,#{json_data}"
61
+ end
62
+
63
+ sig [UInt], :view, returns: String
64
+ def getHTML( seed )
65
+ %{<!DOCTYPE html>
66
+ <html>
67
+ <head>
68
+ <style>
69
+ body,
70
+ html {
71
+ width: 100%;
72
+ height: 100%;
73
+ margin: 0;
74
+ padding: 0;
75
+ overflow: hidden;
76
+ display: block;
77
+ }
78
+
79
+ #canvas {
80
+ position: absolute;
81
+ }
82
+ </style>
83
+ </head>
84
+ <body>
85
+ <canvas id="canvas"></canvas>
86
+ </body>
87
+ <script>
88
+ window.SEED = #{string(seed)};
89
+ #{@generativeScript}
90
+ </script>
91
+ </html>}
92
+ end
93
+ end
@@ -0,0 +1,54 @@
1
+ class OpenEditionERC721 < ERC721
2
+
3
+ storage contentURI: String,
4
+ maxPerAddress: UInt,
5
+ totalSupply: UInt,
6
+ description: String,
7
+ mintStart: Timestamp,
8
+ mintEnd: Timestamp
9
+
10
+ sig [String, String, String, UInt, String, Timestamp, Timestamp]
11
+ def constructor(
12
+ name:,
13
+ symbol:,
14
+ contentURI:,
15
+ maxPerAddress:,
16
+ description:,
17
+ mintStart:,
18
+ mintEnd: )
19
+ super(name: name, symbol: symbol)
20
+
21
+ @maxPerAddress = maxPerAddress
22
+ @description = description
23
+ @contentURI = contentURI
24
+ @mintStart = mintStart
25
+ @mintEnd = mintEnd
26
+ end
27
+
28
+ sig [UInt]
29
+ def mint( amount: )
30
+ assert(amount > 0, 'Amount must be positive')
31
+ assert(amount + @_balanceOf[msg.sender] <= @maxPerAddress, 'Exceeded mint limit')
32
+ assert(block.timestamp >= @mintStart, 'Minting has not started')
33
+ assert(block.timestamp < @mintEnd, 'Minting has ended')
34
+
35
+ amount.times do |id|
36
+ _mint(to: msg.sender, id: @totalSupply + id)
37
+ end
38
+
39
+ @totalSupply += amount
40
+ end
41
+
42
+ sig [UInt], :view, returns: String
43
+ def tokenURI( id: )
44
+ assert(_exists(id: id), 'ERC721Metadata: URI query for nonexistent token')
45
+
46
+ json_data = {
47
+ name: "#{@name} ##{string(id)}",
48
+ description: @description,
49
+ image: @contentURI,
50
+ }.to_json
51
+
52
+ "data:application/json,#{json_data}"
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+
2
+ class PublicMintERC20 < ERC20
3
+
4
+ storage maxSupply: UInt,
5
+ perMintLimit: UInt
6
+
7
+ sig [String, String, UInt, UInt, UInt]
8
+ def constructor(
9
+ name:,
10
+ symbol:,
11
+ maxSupply:,
12
+ perMintLimit:,
13
+ decimals:
14
+ )
15
+ super( name: name, symbol: symbol, decimals: decimals )
16
+ @maxSupply = maxSupply
17
+ @perMintLimit = perMintLimit
18
+ end
19
+
20
+ sig [UInt]
21
+ def mint( amount: )
22
+ assert amount > 0, 'Amount must be positive'
23
+ assert amount <= @perMintLimit, 'Exceeded mint limit'
24
+ assert @totalSupply + amount <= @maxSupply, 'Exceeded max supply'
25
+
26
+ _mint( to: msg.sender, amount: amount )
27
+ end
28
+
29
+ sig [Address, UInt]
30
+ def airdrop( to:, amount: )
31
+ assert amount > 0, 'Amount must be positive'
32
+ assert amount <= @perMintLimit, 'Exceeded mint limit'
33
+ assert @totalSupply + amount <= @maxSupply, 'Exceeded max supply'
34
+
35
+ _mint( to: to, amount: amount )
36
+ end
37
+ end # class PublicMintERC20
@@ -0,0 +1,23 @@
1
+ module Rubysol
2
+ module Module
3
+ module Contracts
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+ VERSION = [MAJOR,MINOR,PATCH].join('.')
8
+
9
+ def self.version
10
+ VERSION
11
+ end
12
+
13
+ def self.banner
14
+ "rubysol-contracts/#{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 Contracts
22
+ end # module Module
23
+ end # module Rubysol
@@ -0,0 +1,18 @@
1
+ require 'solidity/typed'
2
+ require 'rubysol'
3
+
4
+ ## our own code / contracts
5
+ require_relative 'contracts/version'
6
+ require_relative 'contracts/erc20.rb'
7
+ require_relative 'contracts/erc20_liquidity_pool.rb'
8
+ require_relative 'contracts/erc721.rb'
9
+ require_relative 'contracts/ether_erc20_bridge.rb'
10
+ require_relative 'contracts/ethscription_erc20_bridge.rb'
11
+ require_relative 'contracts/generative_erc721.rb'
12
+ require_relative 'contracts/open_edition_erc721.rb'
13
+ require_relative 'contracts/public_mint_erc20.rb'
14
+
15
+
16
+
17
+
18
+ puts Rubysol::Module::Contracts.banner ## say hello
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubysol-contracts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.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: rubysol-contracts - standard contracts (incl. erc20, erc721, etc) for
62
+ ruby for layer 1 (l1) with "off-chain" indexer
63
+ email: gerald.bauer@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files:
67
+ - CHANGELOG.md
68
+ - Manifest.txt
69
+ - README.md
70
+ files:
71
+ - CHANGELOG.md
72
+ - Manifest.txt
73
+ - README.md
74
+ - Rakefile
75
+ - lib/rubysol/contracts.rb
76
+ - lib/rubysol/contracts/erc20.rb
77
+ - lib/rubysol/contracts/erc20_liquidity_pool.rb
78
+ - lib/rubysol/contracts/erc721.rb
79
+ - lib/rubysol/contracts/ether_erc20_bridge.rb
80
+ - lib/rubysol/contracts/ethscription_erc20_bridge.rb
81
+ - lib/rubysol/contracts/generative_erc721.rb
82
+ - lib/rubysol/contracts/open_edition_erc721.rb
83
+ - lib/rubysol/contracts/public_mint_erc20.rb
84
+ - lib/rubysol/contracts/version.rb
85
+ homepage: https://github.com/s6ruby/rubidity
86
+ licenses:
87
+ - Public Domain
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options:
91
+ - "--main"
92
+ - README.md
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '2.3'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.4.10
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: rubysol-contracts - standard contracts (incl. erc20, erc721, etc) for ruby
110
+ for layer 1 (l1) with "off-chain" indexer
111
+ test_files: []