klay 0.0.1

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.
data/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # Klay for Ruby
2
+
3
+ A simple library to build and sign Klayereum transactions. Allows separation of key and node management. Sign transactions and handle keys anywhere you can run ruby, broadcast transactions through any node. Sign messages and recover signatures for authentication.
4
+
5
+ What you get:
6
+ - [x] Secp256k1 Key-Pairs and Encrypted Klayereum Key-Stores (JSON)
7
+ - [x] EIP-55 Checksummed Klayereum Addresses
8
+ - [x] EIP-155 Replay protection with Chain IDs (with presets)
9
+ - [x] EIP-191 Klayereum Signed Messages (with prefix and type)
10
+ - [x] EIP-712 Klayereum Signed Type Data
11
+ - [x] EIP-1559 Klayereum Type-2 Transactions (with priority fee and max gas fee)
12
+ - [x] EIP-2028 Call-data intrinsic gas cost estimates (plus access lists)
13
+ - [x] EIP-2718 Klayereum Transaction Envelopes (and types)
14
+ - [x] EIP-2930 Klayereum Type-1 Transactions (with access lists)
15
+ - [x] ABI-Encoder and Decoder (including type parser)
16
+ - [x] RLP-Encoder and Decoder (including sedes)
17
+ - [x] RPC-Client (IPC/HTTP) for Execution-Layer APIs
18
+
19
+ Soon (TM):
20
+ - [ ] Smart Contracts and Solidity Support
21
+ - [ ] EIP-1271 Smart-Contract Authentification
22
+ - [ ] HD-Wallets (BIP-32) and Mnemonics (BIP-39)
23
+
24
+ Contents:
25
+ - [1. Installation](#1-installation)
26
+ - [2. Usage](#2-usage)
27
+ - [2.1. Klayereum Keys and Addresses (EIP-55)](#21-ethereum-keys-and-addresses-eip-55)
28
+ - [2.2. Klayereum Signatures (EIP-191, EIP-712)](#22-ethereum-signatures-eip-191-eip-712)
29
+ - [2.3. Klayereum Chains (EIP-155)](#23-ethereum-chains-eip-155)
30
+ - [2.4. Klayereum Transactions (EIP-1559, EIP-2718, EIP-2930)](#24-ethereum-transactions-eip-1559-eip-2718-eip-2930)
31
+ - [2.5. Klayereum ABI Encoder and Decoder](#25-ethereum-abi-encoder-and-decoder)
32
+ - [2.6. Klayereum RLP Encoder and Decoder](#26-ethereum-rlp-encoder-and-decoder)
33
+ - [2.7. Klayereum RPC-Client](#27-ethereum-rpc-client)
34
+ - [3. Documentation](#3-documentation)
35
+ - [4. Testing](#4-testing)
36
+ - [5. Contributing](#5-contributing)
37
+ - [6. License and Credits](#6-license-and-credits)
38
+
39
+ ## 1. Installation
40
+
41
+ Add this line to your application's Gemfile:
42
+
43
+ ```ruby
44
+ gem "eth"
45
+ ```
46
+
47
+ Or install it yourself as:
48
+
49
+ ```shell
50
+ gem install eth
51
+ ```
52
+
53
+ ## 2. Usage
54
+
55
+ ### 2.1. Klayereum Keys and Addresses (EIP-55)
56
+
57
+ Generate a random Secp256k1 key-pair.
58
+
59
+ ```ruby
60
+ key = Klay::Key.new
61
+ # => #<Klay::Key:0x00005574a6ba80b8 @private_key=#<Secp256k1::PrivateKey:0x00005574a6b9a0a8 @data=")&\x86P\xB5\x16\xD9]\xFA;\x1F\xF6\xD9\xCF\xE3Vj/\xE2\x81\xC0\x9D\xE9\x05o!q\x82G\x9A\x10Q">, @public_key=#<Secp256k1::PublicKey:0x00005574a6b9bf98>>
62
+ ```
63
+
64
+ Create an password-encrypted Klayereum key-store.
65
+
66
+ ```ruby
67
+ my_key = Klay::Key.new priv: "30137644b564785d01420f8043f043d74dcca64008e57c59f8ce713a0005a54b"
68
+ key_store = Klay::Key::Encrypter.perform my_key, "secret-password-1337"
69
+ # => "{\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"7e5c0fe1e27f4ea61b0f4427dd63555f\"},\"ciphertext\":\"6353653bba494cdae6bcd510febc980cdc6f7b23cfbdf950d7a909a69625c8fd\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":262144,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"cce96286f3c32267fc91f756365307fe6a4c83b6b2a73c69535f721fa407736c\"},\"mac\":\"3361ffd2b158a1d7bca5a5fd86a251ba3e9d80b602c867a2e0f47023a0e17a57\"},\"id\":\"642ee9fc-72e4-4d0a-902f-247c0b59bfda\",\"version\":3}"
70
+ restored_key = Klay::Key::Decrypter.perform key_store, "secret-password-1337"
71
+ # => "30137644b564785d01420f8043f043d74dcca64008e57c59f8ce713a0005a54b"
72
+ ```
73
+
74
+ Manage Klayereum address objects adhering to EIP-55 checksum format.
75
+
76
+ ```ruby
77
+ address = Klay::Address.new "0xd496b23d61f88a8c7758fca7560dcfac7b3b01f9"
78
+ # => #<Klay::Address:0x00005574a6bd4fc8 @address="0xd496b23d61f88a8c7758fca7560dcfac7b3b01f9">
79
+ address.valid?
80
+ # => true
81
+ address.checksummed # EIP 55
82
+ # => "0xD496b23D61F88A8C7758fca7560dCFac7b3b01F9"
83
+ ```
84
+
85
+ See `/spec` or [Documentation](https://q9f.github.io/eth.rb/) for more details about key-pairs, encrypting/decrypting key-stores with a secret, and checksummed addresses.
86
+
87
+ ### 2.2. Klayereum Signatures (EIP-191, EIP-712)
88
+
89
+ Manage keypairs to sign messages in EIP-191 (`personal_sign`) format or typed data in EIP-712 (`sign_typed_data`) format.
90
+
91
+ ```ruby
92
+ key = Klay::Key.new priv: "268be6f4a68c40f6862b7ac9aed8f701dc25a95ddb9a44d8b1f520b75f440a9a"
93
+ # => #<Klay::Key:0x00005574a699adc0 @private_key=#<Secp256k1::PrivateKey:0x00005574a6998200 @data="&\x8B\xE6\xF4\xA6\x8C@\xF6\x86+z\xC9\xAE\xD8\xF7\x01\xDC%\xA9]\xDB\x9AD\xD8\xB1\xF5 \xB7_D\n\x9A">, @public_key=#<Secp256k1::PublicKey:0x00005574a6998160>>
94
+ key.public_hex
95
+ # => "04b45200621c013a5fbab999ac33b0c836328a04afa0255ffbe6ea0f6fd97e187b02199886d942a9f50f7e279a2bc74c93b2afcbd7255489939f9b36a5eae5e281"
96
+ key.address.to_s
97
+ # => "0xD496b23D61F88A8C7758fca7560dCFac7b3b01F9"
98
+ key.personal_sign "Hello World!"
99
+ # => "ac6a59417d8688c8144f01a662384fa691636b48a071d4b7c13902bb87ca472b0bce1d7a758f39a5759ed5e937ce61f50dd1b83158371f8d0faeb9b7d81c194225"
100
+ ```
101
+
102
+ Recover and verify personal signatures respecting EIPs 155, 191, and 712.
103
+
104
+ ```ruby
105
+ address = Klay::Address.new "0xd496b23d61f88a8c7758fca7560dcfac7b3b01f9"
106
+ # => #<Klay::Address:0x00005574a6bd4fc8 @address="0xd496b23d61f88a8c7758fca7560dcfac7b3b01f9">
107
+ signature = "ac6a59417d8688c8144f01a662384fa691636b48a071d4b7c13902bb87ca472b0bce1d7a758f39a5759ed5e937ce61f50dd1b83158371f8d0faeb9b7d81c19422d"
108
+ # => "ac6a59417d8688c8144f01a662384fa691636b48a071d4b7c13902bb87ca472b0bce1d7a758f39a5759ed5e937ce61f50dd1b83158371f8d0faeb9b7d81c19422d"
109
+ recovered_key = Klay::Signature.personal_recover "Hello World!", signature, Klay::Chain::GOERLI
110
+ # => "04b45200621c013a5fbab999ac33b0c836328a04afa0255ffbe6ea0f6fd97e187b02199886d942a9f50f7e279a2bc74c93b2afcbd7255489939f9b36a5eae5e281"
111
+ Klay::Util.public_key_to_address(recovered_key).to_s
112
+ # => "0xD496b23D61F88A8C7758fca7560dCFac7b3b01F9"
113
+ Klay::Signature.verify "Hello World!", signature, address, Klay::Chain::GOERLI
114
+ # => true
115
+ ```
116
+
117
+ See `/spec` or [Documentation](https://q9f.github.io/eth.rb/) for signing typed data as per EIP-712.
118
+
119
+ ### 2.3. Klayereum Chains (EIP-155)
120
+
121
+ Manage Klayereum chain IDs for EIP-155 replay protection.
122
+
123
+ ```ruby
124
+ chain_id = Klay::Chain::OPTIMISM
125
+ # => 10
126
+ v = Klay::Chain.to_v 0, Klay::Chain::OPTIMISM
127
+ # => 55
128
+ recovery_id = Klay::Chain.to_recovery_id v, Klay::Chain::OPTIMISM
129
+ # => 0
130
+ chain_id = Klay::Chain.to_chain_id v
131
+ # => 10
132
+ ```
133
+
134
+ ### 2.4. Klayereum Transactions (EIP-1559, EIP-2718, EIP-2930)
135
+
136
+ Create an EIP-1559-conform transaction:
137
+
138
+ ```ruby
139
+ payload = {
140
+ chain_id: Klay::Chain::GOERLI,
141
+ nonce: 5,
142
+ priority_fee: 3 * Klay::Unit::GWEI,
143
+ max_gas_fee: 69 * Klay::Unit::GWEI,
144
+ gas_limit: 230_420,
145
+ to: "0xCaA29806044A08E533963b2e573C1230A2cd9a2d",
146
+ value: 0.069423 * Klay::Unit::ETHER,
147
+ }
148
+ # => {:chain_id=>5, :nonce=>5, :priority_fee=>0.3e10, :max_gas_fee=>0.69e11, :gas_limit=>230420, :to=>"0xCaA29806044A08E533963b2e573C1230A2cd9a2d", :value=>0.69423e17}
149
+ tx = Klay::Tx.new payload
150
+ # => #<Klay::Tx::Eip1559:0x0000557e35fc5a68 @access_list=[], @amount=69423000000000000, @chain_id=5, @destination="CaA29806044A08E533963b2e573C1230A2cd9a2d", @gas_limit=230420, @max_fee_per_gas=69000000000, @max_priority_fee_per_gas=3000000000, @payload="", @sender="", @signature_r=0, @signature_s=0, @signature_y_parity=nil, @signer_nonce=5, @type=2>
151
+ my_key = Klay::Key.new priv: "30137644b564785d01420f8043f043d74dcca64008e57c59f8ce713a0005a54b"
152
+ # => #<Klay::Key:0x0000557e36243178 @private_key=#<Secp256k1::PrivateKey:0x0000557e36242d40 @data="0\x13vD\xB5dx]\x01B\x0F\x80C\xF0C\xD7M\xCC\xA6@\b\xE5|Y\xF8\xCEq:\x00\x05\xA5K">, @public_key=#<Secp256k1::PublicKey:0x0000557e36242cf0>>
153
+ tx.sign my_key
154
+ # => "cba302c0ebf8d0205a78ae97f560419b407e32e2426f416abc95a9bfc9dac09c"
155
+ tx.hex
156
+ # => "02f873050584b2d05e00851010b872008303841494caa29806044a08e533963b2e573c1230a2cd9a2d87f6a3d9c63df00080c080a03aa187d10b138d3e0155729adb961cd89e10f988ba2d19d6869770b9e5a23d10a04d40864600136ae214916043c7d63b849c98db757e95c86983a036982816e1af"
157
+ ```
158
+
159
+ This gem also supports access lists and ABI-encoded data payloads. See `/spec` or [Documentation](https://q9f.github.io/eth.rb/) for more details about the various supported transaction types (legacy, type-1, type-2), payload parameters, and how to estimate intrinsic gas costs.
160
+
161
+ ### 2.5. Klayereum ABI Encoder and Decoder
162
+
163
+ Encode and decode Klayereum application binary interface data (ABI).
164
+
165
+ ```ruby
166
+ Klay::Util.bin_to_hex Klay::Abi.encode(["string", "address"], ["Hello, Bob!", "0xd496b23d61f88a8c7758fca7560dcfac7b3b01f9"])
167
+ # => "0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000d496b23d61f88a8c7758fca7560dcfac7b3b01f9000000000000000000000000000000000000000000000000000000000000000b48656c6c6f2c20426f6221000000000000000000000000000000000000000000"
168
+ Klay::Abi.decode(["string", "address"], "0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000d496b23d61f88a8c7758fca7560dcfac7b3b01f9000000000000000000000000000000000000000000000000000000000000000b48656c6c6f2c20426f6221000000000000000000000000000000000000000000")
169
+ # => ["Hello, Bob!", "0xd496b23d61f88a8c7758fca7560dcfac7b3b01f9"]
170
+ ```
171
+
172
+ ### 2.6. Klayereum RLP Encoder and Decoder
173
+
174
+ Serialize and deserialize Klayereum recursive-length prefix data (RLP).
175
+
176
+ ```ruby
177
+ Klay::Util.bin_to_hex Klay::Rlp.encode ["Hello, Bob!", "0xd496b23d61f88a8c7758fca7560dcfac7b3b01f9"]
178
+ # => "f78b48656c6c6f2c20426f6221aa307864343936623233643631663838613863373735386663613735363064636661633762336230316639"
179
+ Klay::Rlp.decode "f78b48656c6c6f2c20426f6221aa307864343936623233643631663838613863373735386663613735363064636661633762336230316639"
180
+ # => ["Hello, Bob!", "0xd496b23d61f88a8c7758fca7560dcfac7b3b01f9"]
181
+ ```
182
+
183
+ Or ;-)
184
+
185
+ ```ruby
186
+ Klay::Rlp.decode "c7c0c1c0c3c0c1c0"
187
+ # => [[], [[]], [[], [[]]]]
188
+ ```
189
+
190
+ ### 2.7. Klayereum RPC-Client
191
+
192
+ Create an IPC- or HTTP-RPC-API client to seamlessly query the chain state, e.g., Infura over HTTPS with access token:
193
+
194
+ ```ruby
195
+ infura = Klay::Client.create "https://mainnet.infura.io/v3/#{access_token}"
196
+ # => #<Klay::Client::Http:0x000055d43f3ca460 @gas_limit=21000, @host="mainnet.infura.io", @id=0, @max_fee_per_gas=0.2e11, @max_priority_fee_per_gas=0, @port=443, @ssl=true, @uri=#<URI::HTTPS https://mainnet.infura.io/v3/31b...d93>>
197
+ deposit_contract = Klay::Address.new "0x00000000219ab540356cBB839Cbe05303d7705Fa"
198
+ # => #<Klay::Address:0x000055d43f381738 @address="0x00000000219ab540356cBB839Cbe05303d7705Fa">
199
+ infura.get_balance deposit_contract
200
+ # => 9087314000069000000000069
201
+ ```
202
+
203
+ Or set up a local development environment with `geth --dev`:
204
+
205
+ ```ruby
206
+ cli = Klay::Client.create "/tmp/geth.ipc"
207
+ # => #<Klay::Client::Ipc:0x000055d43f51c390 @gas_limit=21000, @id=0, @max_fee_per_gas=0.2e11, @max_priority_fee_per_gas=0, @path="/tmp/geth.ipc">
208
+ cli.eth_coinbase
209
+ # => {"jsonrpc"=>"2.0", "id"=>1, "result"=>"0x6868074fb21c48dfad0c448fbabd99383a6598e4"}
210
+ tx = cli.transfer_and_wait(Klay::Key.new.address, 1337 * Klay::Unit::ETHER)
211
+ # => "0x141c6dff40df34fe4fce5a65588d2161dab3e0e977fb8049ff7d79bc901034f7"
212
+ cli.eth_get_transaction_by_hash tx
213
+ # => {"jsonrpc"=>"2.0", "id"=>8, "result"=> {"blockHash"=>"0x47e742038c75851348dbda87b15fde044d54c442c371f43bea881a44d5589de3", "blockNumber"=>"0x1", "from"=>"0x6868074fb21c48dfad0c448fbabd99383a6598e4", "gas"=>"0x5208", "gasPrice"=>"0x342770c1", "maxFeePerGas"=>"0x77359401", "maxPriorityFeePerGas"=>"0x1", "hash"=>"0x141c6dff40df34fe4fce5a65588d2161dab3e0e977fb8049ff7d79bc901034f7", "input"=>"0x", "nonce"=>"0x0", "to"=>"0x311c61e5dc6123ad016bb7fd687d283c327bcd5f", "transactionIndex"=>"0x0", "value"=>"0x487a9a304539440000", "type"=>"0x2", "accessList"=>[], "chainId"=>"0x539", "v"=>"0x0", "r"=>"0xb42477d69eae65a3a3d91d9cb173e4a45a403fb0a15fa729dbfdc9d13211d7b5", "s"=>"0x4a2f98fc2b61c2d7c907520bc8c6ebe42ea6fe1cb6824f95e4b30e9464395100"}}
214
+ cli.get_balance "0x311c61e5dc6123ad016bb7fd687d283c327bcd5f"
215
+ # => 1337000000000000000000
216
+ cli.get_nonce cli.eth_coinbase["result"]
217
+ # => 1
218
+ ```
219
+
220
+ ## 3. Documentation
221
+
222
+ For any specific version, docs can be generated by `yard`:
223
+
224
+ ```shell
225
+ gem install bundler rdoc yard
226
+ git checkout v0.0.1
227
+ yard doc
228
+ ```
229
+
230
+ The goal is to have 100% API documentation available.
231
+
232
+ ## 4. Testing
233
+
234
+ To run tests, simply use `rspec`. Note, that the Klayereum tests fixtures are required.
235
+
236
+ ```shell
237
+ git submodule update --init --recursive
238
+ bundle install
239
+ rspec
240
+ ```
241
+
242
+ The goal is to have 100% specification coverage for all code inside this gem.
243
+
244
+ ## 5. Contributing
245
+
246
+ Pull requests are welcome! To contribute, please consider the following:
247
+ * Code should be fully documented. Run `yard doc` and make sure it does not yield any warnings or undocumented sets.
248
+ * Code should be fully covered by tests. Run `rspec` to make sure all tests pass. The CI has an integration that will assis you to identify uncovered lines of code and get coverage up to 100%.
249
+ * Code should be formatted properly. Try to eliminate the most common issues such as trailing white-spaces or duplicate new-lines. Usage of the `rufo` gem is recommended.
250
+ * Submit pull requests, questions, or issues to Github: https://github.com/noMacGuffins/klay.rb
251
+
252
+ ## 6. License and Credits
253
+ The `eth` gem is licensed under the conditions of [Apache 2.0](./LICENSE.txt). Please see [AUTHORS](./AUTHORS.txt) for contributors and copyright notices.
254
+
255
+ This gem is a complete rewrite of the old `eth` gem by Steve Ellis.
256
+ * https://github.com/se3000/ruby-eth/ (MIT)
257
+
258
+ It also contains a revised version the ABI gem by Jan Xie and Zhang Yaning.
259
+ * https://github.com/cryptape/ruby-ethereum-abi (MIT)
260
+
261
+ It also contains a revised version the RLP gem by Jan Xie and Zhang Yaning.
262
+ * https://github.com/cryptape/ruby-rlp (MIT)
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # use the local version of the code instead of a globally installed gem
4
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
5
+
6
+ require "klay"
7
+ include Klay
8
+
9
+ require "pry"
10
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ bundle install
4
+ git submodule update --init --recursive
5
+ rufo .
6
+ yard doc
7
+ rspec
8
+
9
+ echo "Tests fail? Run \`geth --dev --http --ipcpath /tmp/geth.ipc\` in background and try again."
data/klay.gemspec ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ # coding: utf-8
3
+
4
+ lib = File.expand_path("lib", __dir__).freeze
5
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
6
+
7
+ require "klay/version"
8
+
9
+ Gem::Specification.new do |spec|
10
+ spec.name = "klay"
11
+ spec.version = Klay::VERSION
12
+ spec.authors = ["Sehan Park"]
13
+ spec.email = ["ianparkfirst@gmail.com"]
14
+
15
+ spec.summary = %q{Ruby Klaytn library.}
16
+ spec.description = %q{Library to handle Klaytn accounts, messages, and transactions.}
17
+ spec.homepage = "https://github.com/noMacGuffins/klay"
18
+ spec.license = "Apache-2.0"
19
+
20
+ spec.metadata = {
21
+ "bug_tracker_uri" => "https://github.com/noMacGuffins/klay/issues",
22
+ "changelog_uri" => "https://github.com/noMacGuffins/klay/blob/main/CHANGELOG.md",
23
+ "github_repo" => "https://github.com/noMacGuffins/klay",
24
+ "source_code_uri" => "https://github.com/noMacGuffins/klay",
25
+ }.freeze
26
+
27
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+ spec.test_files = spec.files.grep %r{^(test|spec|features)/}
32
+
33
+ spec.platform = Gem::Platform::RUBY
34
+ spec.required_ruby_version = ">= 2.6", "< 4.0"
35
+
36
+ # keccak for hashing everything in ethereum
37
+ spec.add_dependency "keccak", "~> 1.3"
38
+
39
+ # konstructor gem for overloading constructors
40
+ spec.add_dependency "konstructor", "~> 1.0"
41
+
42
+ # rbsecp256k1 for key-pairs and signatures
43
+ spec.add_dependency "rbsecp256k1", "~> 5.1"
44
+
45
+ # openssl for encrypted key derivation
46
+ spec.add_dependency "openssl", "~> 2.2"
47
+
48
+ # scrypt for encrypted key derivation
49
+ spec.add_dependency "scrypt", "~> 3.0"
50
+ end
@@ -0,0 +1,178 @@
1
+ # Copyright (c) 2016-2022 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -*- encoding : ascii-8bit -*-
16
+
17
+ # Provides the {Eth} module.
18
+ module Klay
19
+
20
+ # Provides a Ruby implementation of the Ethereum Applicatoin Binary Interface (ABI).
21
+ module Abi
22
+
23
+ # Provides a class to handle and parse common ABI types.
24
+ class Type
25
+
26
+ # Provides a specific parser error if type cannot be determined.
27
+ class ParseError < StandardError; end
28
+
29
+ # The base attribute, e.g., `string` or `bytes`.
30
+ attr :base_type
31
+
32
+ # The sub-type attribute, e.g., `256` as size of an uint256.
33
+ attr :sub_type
34
+
35
+ # The dimension attribute, e.g., `[10]` for an array of size 10.
36
+ attr :dimensions
37
+
38
+ # Create a new Type object for base types, sub types, and dimensions.
39
+ # Should not be used; use {Type.parse} instead.
40
+ #
41
+ # @param base_type [String] the base-type attribute.
42
+ # @param sub_type [String] the sub-type attribute.
43
+ # @param dimensions [Array] the dimension attribute.
44
+ # @return [Eth::Abi::Type] an ABI type object.
45
+ def initialize(base_type, sub_type, dimensions)
46
+ sub_type = sub_type.to_s
47
+ @base_type = base_type
48
+ @sub_type = sub_type
49
+ @dimensions = dimensions
50
+ end
51
+
52
+ # Converts the self.parse method into a constructor.
53
+ konstructor :parse
54
+
55
+ # Attempts to parse a string containing a common Solidity type.
56
+ # Creates a new Type upon success (using konstructor).
57
+ #
58
+ # @param type [String] a common Solidity type.
59
+ # @return [Eth::Abi::Type] a parsed Type object.
60
+ # @raise [ParseError] if it fails to parse the type.
61
+ def parse(type)
62
+ _, base_type, sub_type, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a
63
+
64
+ # type dimension can only be numeric
65
+ dims = dimension.scan(/\[[0-9]*\]/)
66
+ raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
67
+
68
+ # enforce base types
69
+ validate_base_type base_type, sub_type
70
+
71
+ # return a new Type (using konstructor)
72
+ sub_type = sub_type.to_s
73
+ @base_type = base_type
74
+ @sub_type = sub_type
75
+ @dimensions = dims.map { |x| x[1...-1].to_i }
76
+ end
77
+
78
+ # Creates a new uint256 type used for size.
79
+ #
80
+ # @return [Eth::Abi::Type] a uint256 size type.
81
+ def self.size_type
82
+ @size_type ||= new("uint", 256, [])
83
+ end
84
+
85
+ # Compares two types for their attributes.
86
+ #
87
+ # @param another_type [Eth::Abi::Type] another type to be compared.
88
+ # @return [Boolean] true if all attributes match.
89
+ def ==(another_type)
90
+ base_type == another_type.base_type and
91
+ sub_type == another_type.sub_type and
92
+ dimensions == another_type.dimensions
93
+ end
94
+
95
+ # Computes the size of a type if possible.
96
+ #
97
+ # @return [Integer] the size of the type; or nil if not available.
98
+ def size
99
+ s = nil
100
+ if dimensions.empty?
101
+ unless ["string", "bytes"].include?(base_type) and sub_type.empty?
102
+ s = 32
103
+ end
104
+ else
105
+ unless dimensions.last == 0
106
+ unless nested_sub.is_dynamic?
107
+ s = dimensions.last * nested_sub.size
108
+ end
109
+ end
110
+ end
111
+ @size ||= s
112
+ end
113
+
114
+ # Helpes to determine whether array is of dynamic size.
115
+ #
116
+ # @return [Boolean] true if array is of dynamic size.
117
+ def is_dynamic?
118
+ size.nil?
119
+ end
120
+
121
+ # Types can have nested sub-types in arrays.
122
+ #
123
+ # @return [Eth::Abi::Type] nested sub-type.
124
+ def nested_sub
125
+ @nested_sub ||= self.class.new(base_type, sub_type, dimensions[0...-1])
126
+ end
127
+
128
+ private
129
+
130
+ # Validates all known base types and raises if an issue occurs.
131
+ def validate_base_type(base_type, sub_type)
132
+ case base_type
133
+ when "string"
134
+
135
+ # string can not have any suffix
136
+ raise ParseError, "String type must have no suffix or numerical suffix" unless sub_type.empty?
137
+ when "bytes"
138
+
139
+ # bytes can be no longer than 32 bytes
140
+ raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub_type.empty? || sub_type.to_i <= 32
141
+ when "uint", "int"
142
+
143
+ # integers must have a numerical suffix
144
+ raise ParseError, "Integer type must have numerical suffix" unless sub_type =~ /\A[0-9]+\z/
145
+
146
+ # integer size must be valid
147
+ size = sub_type.to_i
148
+ raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
149
+ raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
150
+ when "ureal", "real", "fixed", "ufixed"
151
+
152
+ # floats must have valid dimensional suffix
153
+ raise ParseError, "Real type must have suffix of form <high>x<low>, e.g. 128x128" unless sub_type =~ /\A[0-9]+x[0-9]+\z/
154
+ high, low = sub_type.split("x").map(&:to_i)
155
+ total = high + low
156
+ raise ParseError, "Real size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
157
+ raise ParseError, "Real high/low sizes must be multiples of 8" unless high % 8 == 0 && low % 8 == 0
158
+ when "hash"
159
+
160
+ # hashs must have numerical suffix
161
+ raise ParseError, "Hash type must have numerical suffix" unless sub_type =~ /\A[0-9]+\z/
162
+ when "address"
163
+
164
+ # addresses cannot have any suffix
165
+ raise ParseError, "Address cannot have suffix" unless sub_type.empty?
166
+ when "bool"
167
+
168
+ # booleans cannot have any suffix
169
+ raise ParseError, "Bool cannot have suffix" unless sub_type.empty?
170
+ else
171
+
172
+ # we cannot parse arbitrary types such as 'decimal' or 'hex'
173
+ raise ParseError, "Unknown base type"
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end