ic_agent 0.1.4 → 0.2.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +16 -10
- data/README.md +2 -0
- data/ic_agent.gemspec +2 -0
- data/lib/ic_agent/agent.rb +154 -4
- data/lib/ic_agent/ast/assembler.rb +17 -0
- data/lib/ic_agent/ast/nodes/named_nodes.rb +99 -1
- data/lib/ic_agent/ast/nodes/statement_nodes.rb +63 -1
- data/lib/ic_agent/ast/nodes/string_literal.rb +5 -0
- data/lib/ic_agent/ast/parser.rb +35 -1
- data/lib/ic_agent/ast/statement_parser.rb +15 -0
- data/lib/ic_agent/ast/writer.rb +15 -0
- data/lib/ic_agent/candid.rb +26 -5
- data/lib/ic_agent/canister.rb +7 -0
- data/lib/ic_agent/certificate.rb +98 -0
- data/lib/ic_agent/client.rb +32 -0
- data/lib/ic_agent/common/cycles_wallet.rb +5 -0
- data/lib/ic_agent/common/governance.rb +4 -0
- data/lib/ic_agent/common/ledger.rb +4 -0
- data/lib/ic_agent/common/management.rb +4 -0
- data/lib/ic_agent/identity.rb +52 -0
- data/lib/ic_agent/principal.rb +104 -6
- data/lib/ic_agent/system_state.rb +37 -0
- data/lib/ic_agent/utils.rb +30 -2
- data/lib/ic_agent/version.rb +1 -1
- data/lib/ic_agent.rb +7 -2
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1bb3c395959ef0b33094ab3e34a6087b061a76287ce34b312286703c568fade
|
4
|
+
data.tar.gz: f71667c7d06d470c89afd7d9e8734bfded8abc2f7d85a89a77d84fa3ce7e210c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 437bab4be804cc57aa1098aa91275d88623e79c98f9190129956eb2ceb0447dd5dbd305ec7425b3a3fd7fc945035a79329b6a73052c876bc31118a94387cb61a
|
7
|
+
data.tar.gz: 05adc3829656980d0a833ac495cbfbf67e84fee5474dda75e39d6f94fb538ee22120de034ba7b81c36680ef4444a586409ecb2c6f0f43d53f9cd7aa28b51e394
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ic_agent (0.1
|
4
|
+
ic_agent (0.2.1)
|
5
5
|
base32 (~> 0.3.4)
|
6
6
|
bitcoin-ruby (~> 0.0.20)
|
7
|
+
bls12-381 (~> 0.3.0)
|
7
8
|
cbor (~> 0.5.9.6)
|
8
9
|
ctf-party (~> 2.3)
|
9
10
|
ecdsa (~> 1.2)
|
@@ -23,6 +24,8 @@ GEM
|
|
23
24
|
eventmachine
|
24
25
|
ffi
|
25
26
|
scrypt
|
27
|
+
bls12-381 (0.3.0)
|
28
|
+
h2c (~> 0.2.0)
|
26
29
|
byebug (11.1.3)
|
27
30
|
cbor (0.5.9.6)
|
28
31
|
coderay (1.1.3)
|
@@ -34,7 +37,7 @@ GEM
|
|
34
37
|
ecdsa (1.2.0)
|
35
38
|
ed25519 (1.3.0)
|
36
39
|
eventmachine (1.2.7)
|
37
|
-
faraday (2.7.
|
40
|
+
faraday (2.7.10)
|
38
41
|
faraday-net_http (>= 2.0, < 3.1)
|
39
42
|
ruby2_keywords (>= 0.0.4)
|
40
43
|
faraday-net_http (3.0.2)
|
@@ -42,13 +45,15 @@ GEM
|
|
42
45
|
ffi-compiler (1.0.1)
|
43
46
|
ffi (>= 1.0.0)
|
44
47
|
rake
|
45
|
-
|
48
|
+
h2c (0.2.0)
|
49
|
+
ecdsa (~> 1.2.0)
|
50
|
+
i18n (1.14.1)
|
46
51
|
concurrent-ruby (~> 1.0)
|
47
52
|
json (2.6.3)
|
48
53
|
leb128 (1.0.0)
|
49
54
|
method_source (1.0.0)
|
50
|
-
mini_portile2 (2.8.
|
51
|
-
pkg-config (1.5.
|
55
|
+
mini_portile2 (2.8.4)
|
56
|
+
pkg-config (1.5.2)
|
52
57
|
polyglot (0.3.5)
|
53
58
|
pry (0.14.2)
|
54
59
|
coderay (~> 1.1)
|
@@ -62,19 +67,19 @@ GEM
|
|
62
67
|
rspec-core (~> 3.12.0)
|
63
68
|
rspec-expectations (~> 3.12.0)
|
64
69
|
rspec-mocks (~> 3.12.0)
|
65
|
-
rspec-core (3.12.
|
70
|
+
rspec-core (3.12.2)
|
66
71
|
rspec-support (~> 3.12.0)
|
67
|
-
rspec-expectations (3.12.
|
72
|
+
rspec-expectations (3.12.3)
|
68
73
|
diff-lcs (>= 1.2.0, < 2.0)
|
69
74
|
rspec-support (~> 3.12.0)
|
70
|
-
rspec-mocks (3.12.
|
75
|
+
rspec-mocks (3.12.6)
|
71
76
|
diff-lcs (>= 1.2.0, < 2.0)
|
72
77
|
rspec-support (~> 3.12.0)
|
73
|
-
rspec-support (3.12.
|
78
|
+
rspec-support (3.12.1)
|
74
79
|
ruby-enum (0.9.0)
|
75
80
|
i18n
|
76
81
|
ruby2_keywords (0.0.5)
|
77
|
-
rubytree (2.0.
|
82
|
+
rubytree (2.0.2)
|
78
83
|
json (~> 2.0, > 2.3.1)
|
79
84
|
rubyzip (2.3.2)
|
80
85
|
scrypt (3.0.7)
|
@@ -88,6 +93,7 @@ PLATFORMS
|
|
88
93
|
DEPENDENCIES
|
89
94
|
base32 (~> 0.3.4)
|
90
95
|
bitcoin-ruby (~> 0.0.20)
|
96
|
+
bls12-381 (~> 0.3.0)
|
91
97
|
byebug (~> 11.1, >= 11.1.3)
|
92
98
|
cbor (~> 0.5.9.6)
|
93
99
|
ctf-party (~> 2.3)
|
data/README.md
CHANGED
@@ -4,6 +4,8 @@
|
|
4
4
|
|
5
5
|
`ic_agent` provides basic modules to interact with canisters on the DFINITY Internet Computer.
|
6
6
|
|
7
|
+
[](https://badge.fury.io/rb/ic_agent)[](LICENSE)
|
8
|
+
|
7
9
|
|
8
10
|
## Installation
|
9
11
|
|
data/ic_agent.gemspec
CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.metadata['homepage_uri'] = spec.homepage
|
18
18
|
spec.metadata['source_code_uri'] = spec.homepage
|
19
19
|
spec.metadata['changelog_uri'] = spec.homepage
|
20
|
+
spec.metadata['documentation_uri'] = 'https://tuminfei.github.io/ic_agent.github.com/'
|
20
21
|
|
21
22
|
# Specify which files should be added to the gem when it is released.
|
22
23
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -33,6 +34,7 @@ Gem::Specification.new do |spec|
|
|
33
34
|
# spec.add_dependency "example-gem", "~> 1.0"
|
34
35
|
spec.add_dependency 'base32', '~> 0.3.4'
|
35
36
|
spec.add_dependency 'bitcoin-ruby', '~> 0.0.20'
|
37
|
+
spec.add_dependency 'bls12-381', '~> 0.3.0'
|
36
38
|
spec.add_dependency 'cbor', '~> 0.5.9.6'
|
37
39
|
spec.add_dependency 'ctf-party', '~> 2.3'
|
38
40
|
spec.add_dependency 'ecdsa', '~> 1.2'
|
data/lib/ic_agent/agent.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
require 'cbor'
|
2
|
+
require 'bls'
|
2
3
|
require 'ctf_party'
|
4
|
+
require 'bitcoin'
|
3
5
|
|
4
6
|
module IcAgent
|
5
7
|
class Request
|
8
|
+
# Signs a request with an identity's signature and encodes it using CBOR.
|
9
|
+
#
|
10
|
+
# @param req [Hash] The request to be signed.
|
11
|
+
# @param iden [Identity] The identity used for signing.
|
12
|
+
# @return [Array] The request ID and the encoded signed request.
|
6
13
|
def self.sign_request(req, iden)
|
7
14
|
req_id = IcAgent::Utils.to_request_id(req)
|
8
15
|
msg = IcAgent::IC_REQUEST_DOMAIN_SEPARATOR + req_id
|
@@ -26,6 +33,13 @@ module IcAgent
|
|
26
33
|
class Agent
|
27
34
|
attr_accessor :identity, :client, :ingress_expiry, :root_key, :nonce_factory
|
28
35
|
|
36
|
+
# Initializes a new IC agent.
|
37
|
+
#
|
38
|
+
# @param identity [Identity] The identity associated with the agent.
|
39
|
+
# @param client [Client] The client used for communication with the IC network.
|
40
|
+
# @param nonce_factory [NonceFactory] The factory for generating nonces.
|
41
|
+
# @param ingress_expiry [Integer] The expiration time for ingress requests.
|
42
|
+
# @param root_key [String] The IC root key used for verification.
|
29
43
|
def initialize(identity, client, nonce_factory = nil, ingress_expiry = 300, root_key = IcAgent::IC_ROOT_KEY)
|
30
44
|
@identity = identity
|
31
45
|
@client = client
|
@@ -34,14 +48,25 @@ module IcAgent
|
|
34
48
|
@nonce_factory = nonce_factory
|
35
49
|
end
|
36
50
|
|
51
|
+
# Retrieves the principal associated with the agent's identity.
|
52
|
+
#
|
53
|
+
# @return [Principal] The principal associated with the agent.
|
37
54
|
def get_principal
|
38
55
|
@identity.sender
|
39
56
|
end
|
40
57
|
|
58
|
+
# Calculates the expiration date for ingress requests.
|
59
|
+
#
|
60
|
+
# @return [Integer] The expiration date in nanoseconds.
|
41
61
|
def get_expiry_date
|
42
62
|
((Time.now.to_i + @ingress_expiry) * 10**9).to_i
|
43
63
|
end
|
44
64
|
|
65
|
+
# Sends a query request to a canister and decodes the response using CBOR.
|
66
|
+
#
|
67
|
+
# @param canister_id [String] The ID of the target canister.
|
68
|
+
# @param data [Hash] The data to be sent in the query request.
|
69
|
+
# @return [Object] The decoded response from the canister.
|
45
70
|
def query_endpoint(canister_id, data)
|
46
71
|
ret = @client.query(canister_id, data)
|
47
72
|
decode_ret = nil
|
@@ -54,16 +79,34 @@ module IcAgent
|
|
54
79
|
decode_ret
|
55
80
|
end
|
56
81
|
|
82
|
+
# Calls a method on a canister and returns the request ID.
|
83
|
+
#
|
84
|
+
# @param canister_id [String] The ID of the target canister.
|
85
|
+
# @param request_id [String] The ID of the request.
|
86
|
+
# @param data [Hash] The data to be sent in the call request.
|
87
|
+
# @return [String] The request ID.
|
57
88
|
def call_endpoint(canister_id, request_id, data)
|
58
89
|
@client.call(canister_id, request_id, data)
|
59
90
|
request_id
|
60
91
|
end
|
61
92
|
|
93
|
+
# Reads the state of a canister.
|
94
|
+
#
|
95
|
+
# @param canister_id [String] The ID of the target canister.
|
96
|
+
# @param data [Hash] The data to be sent in the read state request.
|
97
|
+
# @return [Object] The response from the canister.
|
62
98
|
def read_state_endpoint(canister_id, data)
|
63
|
-
|
64
|
-
result
|
99
|
+
@client.read_state(canister_id, data)
|
65
100
|
end
|
66
101
|
|
102
|
+
# Sends a raw query request to a canister and handles the response.
|
103
|
+
#
|
104
|
+
# @param canister_id [String] The ID of the target canister.
|
105
|
+
# @param method_name [String] The name of the method to be called.
|
106
|
+
# @param arg [String] The argument to be passed to the method.
|
107
|
+
# @param return_type [Object] The expected type of the return value.
|
108
|
+
# @param effective_canister_id [String] The effective canister ID (optional).
|
109
|
+
# @return [Object] The decoded response from the canister.
|
67
110
|
def query_raw(canister_id, method_name, arg, return_type = nil, effective_canister_id = nil)
|
68
111
|
req_canister_id = canister_id.is_a?(String) ? Principal.from_str(canister_id).bytes : canister_id.bytes
|
69
112
|
req = {
|
@@ -92,6 +135,15 @@ module IcAgent
|
|
92
135
|
end
|
93
136
|
end
|
94
137
|
|
138
|
+
# Sends a raw update request to a canister and handles the response.
|
139
|
+
#
|
140
|
+
# @param canister_id [String] The ID of the target canister.
|
141
|
+
# @param method_name [String] The name of the method to be called.
|
142
|
+
# @param arg [String] The argument to be passed to the method.
|
143
|
+
# @param return_type [Object] The expected type of the return value.
|
144
|
+
# @param effective_canister_id [String] The effective canister ID (optional).
|
145
|
+
# @param kwargs [Hash] Additional keyword arguments.
|
146
|
+
# @return [Object] The decoded response from the canister.
|
95
147
|
def update_raw(canister_id, method_name, arg, return_type = nil, effective_canister_id = nil, **kwargs)
|
96
148
|
req_canister_id = canister_id.is_a?(String) ? Principal.from_str(canister_id).bytes : canister_id.bytes
|
97
149
|
req = {
|
@@ -120,7 +172,13 @@ module IcAgent
|
|
120
172
|
end
|
121
173
|
end
|
122
174
|
|
123
|
-
|
175
|
+
# Sends a raw read state request to a canister and handles the response.
|
176
|
+
#
|
177
|
+
# @param canister_id [String] The ID of the target canister.
|
178
|
+
# @param paths [Array] The paths to read from the canister's state.
|
179
|
+
# @param [TrueClass] bls_verify
|
180
|
+
# @return [Object] The decoded response from the canister.
|
181
|
+
def read_state_raw(canister_id, paths, bls_verify = true)
|
124
182
|
req = {
|
125
183
|
'request_type' => 'read_state',
|
126
184
|
'sender' => @identity.sender.bytes,
|
@@ -140,9 +198,20 @@ module IcAgent
|
|
140
198
|
rescue StandardError
|
141
199
|
raise ValueError, "Unable to decode cbor value: #{ret}"
|
142
200
|
end
|
143
|
-
CBOR.decode(d.value['certificate'])
|
201
|
+
cert = CBOR.decode(d.value['certificate'])
|
202
|
+
|
203
|
+
if bls_verify
|
204
|
+
verify(cert, canister_id) ? cert : false
|
205
|
+
else
|
206
|
+
cert
|
207
|
+
end
|
144
208
|
end
|
145
209
|
|
210
|
+
# Retrieves the status and certificate of a request from a canister.
|
211
|
+
#
|
212
|
+
# @param canister_id [String] The ID of the target canister.
|
213
|
+
# @param req_id [String] The ID of the request.
|
214
|
+
# @return [Array] The status and certificate of the request.
|
146
215
|
def request_status_raw(canister_id, req_id)
|
147
216
|
paths = [['request_status', req_id]]
|
148
217
|
cert = read_state_raw(canister_id, paths)
|
@@ -150,6 +219,12 @@ module IcAgent
|
|
150
219
|
[status, cert]
|
151
220
|
end
|
152
221
|
|
222
|
+
# Polls a canister for the status of a request.
|
223
|
+
#
|
224
|
+
# @param canister_id [String] The ID of the target canister.
|
225
|
+
# @param req_id [String] The ID of the request.
|
226
|
+
# @param delay [Integer] The delay between each poll attempt (in seconds).
|
227
|
+
# @param timeout [Integer] The maximum timeout for polling.
|
153
228
|
def poll(canister_id, req_id, delay = 1, timeout = IcAgent::DEFAULT_POLL_TIMEOUT_SECS)
|
154
229
|
status = nil
|
155
230
|
cert = nil
|
@@ -172,5 +247,80 @@ module IcAgent
|
|
172
247
|
[status, _]
|
173
248
|
end
|
174
249
|
end
|
250
|
+
|
251
|
+
# Verify a BLS signature
|
252
|
+
# The signature must be exactly 48 bytes (compressed G1 element)
|
253
|
+
# The key must be exactly 96 bytes (compressed G2 element)
|
254
|
+
def verify(cert, canister_id)
|
255
|
+
signature_hex = IcAgent::Certificate.signature(cert).str2hex
|
256
|
+
tree = IcAgent::Certificate.tree(cert)
|
257
|
+
delegation = IcAgent::Certificate.delegation(cert)
|
258
|
+
root_hash = IcAgent::Certificate.reconstruct(tree).str2hex
|
259
|
+
msg = IcAgent::IC_STATE_ROOT_DOMAIN_SEPARATOR + root_hash
|
260
|
+
der_key = check_delegation(delegation, canister_id, true)
|
261
|
+
public_key_hash = extract_der(der_key).str2hex
|
262
|
+
|
263
|
+
public_key = BLS::PointG2.from_hex(public_key_hash)
|
264
|
+
signature = BLS::PointG1.from_hex(signature_hex)
|
265
|
+
BLS.verify(signature, msg, public_key)
|
266
|
+
end
|
267
|
+
|
268
|
+
# Check the delegation and return the corresponding root key.
|
269
|
+
def check_delegation(delegation, effective_canister_id, disable_range_check)
|
270
|
+
return @root_key unless delegation
|
271
|
+
|
272
|
+
begin
|
273
|
+
cert = CBOR.decode(delegation['certificate'])
|
274
|
+
rescue CBOR::MalformedFormatError => e
|
275
|
+
raise TypeError, "certificate CBOR::MalformedFormatError: #{delegation['certificate']}"
|
276
|
+
end
|
277
|
+
|
278
|
+
path = ['subnet', delegation['subnet_id'], 'canister_ranges']
|
279
|
+
canister_range = IcAgent::Certificate.lookup(path, cert)
|
280
|
+
|
281
|
+
begin
|
282
|
+
ranges = []
|
283
|
+
ranges_json = CBOR.decode(canister_range).values[1]
|
284
|
+
|
285
|
+
ranges_json.each do |range_json|
|
286
|
+
range = {}
|
287
|
+
range['low'] = Principal.from_hex(range_json[0])
|
288
|
+
range['high'] = Principal.from_hex(range_json[1])
|
289
|
+
ranges << range
|
290
|
+
end
|
291
|
+
|
292
|
+
if !disable_range_check && !principal_is_within_ranges(effective_canister_id, ranges)
|
293
|
+
raise AgentError 'certificate CERTIFICATE_NOT_AUTHORIZED'
|
294
|
+
end
|
295
|
+
rescue Exception => e
|
296
|
+
raise AgentError "certificate INVALID_CBOR_DATA, canister_range: #{canister_range.to_s}"
|
297
|
+
end
|
298
|
+
|
299
|
+
path = ['subnet', delegation['subnet_id'], 'public_key']
|
300
|
+
IcAgent::Certificate.lookup(path, cert)
|
301
|
+
end
|
302
|
+
|
303
|
+
def principal_is_within_ranges(principal, ranges)
|
304
|
+
ranges.each do |range|
|
305
|
+
return true if range['low'].lt_eq(principal) && range['high'].gt_eq(principal)
|
306
|
+
end
|
307
|
+
false
|
308
|
+
end
|
309
|
+
|
310
|
+
# Extract the BLS public key from the DER buffer.
|
311
|
+
def extract_der(der_buf)
|
312
|
+
bls_der_prefix = OpenSSL::BN.from_hex(IcAgent::BLS_DER_PREFIX).to_s(2)
|
313
|
+
expected_length = bls_der_prefix.bytesize + IcAgent::BLS_KEY_LENGTH
|
314
|
+
if der_buf.bytesize != expected_length
|
315
|
+
raise TypeError, "BLS DER-encoded public key must be #{expected_length} bytes long"
|
316
|
+
end
|
317
|
+
|
318
|
+
prefix = der_buf.byteslice(0, bls_der_prefix.bytesize)
|
319
|
+
if prefix != bls_der_prefix
|
320
|
+
raise TypeError, "BLS DER-encoded public key is invalid. Expect the following prefix: #{bls_der_prefix}, but get #{prefix}"
|
321
|
+
end
|
322
|
+
|
323
|
+
der_buf.byteslice(bls_der_prefix.bytesize..-1)
|
324
|
+
end
|
175
325
|
end
|
176
326
|
end
|
@@ -5,24 +5,30 @@ module IcAgent
|
|
5
5
|
class Assembler
|
6
6
|
TYPE_MAPPING = {}
|
7
7
|
|
8
|
+
# Builds a single Candid type from a given child type.
|
8
9
|
def self.build_single_type(child_type)
|
9
10
|
IcAgent::Candid::BaseTypes.send(child_type)
|
10
11
|
end
|
11
12
|
|
13
|
+
|
14
|
+
# Builds a Candid blob type.
|
12
15
|
def self.build_blob
|
13
16
|
IcAgent::Candid::BaseTypes.vec(IcAgent::Candid::BaseTypes.nat8)
|
14
17
|
end
|
15
18
|
|
19
|
+
# Builds a Candid optional type from a given child type.
|
16
20
|
def self.build_opt(child_type, key_types = {})
|
17
21
|
child_type = key_types[child_type].nil? ? build_type(child_type, key_types) : key_types[child_type]
|
18
22
|
IcAgent::Candid::BaseTypes.opt(child_type)
|
19
23
|
end
|
20
24
|
|
25
|
+
# Builds a Candid vector type from a given child type.
|
21
26
|
def self.build_vec(child_type, key_types = {})
|
22
27
|
child_type = key_types[child_type].nil? ? build_type(child_type, key_types) : key_types[child_type]
|
23
28
|
IcAgent::Candid::BaseTypes.vec(child_type)
|
24
29
|
end
|
25
30
|
|
31
|
+
# Builds a Candid record type from a given hash of field names and types.
|
26
32
|
def self.build_record(child_hash, multi_types = {}, key_types = {})
|
27
33
|
child_types = {}
|
28
34
|
child_hash.each_key do |key|
|
@@ -38,6 +44,7 @@ module IcAgent
|
|
38
44
|
IcAgent::Candid::BaseTypes.record(child_types)
|
39
45
|
end
|
40
46
|
|
47
|
+
# Builds a Candid variant type from a given hash of field names and types.
|
41
48
|
def self.build_variant(child_hash, multi_types = {}, key_types = {})
|
42
49
|
child_types = {}
|
43
50
|
child_hash.each_key do |key|
|
@@ -53,6 +60,7 @@ module IcAgent
|
|
53
60
|
IcAgent::Candid::BaseTypes.variant(child_types)
|
54
61
|
end
|
55
62
|
|
63
|
+
# Builds a Candid type based on the given type string.
|
56
64
|
def self.build_type(type_str, key_types = {}, multi_types = {})
|
57
65
|
opt_code = get_opt_code(type_str)
|
58
66
|
|
@@ -91,6 +99,7 @@ module IcAgent
|
|
91
99
|
end
|
92
100
|
end
|
93
101
|
|
102
|
+
# Replaces the last occurrence of a pattern in a string with the given replacement.
|
94
103
|
def self.replace_last_occurrence(string, pattern, replacement)
|
95
104
|
last_index = string.rindex(pattern)
|
96
105
|
return string unless last_index
|
@@ -99,18 +108,21 @@ module IcAgent
|
|
99
108
|
string
|
100
109
|
end
|
101
110
|
|
111
|
+
# Extracts the content of a Candid record type from the type string.
|
102
112
|
def self.get_record_content(record_str)
|
103
113
|
record_str = record_str.sub('record', '').sub('{', '')
|
104
114
|
record_str = replace_last_occurrence(record_str, '}', '')
|
105
115
|
record_str.strip
|
106
116
|
end
|
107
117
|
|
118
|
+
# Extracts the content of a Candid variant type from the type string.
|
108
119
|
def self.get_variant_content(variant_str)
|
109
120
|
variant_str = variant_str.sub('variant', '').sub('{', '')
|
110
121
|
variant_str = replace_last_occurrence(variant_str, '}', '')
|
111
122
|
variant_str.strip
|
112
123
|
end
|
113
124
|
|
125
|
+
# Extracts the key-value pairs from a Candid record item string.
|
114
126
|
def self.get_record_key_value(item_str, index_str, key_index = 0)
|
115
127
|
first_index = item_str.index(index_str)
|
116
128
|
if first_index
|
@@ -123,16 +135,19 @@ module IcAgent
|
|
123
135
|
return key, value
|
124
136
|
end
|
125
137
|
|
138
|
+
# Extracts the Candid code (e.g., "record", "variant", "opt", etc.) from the type string.
|
126
139
|
def self.get_opt_code(item_str)
|
127
140
|
opt_code = item_str.strip
|
128
141
|
opt_code.split(' ')[0]
|
129
142
|
end
|
130
143
|
|
144
|
+
# Extracts the child Candid code from the type string.
|
131
145
|
def self.get_child_code(item_str, index_str)
|
132
146
|
first_index = item_str.index(index_str)
|
133
147
|
item_str[(first_index + index_str.size)..].strip
|
134
148
|
end
|
135
149
|
|
150
|
+
# Replaces occurrences of Candid record and variant types with unique type names.
|
136
151
|
def self.replace_multi_type(type_str)
|
137
152
|
replaced_hash = {}
|
138
153
|
modified_str = type_str.gsub(/record\s*{[^{}]*}/) do |match|
|
@@ -152,6 +167,7 @@ module IcAgent
|
|
152
167
|
return modified_str, replaced_hash
|
153
168
|
end
|
154
169
|
|
170
|
+
# Gets the refer types used in the type string.
|
155
171
|
def self.get_params_refer_values(type_str)
|
156
172
|
parser = IcAgent::Ast::StatementParser.new
|
157
173
|
parser.parse(type_str)
|
@@ -159,6 +175,7 @@ module IcAgent
|
|
159
175
|
refer_type
|
160
176
|
end
|
161
177
|
|
178
|
+
# Recovers the original type string from the multi_types hash.
|
162
179
|
def self.recover_type(type_str, multi_types)
|
163
180
|
multi_types.each_key do |key|
|
164
181
|
type_str = type_str.gsub(key, multi_types[key])
|