universa 0.1.9 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3393ecdb08b1b7fe37288be48d86b47a740cf7e923575caa45295c8cdb861247
4
- data.tar.gz: 76a9a13bdf1a39c8d5e9a0e838a3faf18283d9b357cd3bca18c75f076dbcc65f
3
+ metadata.gz: ff86a735f983f4cb94e6ca82be229c8c47749d975483fe52d11972a8664853d8
4
+ data.tar.gz: f1625ae689ba6bd1ff017c2a3926e16e3b6efcc71f90f7896b60ccce19839413
5
5
  SHA512:
6
- metadata.gz: 44c450550570eb0e447f7824dae25129cfd59ead09cac86c19b8d3b261d226d7964b88ca3304fc19790ca7512509c5ae3575c0aed09da608133de588d1d1548c
7
- data.tar.gz: 5b093f28766e602653ad3c9797f4ce6d6a63796c8cd45ab9252b4bd78e145a43aa9510c51370d210d23a97eeab7d4cd14049466f3bc3de0fd933db3b3c1a3467
6
+ metadata.gz: fd4b45a562f5340511e659fb1019642599b74d431cdb5c8277a168e014ac1c085518798232d6c156de43377a3bdb3eec819ff935518bd107aef65726515f60db
7
+ data.tar.gz: 00a4544555433540ca9317ee4ce3bb7a090c649b5489809b364e79a36d784d4905f2af5b42534ade5b79b809ba19fe1d98a63ea784aff4cb19bf0aeb3af514ca
data/README.md CHANGED
@@ -6,6 +6,18 @@ for direct access to remote objects.
6
6
  This is an under-construction official gem from [Universa][universa] to facilitate access to the
7
7
  Java library using Universa's UMI protocol.
8
8
 
9
+ ## News
10
+
11
+ - alfa version of the local FS-based contract store.
12
+ - ability to edit `contract.state` and `contract.transactional` in new revisions.
13
+ - fixed errors with interchange builder and set based objects
14
+ - ruby sugar for Universa native classes: Role, Adapter, Contract, Binder, HashId.
15
+ - Contract creation, revocation, changing owner in ruby way
16
+ - Network operation for white keys/private networks: parallel state check, contract recistration.
17
+
18
+ This gem is already used in new Universa projects and is being actively tested.
19
+
20
+
9
21
  ## Installation
10
22
 
11
23
  ### Prerequisites
@@ -8,6 +8,9 @@ require "universa/keys"
8
8
  require "universa/binder"
9
9
  require "universa/contract"
10
10
  require "universa/client"
11
+ require 'universa/stored_contract'
12
+ require "universa/chain_store"
13
+ require "universa/fs_store/file_store"
11
14
 
12
15
  # The Universa gem
13
16
  #
@@ -68,8 +68,8 @@ module Universa
68
68
  end
69
69
 
70
70
  # @return an array of values returned by the block
71
- # @yiekd [key,value] pairs.
72
- def map &block
71
+ # @yield [key,value] pairs.
72
+ def map(&block)
73
73
  keys.map {|k| block.call [k, __getobj__.get(k)]}
74
74
  end
75
75
 
@@ -86,7 +86,13 @@ module Universa
86
86
 
87
87
  end
88
88
 
89
+ # enhance Hash with UMI access convenience methods.
89
90
  class Hash
91
+ # Convert the hash to the {}Binder} stored in the remote side and suitable for
92
+ # Universa UMI calls.
93
+ #
94
+ # @return [Binder] constructed reference to the remotely created Binder.
95
+ #
90
96
  def to_binder
91
97
  Binder.of self
92
98
  end
