0xfacet-typed 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +12 -0
- data/README.md +25 -0
- data/Rakefile +31 -0
- data/lib/0xfacet/typed/array_type.rb +89 -0
- data/lib/0xfacet/typed/attr_public_read_private_write.rb +9 -0
- data/lib/0xfacet/typed/contract_errors.rb +34 -0
- data/lib/0xfacet/typed/contract_type.rb +64 -0
- data/lib/0xfacet/typed/mapping_type.rb +102 -0
- data/lib/0xfacet/typed/type.rb +290 -0
- data/lib/0xfacet/typed/typed_variable.rb +129 -0
- data/lib/0xfacet/typed.rb +16 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a0ed00364240e4bb50034acabd0cec233cd24cae34ccda46f7e245a52d43b666
|
4
|
+
data.tar.gz: 359636ba089c58bf1fdd97d45b425dec14d63ee30f58815d9a268cfba5a3fa8e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2bb991ecfa5a60979e4998e13613dc9400df98d09ee892d5e6747107d3675944b9178c072b1c3ae39d54d3ce1e7f7b06f523312c24d35e5efe1e8c7b04649c58
|
7
|
+
data.tar.gz: 70c0e53db248bb2f12353059bf1d42adc468d7d2d7bb27b8f25b17df30d7143df6be72ce621df153506e388b3d888dfe89fe77b8e224f4cb7e00b783a27dafac
|
data/CHANGELOG.md
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
CHANGELOG.md
|
2
|
+
Manifest.txt
|
3
|
+
README.md
|
4
|
+
Rakefile
|
5
|
+
lib/0xfacet/typed.rb
|
6
|
+
lib/0xfacet/typed/array_type.rb
|
7
|
+
lib/0xfacet/typed/attr_public_read_private_write.rb
|
8
|
+
lib/0xfacet/typed/contract_errors.rb
|
9
|
+
lib/0xfacet/typed/contract_type.rb
|
10
|
+
lib/0xfacet/typed/mapping_type.rb
|
11
|
+
lib/0xfacet/typed/type.rb
|
12
|
+
lib/0xfacet/typed/typed_variable.rb
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# 0xFacet Typed
|
2
|
+
|
3
|
+
0xfacet-typed - solidity-like value and reference types for rubidity o.g.
|
4
|
+
|
5
|
+
|
6
|
+
* home :: [github.com/s6ruby/rubidity.review](https://github.com/s6ruby/rubidity.review)
|
7
|
+
* bugs :: [github.com/s6ruby/rubidity.review/issues](https://github.com/s6ruby/rubidity/issues)
|
8
|
+
* gem :: [rubygems.org/gems/0xfacet-typed](https://rubygems.org/gems/0xfacet-typed)
|
9
|
+
* rdoc :: [rubydoc.info/gems/0xfacet-typed](http://rubydoc.info/gems/0xfacet-typed)
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
## About
|
14
|
+
|
15
|
+
What's happening herè?
|
16
|
+
|
17
|
+
The idea is to look at the facet vm code as-is (that is, NOT suggesting new or alternate syntax and semantics) in the review / commentary
|
18
|
+
and start to (re)package / modular-ize
|
19
|
+
code in "place holder" gems (waiting for adoption by the founders) such as 0xfacet and 0xfacet-typed and 0xfacet-rubidity.
|
20
|
+
|
21
|
+
See [Rubidity O.G. (Dumb Contracts) Public Code Review / (More) Tests / Gems & More »](https://github.com/s6ruby/rubidity.review)
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'hoe'
|
2
|
+
|
3
|
+
# require './lib/0xfacet/typed/version.rb'
|
4
|
+
|
5
|
+
|
6
|
+
Hoe.spec '0xfacet-typed' do
|
7
|
+
|
8
|
+
self.version = '0.0.1'
|
9
|
+
|
10
|
+
self.summary = '0xfacet-typed - solidity-like value and reference types for rubidity o.g.'
|
11
|
+
self.description = summary
|
12
|
+
|
13
|
+
self.urls = { home: 'https://github.com/s6ruby/rubidity.review' }
|
14
|
+
|
15
|
+
self.author = 'Gerald Bauer'
|
16
|
+
self.email = 'gerald.bauer@gmail.com'
|
17
|
+
|
18
|
+
# switch extension to .markdown for gihub formatting
|
19
|
+
self.readme_file = 'README.md'
|
20
|
+
self.history_file = 'CHANGELOG.md'
|
21
|
+
|
22
|
+
self.extra_deps = [
|
23
|
+
]
|
24
|
+
|
25
|
+
|
26
|
+
self.licenses = ['Public Domain']
|
27
|
+
|
28
|
+
self.spec_extras = {
|
29
|
+
required_ruby_version: '>= 2.3'
|
30
|
+
}
|
31
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class ArrayType < TypedVariable
|
2
|
+
def initialize(...)
|
3
|
+
super(...)
|
4
|
+
value.on_change = on_change
|
5
|
+
end
|
6
|
+
|
7
|
+
def serialize
|
8
|
+
value.data.map(&:serialize)
|
9
|
+
end
|
10
|
+
|
11
|
+
class Proxy
|
12
|
+
extend AttrPublicReadPrivateWrite
|
13
|
+
|
14
|
+
attr_accessor :on_change
|
15
|
+
attr_public_read_private_write :value_type, :data
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
return false unless other.is_a?(self.class)
|
19
|
+
|
20
|
+
other.value_type == value_type &&
|
21
|
+
other.data == data
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(
|
25
|
+
initial_value = [],
|
26
|
+
value_type:,
|
27
|
+
initial_length: nil,
|
28
|
+
on_change: nil
|
29
|
+
)
|
30
|
+
unless value_type.is_value_type?
|
31
|
+
raise VariableTypeError.new("Only value types can me array elements")
|
32
|
+
end
|
33
|
+
|
34
|
+
self.value_type = value_type
|
35
|
+
self.data = initial_value
|
36
|
+
|
37
|
+
if initial_length
|
38
|
+
amount_to_pad = initial_length - data.size
|
39
|
+
|
40
|
+
amount_to_pad.times do
|
41
|
+
data << TypedVariable.create(value_type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
self.on_change = on_change
|
46
|
+
end
|
47
|
+
|
48
|
+
def [](index)
|
49
|
+
index_var = TypedVariable.create_or_validate(:uint256, index, on_change: on_change)
|
50
|
+
|
51
|
+
raise "Index out of bounds" if index_var >= data.size
|
52
|
+
|
53
|
+
value = data[index_var]
|
54
|
+
value || TypedVariable.create_or_validate(value_type, on_change: on_change)
|
55
|
+
end
|
56
|
+
|
57
|
+
def []=(index, value)
|
58
|
+
index_var = TypedVariable.create_or_validate(:uint256, index, on_change: on_change)
|
59
|
+
|
60
|
+
raise "Sparse arrays are not supported" if index_var > data.size
|
61
|
+
|
62
|
+
old_value = self.data[index_var]
|
63
|
+
val_var = TypedVariable.create_or_validate(value_type, value, on_change: on_change)
|
64
|
+
|
65
|
+
if old_value != val_var
|
66
|
+
on_change&.call
|
67
|
+
self.data[index_var] ||= val_var
|
68
|
+
self.data[index_var].value = val_var.value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def push(value)
|
73
|
+
next_index = data.size
|
74
|
+
|
75
|
+
self.[]=(next_index, value)
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def pop
|
80
|
+
on_change&.call
|
81
|
+
|
82
|
+
data.pop
|
83
|
+
end
|
84
|
+
|
85
|
+
def length
|
86
|
+
data.length
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ContractErrors
|
2
|
+
class ContractError < StandardError
|
3
|
+
attr_accessor :contract
|
4
|
+
attr_accessor :error_status
|
5
|
+
|
6
|
+
def initialize(message, contract = nil)
|
7
|
+
super(message)
|
8
|
+
@contract = contract
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
return super if contract.blank?
|
13
|
+
|
14
|
+
trace = !Rails.env.production? ? backtrace.join("\n") : ''
|
15
|
+
|
16
|
+
"#{contract.class.name} error: " + super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class StaticCallError < StandardError; end
|
21
|
+
class TransactionError < StandardError; end
|
22
|
+
class ContractRuntimeError < ContractError; end
|
23
|
+
class ContractDefinitionError < ContractError; end
|
24
|
+
class StateVariableTypeError < StandardError; end
|
25
|
+
class VariableTypeError < StandardError; end
|
26
|
+
class StateVariableMutabilityError < StandardError; end
|
27
|
+
class ContractArgumentError < StandardError; end
|
28
|
+
class CallingNonExistentContractError < TransactionError; end
|
29
|
+
class InvalidOverrideError < StandardError; end
|
30
|
+
class FunctionAlreadyDefinedError < StandardError; end
|
31
|
+
class InvalidEthscriptionError < StandardError; end
|
32
|
+
class InvalidDestructuringError < StandardError; end
|
33
|
+
class InvalidStateVariableChange < StandardError; end
|
34
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class ContractType < TypedVariable
|
2
|
+
def initialize(...)
|
3
|
+
super(...)
|
4
|
+
end
|
5
|
+
|
6
|
+
def serialize
|
7
|
+
value.address
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *args, **kwargs, &block)
|
11
|
+
value.send(name, *args, **kwargs, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def respond_to_missing?(name, include_private = false)
|
15
|
+
value.respond_to?(name, include_private) || super
|
16
|
+
end
|
17
|
+
|
18
|
+
class Proxy
|
19
|
+
include ContractErrors
|
20
|
+
extend AttrPublicReadPrivateWrite
|
21
|
+
|
22
|
+
attr_public_read_private_write :contract_type, :address,
|
23
|
+
:uncast_address, :contract_interface
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
return false unless other.is_a?(self.class)
|
27
|
+
|
28
|
+
other.contract_type == contract_type &&
|
29
|
+
other.address == address
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(contract_type:, address:, contract_interface:)
|
33
|
+
self.uncast_address = address
|
34
|
+
address = TypedVariable.create_or_validate(:address, address).value
|
35
|
+
|
36
|
+
self.contract_type = contract_type
|
37
|
+
self.address = address
|
38
|
+
self.contract_interface = contract_interface
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(name, *args, **kwargs, &block)
|
42
|
+
computed_args = args.presence || kwargs
|
43
|
+
|
44
|
+
super unless contract_interface
|
45
|
+
|
46
|
+
known_function = contract_interface.public_abi[name]
|
47
|
+
|
48
|
+
unless known_function && known_function.args.length == computed_args.length
|
49
|
+
raise ContractError.new("Contract doesn't implement interface: #{contract_type}, #{name}")
|
50
|
+
end
|
51
|
+
|
52
|
+
TransactionContext.call_stack.execute_in_new_frame(
|
53
|
+
to_contract_address: address,
|
54
|
+
function: name,
|
55
|
+
args: computed_args,
|
56
|
+
type: :call
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def respond_to_missing?(name, include_private = false)
|
61
|
+
!!contract_interface.public_abi[name] || super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class MappingType < TypedVariable
|
2
|
+
def initialize(...)
|
3
|
+
super(...)
|
4
|
+
value.on_change = on_change
|
5
|
+
end
|
6
|
+
|
7
|
+
def serialize
|
8
|
+
value.serialize
|
9
|
+
end
|
10
|
+
|
11
|
+
class Proxy
|
12
|
+
extend AttrPublicReadPrivateWrite
|
13
|
+
|
14
|
+
attr_accessor :on_change
|
15
|
+
attr_public_read_private_write :transformed_keys, :data, :key_type, :value_type
|
16
|
+
|
17
|
+
def serialize
|
18
|
+
serialized_dirty_data = {}
|
19
|
+
|
20
|
+
transformed_keys.each do |key|
|
21
|
+
value = data[key]
|
22
|
+
|
23
|
+
unless value.value == value.type.default_value
|
24
|
+
serialized_dirty_data[key.serialize.to_s] = value.serialize
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
clean_data = data.except(*transformed_keys)
|
29
|
+
clean_data.merge(serialized_dirty_data).deep_dup
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
return false unless other.is_a?(self.class)
|
34
|
+
|
35
|
+
other.key_type == key_type &&
|
36
|
+
other.value_type == value_type &&
|
37
|
+
other.serialize == serialize
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(initial_value = {}, key_type:, value_type:, on_change: nil)
|
41
|
+
self.key_type = key_type
|
42
|
+
self.value_type = value_type
|
43
|
+
|
44
|
+
self.data = initial_value
|
45
|
+
self.transformed_keys = Set.new
|
46
|
+
self.on_change = on_change
|
47
|
+
end
|
48
|
+
|
49
|
+
def [](key_var)
|
50
|
+
raw_key = key_var.is_a?(TypedVariable) ? key_var.value : key_var
|
51
|
+
string_key = raw_key.to_s
|
52
|
+
|
53
|
+
typed_key_var = TypedVariable.create_or_validate(key_type, key_var, on_change: on_change)
|
54
|
+
|
55
|
+
# First, attempt a lookup using the typed key
|
56
|
+
value = data[typed_key_var]
|
57
|
+
|
58
|
+
# If no value is found, try looking it up as a string (how it would be stored in JSONB)
|
59
|
+
if value.nil? && data.key?(string_key)
|
60
|
+
value = TypedVariable.create_or_validate(value_type, data[string_key], on_change: on_change)
|
61
|
+
|
62
|
+
data.delete(string_key)
|
63
|
+
set_value(typed_key_var, value)
|
64
|
+
end
|
65
|
+
|
66
|
+
# If the value is still nil, it truly doesn't exist; create a new default value
|
67
|
+
if value.nil?
|
68
|
+
value = TypedVariable.create_or_validate(value_type, on_change: on_change)
|
69
|
+
set_value(typed_key_var, value)
|
70
|
+
end
|
71
|
+
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
75
|
+
def []=(key_var, value)
|
76
|
+
key_var = TypedVariable.create_or_validate(key_type, key_var, on_change: on_change)
|
77
|
+
val_var = TypedVariable.create_or_validate(value_type, value, on_change: on_change)
|
78
|
+
|
79
|
+
if value_type.mapping?
|
80
|
+
raise TypeError, "Mappings cannot be assigned to mappings"
|
81
|
+
end
|
82
|
+
|
83
|
+
old_value = self.data[key_var]
|
84
|
+
|
85
|
+
if old_value != val_var
|
86
|
+
on_change&.call
|
87
|
+
|
88
|
+
transformed_keys.add(key_var)
|
89
|
+
|
90
|
+
data[key_var] ||= val_var
|
91
|
+
data[key_var].value = val_var.value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def set_value(key, value)
|
98
|
+
data[key] = value
|
99
|
+
transformed_keys.add(key)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
class Type
|
2
|
+
include ContractErrors
|
3
|
+
|
4
|
+
attr_accessor :name, :metadata, :key_type, :value_type, :initial_length
|
5
|
+
|
6
|
+
INTEGER_TYPES = (8..256).step(8).flat_map do |num|
|
7
|
+
["uint#{num}", "int#{num}"]
|
8
|
+
end.map(&:to_sym)
|
9
|
+
|
10
|
+
TYPES = [:string, :mapping, :address, :bytes32, :contract,
|
11
|
+
:bool, :address, :uint256, :int256, :array, :datetime, :bytes] + INTEGER_TYPES
|
12
|
+
|
13
|
+
TYPES.each do |type|
|
14
|
+
define_method("#{type}?") do
|
15
|
+
self.name == type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.value_types
|
20
|
+
TYPES.select do |type|
|
21
|
+
create(type).is_value_type?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(type_name, metadata = {})
|
26
|
+
if type_name.is_a?(Array)
|
27
|
+
if type_name.length != 1
|
28
|
+
raise "Invalid array type #{type_name.inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
value_type = type_name.first
|
32
|
+
|
33
|
+
if TYPES.exclude?(value_type)
|
34
|
+
raise "Invalid type #{value_type.inspect}"
|
35
|
+
end
|
36
|
+
|
37
|
+
metadata = { value_type: value_type }
|
38
|
+
type_name = :array
|
39
|
+
end
|
40
|
+
|
41
|
+
type_name = type_name.to_sym
|
42
|
+
|
43
|
+
if TYPES.exclude?(type_name)
|
44
|
+
raise "Invalid type #{type_name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
self.name = type_name.to_sym
|
48
|
+
self.metadata = metadata.deep_dup
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.create(type_or_name, metadata = {})
|
52
|
+
return type_or_name if type_or_name.is_a?(self)
|
53
|
+
|
54
|
+
new(type_or_name, metadata)
|
55
|
+
end
|
56
|
+
|
57
|
+
def key_type=(type)
|
58
|
+
return if type.nil?
|
59
|
+
@key_type = self.class.create(type)
|
60
|
+
end
|
61
|
+
|
62
|
+
def value_type=(type)
|
63
|
+
return if type.nil?
|
64
|
+
@value_type = self.class.create(type)
|
65
|
+
end
|
66
|
+
|
67
|
+
def metadata=(metadata)
|
68
|
+
self.key_type = metadata[:key_type]
|
69
|
+
self.value_type = metadata[:value_type]
|
70
|
+
self.initial_length = metadata[:initial_length] if metadata[:initial_length]
|
71
|
+
end
|
72
|
+
|
73
|
+
def can_be_assigned_from?(other_type)
|
74
|
+
return true if self == other_type
|
75
|
+
|
76
|
+
if is_uint? && other_type.is_uint? || is_int? && other_type.is_int?
|
77
|
+
return extract_integer_bits >= other_type.extract_integer_bits
|
78
|
+
end
|
79
|
+
|
80
|
+
if address? && other_type.contract?
|
81
|
+
return true
|
82
|
+
end
|
83
|
+
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
def values_can_be_compared?(other_type)
|
88
|
+
return true if can_be_assigned_from?(other_type)
|
89
|
+
|
90
|
+
if is_uint? && other_type.is_uint? || is_int? && other_type.is_int?
|
91
|
+
return true
|
92
|
+
end
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
def metadata
|
97
|
+
(@metadata ||= {}).with_indifferent_access
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_s
|
101
|
+
name.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
def default_value
|
105
|
+
is_int256_uint256_datetime = is_int? || is_uint? || datetime?
|
106
|
+
|
107
|
+
val = case
|
108
|
+
when is_int256_uint256_datetime
|
109
|
+
0
|
110
|
+
when address?
|
111
|
+
"0x" + "0" * 40
|
112
|
+
when bytes32?
|
113
|
+
"0x" + "0" * 64
|
114
|
+
when string? || bytes?
|
115
|
+
''
|
116
|
+
when bool?
|
117
|
+
false
|
118
|
+
when mapping?
|
119
|
+
MappingType::Proxy.new(key_type: key_type, value_type: value_type)
|
120
|
+
when array?
|
121
|
+
ArrayType::Proxy.new(value_type: value_type, initial_length: initial_length)
|
122
|
+
when contract?
|
123
|
+
ContractType::Proxy.new(contract_interface: metadata[:interface], address: nil, contract_type: nil)
|
124
|
+
else
|
125
|
+
raise "Unknown default value for #{self.inspect}"
|
126
|
+
end
|
127
|
+
|
128
|
+
check_and_normalize_literal(val)
|
129
|
+
end
|
130
|
+
|
131
|
+
def raise_variable_type_error(literal)
|
132
|
+
raise VariableTypeError.new("Invalid #{self}: #{literal.inspect}")
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse_integer(literal)
|
136
|
+
base = literal.start_with?("0x") ? 16 : 10
|
137
|
+
|
138
|
+
begin
|
139
|
+
Integer(literal, base)
|
140
|
+
rescue ArgumentError => e
|
141
|
+
raise_variable_type_error(literal)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def check_and_normalize_literal(literal)
|
146
|
+
if literal.is_a?(TypedVariable)
|
147
|
+
raise VariableTypeError, "Only literals can be passed to check_and_normalize_literal: #{literal.inspect}"
|
148
|
+
end
|
149
|
+
|
150
|
+
if address?
|
151
|
+
if literal.is_a?(ContractType::Proxy)
|
152
|
+
return literal.address
|
153
|
+
end
|
154
|
+
|
155
|
+
unless literal.is_a?(String) && literal.match?(/\A0x[a-f0-9]{40}\z/i)
|
156
|
+
raise_variable_type_error(literal)
|
157
|
+
end
|
158
|
+
|
159
|
+
return literal.downcase
|
160
|
+
elsif is_uint?
|
161
|
+
if literal.is_a?(String)
|
162
|
+
literal = parse_integer(literal)
|
163
|
+
end
|
164
|
+
|
165
|
+
if literal.is_a?(Integer) && literal.between?(0, 2 ** extract_integer_bits - 1)
|
166
|
+
return literal
|
167
|
+
end
|
168
|
+
|
169
|
+
raise_variable_type_error(literal)
|
170
|
+
elsif is_int?
|
171
|
+
if literal.is_a?(String)
|
172
|
+
literal = parse_integer(literal)
|
173
|
+
end
|
174
|
+
|
175
|
+
if literal.is_a?(Integer) && literal.between?(-2 ** (extract_integer_bits - 1), 2 ** (extract_integer_bits - 1) - 1)
|
176
|
+
return literal
|
177
|
+
end
|
178
|
+
|
179
|
+
raise_variable_type_error(literal)
|
180
|
+
elsif string?
|
181
|
+
unless literal.is_a?(String)
|
182
|
+
raise_variable_type_error(literal)
|
183
|
+
end
|
184
|
+
|
185
|
+
return literal.freeze
|
186
|
+
elsif bool?
|
187
|
+
unless literal == true || literal == false
|
188
|
+
raise_variable_type_error(literal)
|
189
|
+
end
|
190
|
+
|
191
|
+
return literal
|
192
|
+
elsif bytes32?
|
193
|
+
unless literal.is_a?(String) && literal.match?(/\A0x[a-f0-9]{64}\z/i)
|
194
|
+
raise_variable_type_error(literal)
|
195
|
+
end
|
196
|
+
|
197
|
+
return literal.downcase
|
198
|
+
elsif bytes?
|
199
|
+
if literal.is_a?(String) && literal.length == 0
|
200
|
+
return literal
|
201
|
+
end
|
202
|
+
|
203
|
+
unless literal.is_a?(String) && literal.match?(/\A0x[a-fA-F0-9]*\z/) && literal.size.even?
|
204
|
+
raise_variable_type_error(literal)
|
205
|
+
end
|
206
|
+
|
207
|
+
return literal.downcase
|
208
|
+
elsif datetime?
|
209
|
+
dummy_uint = Type.create(:uint256)
|
210
|
+
|
211
|
+
begin
|
212
|
+
return dummy_uint.check_and_normalize_literal(literal)
|
213
|
+
rescue VariableTypeError => e
|
214
|
+
raise_variable_type_error(literal)
|
215
|
+
end
|
216
|
+
elsif mapping?
|
217
|
+
if literal.is_a?(MappingType::Proxy)
|
218
|
+
return literal
|
219
|
+
end
|
220
|
+
|
221
|
+
unless literal.is_a?(Hash)
|
222
|
+
raise VariableTypeError.new("invalid #{literal}")
|
223
|
+
end
|
224
|
+
|
225
|
+
proxy = MappingType::Proxy.new(literal, key_type: key_type, value_type: value_type)
|
226
|
+
|
227
|
+
return proxy
|
228
|
+
elsif array?
|
229
|
+
if literal.is_a?(ArrayType::Proxy)
|
230
|
+
return literal
|
231
|
+
end
|
232
|
+
|
233
|
+
unless literal.is_a?(Array)
|
234
|
+
raise_variable_type_error(literal)
|
235
|
+
end
|
236
|
+
|
237
|
+
data = literal.map do |value|
|
238
|
+
TypedVariable.create(value_type, value)
|
239
|
+
end
|
240
|
+
|
241
|
+
proxy = ArrayType::Proxy.new(data, value_type: value_type, initial_length: initial_length)
|
242
|
+
|
243
|
+
return proxy
|
244
|
+
elsif contract?
|
245
|
+
if literal.is_a?(ContractType::Proxy)
|
246
|
+
return literal
|
247
|
+
else
|
248
|
+
raise_variable_type_error("No literals allowed for contract types")
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
raise VariableTypeError.new("Unknown type #{self.inspect}: #{literal.inspect}")
|
253
|
+
end
|
254
|
+
|
255
|
+
def is_uint?
|
256
|
+
name.to_s.start_with?('uint')
|
257
|
+
end
|
258
|
+
|
259
|
+
def is_int?
|
260
|
+
name.to_s.start_with?('int')
|
261
|
+
end
|
262
|
+
|
263
|
+
def extract_integer_bits
|
264
|
+
return name.to_s[4..-1].to_i if is_uint?
|
265
|
+
return name.to_s[3..-1].to_i if is_int?
|
266
|
+
raise "Not an integer type: #{self}"
|
267
|
+
end
|
268
|
+
|
269
|
+
def ==(other)
|
270
|
+
other.is_a?(self.class) &&
|
271
|
+
other.name == name &&
|
272
|
+
other.metadata.except(:initial_length) == metadata.except(:initial_length)
|
273
|
+
end
|
274
|
+
|
275
|
+
def !=(other)
|
276
|
+
!(self == other)
|
277
|
+
end
|
278
|
+
|
279
|
+
def hash
|
280
|
+
[name, metadata].hash
|
281
|
+
end
|
282
|
+
|
283
|
+
def eql?(other)
|
284
|
+
hash == other.hash
|
285
|
+
end
|
286
|
+
|
287
|
+
def is_value_type?
|
288
|
+
!mapping? && !array?
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
class TypedVariable
|
2
|
+
include ContractErrors
|
3
|
+
extend AttrPublicReadPrivateWrite
|
4
|
+
|
5
|
+
attr_accessor :value, :on_change
|
6
|
+
attr_public_read_private_write :type
|
7
|
+
|
8
|
+
def initialize(type, value = nil, on_change: nil, **options)
|
9
|
+
self.type = type
|
10
|
+
self.value = value || type.default_value
|
11
|
+
self.on_change = on_change
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create(type, value = nil, on_change: nil, **options)
|
15
|
+
type = Type.create(type)
|
16
|
+
|
17
|
+
options[:on_change] = on_change
|
18
|
+
|
19
|
+
if type.mapping?
|
20
|
+
MappingType.new(type, value, **options)
|
21
|
+
elsif type.array?
|
22
|
+
ArrayType.new(type, value, **options)
|
23
|
+
elsif type.contract?
|
24
|
+
ContractType.new(type, value, **options)
|
25
|
+
else
|
26
|
+
new(type, value, **options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.create_or_validate(type, value = nil, on_change: nil)
|
31
|
+
if value.is_a?(TypedVariable)
|
32
|
+
unless Type.create(type).can_be_assigned_from?(value.type)
|
33
|
+
raise VariableTypeError.new("invalid #{type}: #{value.inspect}")
|
34
|
+
end
|
35
|
+
|
36
|
+
value = value.value
|
37
|
+
end
|
38
|
+
|
39
|
+
create(type, value, on_change: on_change)
|
40
|
+
end
|
41
|
+
|
42
|
+
def as_json(args = {})
|
43
|
+
serialize
|
44
|
+
end
|
45
|
+
|
46
|
+
def serialize
|
47
|
+
value
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
if value.is_a?(String) || value.is_a?(Integer)
|
52
|
+
value.to_s
|
53
|
+
else
|
54
|
+
raise "No string conversion"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def deserialize(serialized_value)
|
59
|
+
self.value = serialized_value
|
60
|
+
end
|
61
|
+
|
62
|
+
def value=(new_value)
|
63
|
+
new_value = type.check_and_normalize_literal(new_value)
|
64
|
+
|
65
|
+
if @value != new_value
|
66
|
+
on_change&.call
|
67
|
+
|
68
|
+
if new_value.respond_to?(:on_change=)
|
69
|
+
new_value.on_change = on_change
|
70
|
+
end
|
71
|
+
|
72
|
+
@value = new_value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def method_missing(name, *args, &block)
|
77
|
+
if value.respond_to?(name)
|
78
|
+
result = value.send(name, *args, &block)
|
79
|
+
|
80
|
+
if result.class == value.class
|
81
|
+
begin
|
82
|
+
result = type.check_and_normalize_literal(result)
|
83
|
+
rescue ContractErrors::VariableTypeError => e
|
84
|
+
if type.is_uint?
|
85
|
+
result = TypedVariable.create(:uint256, result)
|
86
|
+
else
|
87
|
+
raise
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
result
|
93
|
+
|
94
|
+
if name.to_s.end_with?("=") && !%w[>= <=].include?(name.to_s[-2..])
|
95
|
+
self.value = result if type.is_value_type?
|
96
|
+
self
|
97
|
+
else
|
98
|
+
result
|
99
|
+
end
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def respond_to_missing?(name, include_private = false)
|
106
|
+
value.respond_to?(name, include_private) || super
|
107
|
+
end
|
108
|
+
|
109
|
+
def !=(other)
|
110
|
+
!(self == other)
|
111
|
+
end
|
112
|
+
|
113
|
+
def ==(other)
|
114
|
+
if other.is_a?(self.class)
|
115
|
+
return false unless type.values_can_be_compared?(other.type)
|
116
|
+
return value == other.value
|
117
|
+
else
|
118
|
+
return value == TypedVariable.create(type, other).value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def hash
|
123
|
+
[value, type].hash
|
124
|
+
end
|
125
|
+
|
126
|
+
def eql?(other)
|
127
|
+
hash == other.hash
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
|
5
|
+
|
6
|
+
## our own code
|
7
|
+
require_relative 'typed/contract_errors' ## move upstream to 0xfacet !!!
|
8
|
+
|
9
|
+
require_relative 'typed/attr_public_read_private_write'
|
10
|
+
require_relative 'typed/type'
|
11
|
+
require_relative 'typed/typed_variable'
|
12
|
+
require_relative 'typed/array_type'
|
13
|
+
require_relative 'typed/mapping_type'
|
14
|
+
require_relative 'typed/contract_type'
|
15
|
+
|
16
|
+
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: 0xfacet-typed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
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: rdoc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: hoe
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '4.0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '4.0'
|
47
|
+
description: 0xfacet-typed - solidity-like value and reference types for rubidity
|
48
|
+
o.g.
|
49
|
+
email: gerald.bauer@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files:
|
53
|
+
- CHANGELOG.md
|
54
|
+
- Manifest.txt
|
55
|
+
- README.md
|
56
|
+
files:
|
57
|
+
- CHANGELOG.md
|
58
|
+
- Manifest.txt
|
59
|
+
- README.md
|
60
|
+
- Rakefile
|
61
|
+
- lib/0xfacet/typed.rb
|
62
|
+
- lib/0xfacet/typed/array_type.rb
|
63
|
+
- lib/0xfacet/typed/attr_public_read_private_write.rb
|
64
|
+
- lib/0xfacet/typed/contract_errors.rb
|
65
|
+
- lib/0xfacet/typed/contract_type.rb
|
66
|
+
- lib/0xfacet/typed/mapping_type.rb
|
67
|
+
- lib/0xfacet/typed/type.rb
|
68
|
+
- lib/0xfacet/typed/typed_variable.rb
|
69
|
+
homepage: https://github.com/s6ruby/rubidity.review
|
70
|
+
licenses:
|
71
|
+
- Public Domain
|
72
|
+
metadata: {}
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options:
|
75
|
+
- "--main"
|
76
|
+
- README.md
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.3'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubygems_version: 3.4.10
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: 0xfacet-typed - solidity-like value and reference types for rubidity o.g.
|
94
|
+
test_files: []
|