ic_agent 0.1.4 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/ic_agent.svg)](https://badge.fury.io/rb/ic_agent)[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](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])
|