@@ -0,0 +1,54 @@
1
+ require 'yaml'
2
+
3
+ module Universa
4
+
5
+ # The storage interface capable to store contracts in chains, providing search and attributes.
6
+ # This class is not a store itself but the base class for it, having common boilerplate and
7
+ # sort of interface to implement.
8
+ class ChainStore
9
+
10
+ # Save contract to the store. When this method returns, the contract must me already stored.
11
+ # If the contract with such hasId is already stored, just returns it.
12
+ #
13
+ # @param [Object] contract to store
14
+ # @return [StoredContract] for this contract
15
+ def store_contract(contract)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ # Same as {#store_contract} but returns store
20
+ # @param [Contract] contract to add
21
+ # @return [ChainStore] self
22
+ def <<(contract)
23
+ store_contract(contract)
24
+ self
25
+ end
26
+
27
+ # @return [Contract] with the corresponding id or nil
28
+ # @param [HashId] hash_id instance to look for
29
+ def find_by_id(hash_id)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ # Count contracts in the store. This operation could be slow.
34
+ def count
35
+ raise NotImplementedError
36
+ end
37
+
38
+ # @return [Contract] with the corresponding id or raise.
39
+ # @param [HashId] hash_id instance to look for
40
+ # @raise [NotFoundError]
41
+ def find_by_id! hash_id
42
+ find_by_id(hash_id) or raise NotFoundError
43
+ end
44
+
45
+ # Find all contracts with this parent id.
46
+ # @param [HashId] hash_id of the parent contract
47
+ # @return [Array] all the contracts that match this criterion
48
+ def find_by_parent(hash_id)
49
+ raise NotImplementedError
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -172,9 +172,9 @@ module Universa
172
172
  # Register a single contract (on private network or if you have white key allowing free operations)
173
173
  # on a single node.
174
174
  #
175
- # @param [Contract] contract, muts be sealed ({Contract#seal})
175
+ # @param [Contract] contract must be sealed ({Contract#seal})
176
176
  # @return [ContractState] of the result. Could contain errors.
177
- def register_single contract
177
+ def register_single(contract)
178
178
  retry_with_timeout(15, 3) {
179
179
  result = ContractState.new(execute "approve", packedItem: contract.packed)
180
180
  while result.is_pending
@@ -205,8 +205,9 @@ module Universa
205
205
  # to the node.
206
206
  #
207
207
  # @param [String|Symbol] name of the command
208
+ # @param kwargs arguments to call
208
209
  # @return [SmartHash] with the command result
209
- def execute name, **kwargs
210
+ def execute(name, **kwargs)
210
211
  connection.command name.to_s, *kwargs.to_a.flatten
211
212
  end
212
213
 
@@ -34,11 +34,12 @@ module Universa
34
34
  invoke_static 'with_digest', digest_bytes
35
35
  end
36
36
 
37
- # Construct from string representation of the ID, not to confuse with binary one.
37
+ # Construct from string representation of the ID, not to confuse with binary one. This method takes both
38
+ # regular base64 representation and RFC3548 url-safe modification, as from {#to_url_safe_string}.
38
39
  #
39
40
  # @param [String] string_id id string representation, like from +hash_id_instance.to_s+. See {#to_s}.
40
41
  def self.from_string(string_id)
41
- string_id.force_encoding 'utf-8'
42
+ string_id.force_encoding('utf-8').gsub('-','+').gsub('_','/')
42
43
  invoke_static 'with_digest', string_id
43
44
  end
44
45
 
@@ -57,6 +58,28 @@ module Universa
57
58
  def to_s
58
59
  Base64.encode64(get_digest).gsub(/\s/, '')
59
60
  end
61
+
62
+ # Converts to URL-safe varianot of base64, as RFC 3548 suggests:
63
+ # the 63:nd / character with the underscore _
64
+ # the 62:nd + character with the minus -
65
+ #
66
+ # Could be decoded safely back with {HashId.from_string} but not (most likely) with JAVA API itself
67
+ # @return [String] RFC3548 modified base64
68
+ def to_url_safe_string
69
+ Base64.encode64(get_digest).gsub(/\s/, '').gsub('/','_').gsub('+', '-')
70
+ end
71
+
72
+ # To use it as a hash key_address.
73
+ # @return hash calculated over the digest bytes
74
+ def hash
75
+ bytes.hash
76
+ end
77
+
78
+ # To use it as a hash key_address. Same as this == other.
79
+ def eql? other
80
+ self == other
81
+ end
82
+
60
83
  end
61
84
 
62
85
  # Universa contract adapter.
@@ -66,17 +89,17 @@ module Universa
66
89
  # Create simple contract with preset critical parts:
67
90
  #
68
91
  # - expiration set to 90 days unless specified else
69
- # - issuer role is set to the address of the issuer key, short ot long
92
+ # - issuer role is set to the address of the issuer key_address, short ot long
70
93
  # - creator role is set as link to issuer
71
94
  # - owner role is set as link to issuer
72
95
  # - change owner permission is set to link to owner
73
96
  #
74
- # The while contract is then signed by the issuer key. Not that it will not seal it: caller almost always
97
+ # The while contract is then signed by the issuer key_address. Not that it will not seal it: caller almost always
75
98
  # will add more data before it, then must call #seal().
76
99
  #
77
100
  # @param [PrivateKey] issuer_key also will be used to sign it
78
101
  # @param [Time] expires_at defaults to 90 days
79
- # @param [Boolean] use_short_address set to true to use short address of the issuer key in the role
102
+ # @param [Boolean] use_short_address set to true to use short address of the issuer key_address in the role
80
103
  # @return [Contract] simple contact, not sealed
81
104
  def self.create issuer_key, expires_at: (Time.now + 90 * 24 * 60 * 60), use_short_address: false
82
105
  contract = Contract.new
@@ -92,6 +115,7 @@ module Universa
92
115
 
93
116
  # Load from transaction pack
94
117
  def self.from_packed packed
118
+ packed.nil? and raise ArgumentError, "packed contract required"
95
119
  packed.force_encoding 'binary'
96
120
  self.invoke_static "fromPackedTransaction", packed
97
121
  end
@@ -124,8 +148,10 @@ module Universa
124
148
  get_owner
125
149
  end
126
150
 
127
- def owner= key
128
- set_owner_key key
151
+ # Set owner to the key_address, usable only in the simplest case where owner is the single address.
152
+ # @param [KeyAddress | PublicKey] key_address
153
+ def owner=(key_address)
154
+ set_owner_key key_address
129
155
  end
130
156
 
131
157
  # Shortcut for is_ok
@@ -134,16 +160,29 @@ module Universa
134
160
  end
135
161
 
136
162
  # shortcut for getHashId
163
+ # @return [HashId] of the contracr
137
164
  def hash_id
138
- get_id
165
+ getId()
166
+ end
167
+
168
+ # @return [HashId] of the origin contract
169
+ def origin
170
+ getOrigin()
139
171
  end
140
172
 
141
- # shortcut for get_expires_at
173
+ # @return [HashId] pf the parent contracr
174
+ def parent
175
+ getParent()
176
+ end
177
+
178
+ # shortcut for get_expires_at. Get the contract expiration time.
142
179
  def expires_at
143
180
  get_expires_at
144
181
  end
145
182
 
146
- def expires_at= time
183
+ # set +expires_at+ field
184
+ # @param [Time] time when this contract will be expired, if yet +APPROVED+.
185
+ def expires_at=(time)
147
186
  set_expires_at time
148
187
  end
149
188
 
@@ -152,6 +191,7 @@ module Universa
152
191
  @definition ||= get_definition.get_data
153
192
  end
154
193
 
194
+ # Return +state+ binder. Shortcut for Java API +getStateData()+
155
195
  def state
156
196
  @state ||= getStateData()
157
197
  end
@@ -175,7 +215,7 @@ module Universa
175
215
  # Write helper for many token-like contracts containing state.data.amount. Saves value
176
216
  # in state.data.anomount and properly encodes it so it will be preserved on packing.
177
217
  #
178
- # @param [Object] value, should be some representation of a number (also string)
218
+ # @param [Object] value should be some representation of a number (also string)
179
219
  def amount= (value)
180
220
  state[:amount] = value.to_s.force_encoding('utf-8')
181
221
  end
@@ -203,7 +243,11 @@ module Universa
203
243
  getErrors.map {|e| "(#{e.object || ''}): #{e.error}"}.join(', ').strip
204
244
  end
205
245
 
206
- def can_perform_role name, *keys
246
+ # Test that some set of keys could be used to perform some role.
247
+ #
248
+ # @param [String] name of the role to check
249
+ # @param [PublicKey] keys instances to check against
250
+ def can_perform_role(name, *keys)
207
251
  getRole(name.to_s).isAllowedForKeys(Set.new keys.map {|x|
208
252
  x.is_a?(PrivateKey) ? x.public_key : x
209
253
  })
@@ -16,6 +16,16 @@ module Universa
16
16
  end
17
17
  end
18
18
 
19
+ class StoreError < Error;
20
+ end
21
+
22
+ class NotFoundError < StoreError
23
+ end
24
+
25
+ class IllegalStateError < StoreError
26
+
27
+ end
28
+
19
29
  # Easy print stack trace refinement
20
30
  refine Exception do
21
31
 
@@ -0,0 +1,118 @@
1
+ module Universa::FSStore
2
+
3
+ # The {StoredContract} implementation to work with {FileStore}.
4
+ #
5
+ # @!method name
6
+ # @return [String] the +contract.definition.data.name+ value or nil.
7
+ #
8
+ # @!method currency
9
+ # @return [String] the +contract.definition.data.currency+ value or nil.
10
+ #
11
+ # @!method amount
12
+ # @return [BigDecimal] +contract.state.data.amount+ or nil. See {Contract#amount} for more.
13
+ #
14
+ class Entry < Universa::StoredContract
15
+
16
+ extend Forwardable
17
+
18
+ # (see StoredContract#load)
19
+ def load(hash_id)
20
+ init_with_hash_id hash_id
21
+ self
22
+ end
23
+
24
+ # initialize new instance with an existing contract
25
+ # @param [Contract] contract to store
26
+ def init_with_contract(contract)
27
+ self.contract = contract
28
+ self
29
+ end
30
+
31
+ # initialize new instance with attributes YAML file
32
+ # @param [String] file_name of the +.unicon.yaml+ file
33
+ def load_from_yaml_file(file_name)
34
+ init_with_yaml_file_name file_name
35
+ self
36
+ end
37
+
38
+ # (see StoredContract#hash_id)
39
+ def hash_id
40
+ @id
41
+ end
42
+
43
+ # (see StoredContract#contract=)
44
+ def contract= new_contract
45
+ @id = new_contract.hash_id
46
+ prepare_file_names
47
+ super
48
+ # we will always rewrite existing file to be sure it is correct
49
+ open(@file_name, 'wb') {|f| f << contract.packed}
50
+ # now we are to extract and rewrite attributes
51
+ load_attributes_from_contract # it will save them too
52
+ end
53
+
54
+ # Implement lazy load logic
55
+ # @return [Contract] instance loaded at first call only
56
+ def contract
57
+ # load it if it is not
58
+ self.packed_contract = open(@file_name, 'rb') {|f| f.read} unless has_contract?
59
+ # @attributes must already be set
60
+ super
61
+ end
62
+
63
+ def_delegators :@attributes, :name, :currency, :amount
64
+
65
+ protected
66
+
67
+ # initialize instance for an existing file. Should be a contract already stored in the connected store.
68
+ # @param [Object] hash_id to construct from
69
+ def init_with_hash_id(hash_id)
70
+ raise IllegalStateError, "already initialized" if @id
71
+ @id = hash_id
72
+ prepare_file_names
73
+ load_attributes_from_file
74
+ # attrs are already in the file so we need not to save them
75
+ end
76
+
77
+ # Load from attributes file name
78
+ def init_with_yaml_file_name file_name
79
+ @attr_file_name = file_name
80
+ load_attributes_from_file
81
+ prepare_file_names
82
+ end
83
+
84
+ # save attributes to .yaml file
85
+ def save_attributes
86
+ open(@attr_file_name, 'w') {|f| YAML.dump(@attributes, f)}
87
+ end
88
+
89
+ # load attributes from a contract (already assigned) and store them in the .yaml file
90
+ def load_attributes_from_contract
91
+ state = contract.state
92
+ definition = contract.definition
93
+ @attributes = SmartHash.new({
94
+ id: hash_id.to_s,
95
+ parent: parent&.to_s,
96
+ origin: origin.to_s,
97
+ name: definition.name,
98
+ currency: definition.currency,
99
+ amount: state.amount
100
+ })
101
+ save_attributes
102
+ end
103
+
104
+ # load attributes from the .yaml file
105
+ def load_attributes_from_file
106
+ @attributes = SmartHash.new(YAML.load_file(@attr_file_name))
107
+ @id = HashId.from_string @attributes.id
108
+ end
109
+
110
+ # prepare file name fields (@file_name and @attr_file_name)
111
+ # @param [HashId] hash_id or ni to use one already set in @id
112
+ def prepare_file_names(hash_id = nil)
113
+ @file_name = "#{chain_store.root}/#{@id.to_url_safe_string[0..27]}.unicon"
114
+ @attr_file_name = "#@file_name.yaml"
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,63 @@
1
+ require 'forwardable'
2
+ require_relative './entry'
3
+
4
+ # Filesystem-based storage. See {FileStore}.
5
+ module Universa::FSStore
6
+
7
+ # Simple file-based store that could be efficiently user with per-file cloud storages like Dropbox,
8
+ # Google Disk, NextCloud and like.
9
+ #
10
+ # Notes to developers:
11
+ #
12
+ # - attributes are eager loaded: should always be contructed from contract or from file
13
+ # - contract is lazy loaded
14
+ class FileStore < Universa::ChainStore
15
+
16
+ # [String] The file store root path
17
+ attr :root
18
+
19
+ # Construct store in the path supplied. If the path is not empty, it will be scanned for stored contracts.
20
+ # @param [String] root_path of the store, must exist.
21
+ def initialize(root_path)
22
+ @root = root_path
23
+ @root = @root[0...-1] while (@root[-1] == '/')
24
+ init_cache
25
+ end
26
+
27
+ # (see ChainStore#store_contract)
28
+ def store_contract contract
29
+ entry = FSStore::Entry.new(self)
30
+ entry = entry.init_with_contract(contract)
31
+ add_to_cache entry
32
+ end
33
+
34
+ # (see ChainStore#find_by_id)
35
+ def find_by_id hash_id
36
+ @cache[hash_id]
37
+ end
38
+
39
+ # (see ChainStore#count)
40
+ def count
41
+ @cache.size
42
+ end
43
+
44
+ protected
45
+
46
+ # scan the root folder for attribute files and store them in the cache
47
+ def init_cache
48
+ @cache = {}
49
+ Dir[@root + "/*.unicon.yaml"].each {|name|
50
+ add_to_cache Entry.new(self).load_from_yaml_file(name)
51
+ }
52
+ end
53
+
54
+ # add single entry to the cache
55
+ # @param [Entry] entry to add. Could have contract not yet loaded but should be configured with attributes.
56
+ def add_to_cache(entry)
57
+ raise ArgumentError, "entry can't be nil" unless entry
58
+ @cache[entry.hash_id] = entry
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,76 @@
1
+ module Universa
2
+
3
+ # this is a base class for a contract stored in some contract chain. The implementation
4
+ # must inherit and implement its {#load} and {#save} methods at least. To do it,
5
+ # inherit and implement {ChainStore} to work with it.
6
+ #
7
+ # Notable features:
8
+ #
9
+ # - contract could be assigned only once, no matter how, so its fields could be cached.
10
+ # - origin, hash_id and parent are cached. So other contract parameters should be.
11
+ #
12
+ class StoredContract
13
+
14
+ # {ChainStore} instance to which it is connected
15
+ attr :chain_store
16
+ # {Contract} instance stored in it. Can be lazy-loaded
17
+ attr :contract
18
+ # {HashId} of the {#contract}
19
+ attr :hash_id
20
+
21
+ # @return [HashId] {#contract}.origin. See {Contract#origin}
22
+ def origin
23
+ @origin ||= @contract.origin
24
+ end
25
+
26
+ # @return [HashId] {#contract}.origin. See {Contract#parent}
27
+ def parent
28
+ @parent ||= @contract.parent
29
+ end
30
+
31
+ # Construct implementation connected to a given store
32
+ # @param [ChainStore] chain_store descendant class
33
+ def initialize(chain_store)
34
+ @chain_store = chain_store
35
+ @chain_store.is_a?(ChainStore) or raise ArgumentError, "ChainStore instance required"
36
+ @chain_store = chain_store
37
+ end
38
+
39
+ # For implementation logic, in particular, to make lazy loads.
40
+ # @return true if the stored contract is loaded into this instance
41
+ def has_contract?
42
+ !@contract.nil?
43
+ end
44
+
45
+ # Shortcut for `contract.packed`. See {Contract#packed}
46
+ # @return [String] binary string with contained contract packed transaction.
47
+ def packed_contract
48
+ @contract.packed
49
+ end
50
+
51
+ # override it to save the contract in the connected contract chain.
52
+ def save
53
+ raise NotFoundError
54
+ end
55
+
56
+ # override it to load the contract from the connected contract chain.
57
+ def load hash_id
58
+ raise NotFoundError
59
+ end
60
+
61
+ # Assign contract to the instance.
62
+ # @param [Contracy] new_contract to store
63
+ def contract=(new_contract)
64
+ raise IllegalStateError, "contract can't be reassigned" if has_contract?
65
+ @contract = new_contract
66
+ @hash_id = @contract.hash_id
67
+ @origin = @parent = nil
68
+ end
69
+
70
+ # Convenience method. Unoacks and stores the contract.
71
+ def packed_contract=(new_packed_contract)
72
+ self.contract = Contract.from_packed(new_packed_contract)
73
+ end
74
+
75
+ end
76
+ end
@@ -7,14 +7,16 @@ module Universa
7
7
  class SmartHash < Farcall::SmartHash
8
8
  end
9
9
 
10
- def retry_with_timeout(max_timeout = 15, max_times = 3, &block)
10
+ def retry_with_timeout(max_timeout = 25, max_times = 3, &block)
11
11
  attempt = 0
12
- Timeout::timeout(max_timeout, &block)
13
- rescue
14
- attempt += 1
15
- puts "timeout: retry (#$!): #{attempt}"
16
- retry if attempt < max_times
17
- raise
12
+ begin
13
+ Timeout::timeout(max_timeout, &block)
14
+ rescue
15
+ attempt += 1
16
+ puts "timeout: retry (#$!): #{attempt}"
17
+ retry if attempt < max_times
18
+ raise
19
+ end
18
20
  end
19
21
 
20
22
  module Parallel
@@ -26,7 +28,7 @@ module Universa
26
28
 
27
29
  @@pool = CachedThreadPool.new
28
30
 
29
- # Enumerates in parallel all items. Like {Enumerable#each_with_index}, but requires block.
31
+ # Enumerates in parallel all items. Like +Enumerable#each_with_index+, but requires block.
30
32
  # Blocks until all items are processed.
31
33
  #
32
34
  # @param [Proc] block to call with (object, index) parameters
@@ -55,7 +57,7 @@ module Universa
55
57
  each_with_index {|x, i| block.call(x)}
56
58
  end
57
59
 
58
- # Parallel version of the {Enumerable#map}. Creates a new array containing the values returned by the block,
60
+ # Parallel version of the +Enumerable#map+. Creates a new array containing the values returned by the block,
59
61
  # using parallel execution in threads.
60
62
  #
61
63
  # @return new array containing the values returned by the block.
@@ -1,4 +1,4 @@
1
1
  module Universa
2
2
  # Current gem version
3
- VERSION = "0.1.9"
3
+ VERSION = "0.2.1"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: universa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - sergeych
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-30 00:00:00.000000000 Z
11
+ date: 2018-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: farcall
@@ -154,11 +154,15 @@ files:
154
154
  - exe/universa
155
155
  - lib/universa.rb
156
156
  - lib/universa/binder.rb
157
+ - lib/universa/chain_store.rb
157
158
  - lib/universa/client.rb
158
159
  - lib/universa/contract.rb
159
160
  - lib/universa/errors.rb
161
+ - lib/universa/fs_store/entry.rb
162
+ - lib/universa/fs_store/file_store.rb
160
163
  - lib/universa/keys.rb
161
164
  - lib/universa/service.rb
165
+ - lib/universa/stored_contract.rb
162
166
  - lib/universa/string_utils.rb
163
167
  - lib/universa/tools.rb
164
168
  - lib/universa/umi.rb
@@ -185,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
189
  version: '0'
186
190
  requirements: []
187
191
  rubyforge_project:
188
- rubygems_version: 2.7.3
192
+ rubygems_version: 2.7.6
189
193
  signing_key:
190
194
  specification_version: 4
191
195
  summary: Expose Universa Java API to ruby