File Coverage

blib/lib/Blockchain/Ethereum/ABI/Type.pm
Criterion Covered Total %
statement 114 116 98.2
branch 21 22 95.4
condition 2 2 100.0
subroutine 23 24 95.8
pod 5 9 55.5
total 165 173 95.3


line stmt bran cond sub pod time code
1 7     7   103228 use v5.26;
  7         35  
2 7     7   668 use Object::Pad ':experimental(init_expr)';
  7         11074  
  7         41  
3              
4             package Blockchain::Ethereum::ABI::Type 0.012;
5             class Blockchain::Ethereum::ABI::Type;
6              
7             =encoding utf8
8              
9             =head1 NAME
10              
11             Blockchain::Ethereum::ABI::Type - Interface for solidity variable types
12              
13             =head1 SYNOPSIS
14              
15             Allows you to define and instantiate a solidity variable type:
16              
17             my $type = Blockchain::Ethereum::ABI::Type->new(
18             signature => $signature,
19             data => $value
20             );
21              
22             $type->encode();
23              
24             In most cases you don't want to use this directly, use instead:
25              
26             =over 4
27              
28             =item * B: L
29              
30             =item * B: L
31              
32             =back
33              
34             =cut
35              
36 7     7   2665 use Carp;
  7         18  
  7         489  
37 7     7   3778 use Module::Load;
  7         8852  
  7         42  
38              
39             field $signature :reader :writer :param = undef;
40 0     0 0 0 field $data :reader :writer :param = undef;
  0     1388 0 0  
  1388         2815  
  1388         6347  
41              
42 663     663 1 1020 field $_static :reader(_static) = [];
  251     251 0 424  
  251     46   1515  
  46         90  
  46         97  
43 663     607   1587 field $_dynamic :reader(_dynamic) = [];
  607         972  
44 607     391   1395 field $_instances :reader(_instances) :writer(set_instances) = [];
  391         7149  
45              
46 391     21 0 1036 ADJUST {
  21         45  
  21         59  
47             if ($self->signature) {
48             my $module;
49             if ($self->signature =~ /\[(\d+)?\]$/gm) {
50             $module = "Array";
51             } elsif ($self->signature =~ /^\(.*\)/) {
52             $module = "Tuple";
53             } elsif ($self->signature =~ /^address$/) {
54             $module = "Address";
55             } elsif ($self->signature =~ /^(u)?(int|bool)(\d+)?$/) {
56             $module = "Int";
57             } elsif ($self->signature =~ /^(?:bytes)(\d+)?$/) {
58             $module = "Bytes";
59             } elsif ($self->signature =~ /^string$/) {
60             $module = "String";
61             } else {
62             croak "Module not found for the given parameter signature $signature";
63             }
64              
65             # this is just to avoid `use module` for every new type included
66             my $package = "Blockchain::Ethereum::ABI::Type::$module";
67             load $package;
68              
69             $self = bless $self, $package;
70             $self->_configure;
71             }
72             }
73              
74 122     122   223 method _push_static ($data) {
  122         168  
  122         175  
  122         161  
75              
76 122 100       221 push($self->_static->@*, ref $data eq 'ARRAY' ? $data->@* : $data);
77             }
78              
79 66     66   123 method _push_dynamic ($data) {
  66         89  
  66         102  
  66         87  
80              
81 66 100       106 push($self->_dynamic->@*, ref $data eq 'ARRAY' ? $data->@* : $data);
82             }
83              
84             =head2 pad_right
85              
86             Pads the given data to right 32 bytes with zeros
87              
88             Usage:
89              
90             pad_right("1") -> "100000000000..0"
91              
92             =over 4
93              
94             =item * C<$data> data to be padded
95              
96             =back
97              
98             Returns the padded string
99              
100             =cut
101              
102 16     16 1 45 method pad_right ($data) {
  16         28  
  16         27  
  16         24  
103              
104 16         23 my @chunks;
105 16         116 push(@chunks, $_ . '0' x (64 - length $_)) for unpack("(A64)*", $data);
106              
107 16         59 return \@chunks;
108             }
109              
110             =head2 pad_left
111              
112             Pads the given data to left 32 bytes with zeros
113              
114             Usage:
115              
116             pad_left("1") -> "0000000000..1"
117              
118             =over 4
119              
120             =item * C<$data> data to be padded
121              
122             =back
123              
124             =cut
125              
126 42     42 1 3325 method pad_left ($data) {
  42         63  
  42         79  
  42         61  
127              
128 42         63 my @chunks;
129 42         266 push(@chunks, sprintf("%064s", $_)) for unpack("(A64)*", $data);
130              
131 42         176 return \@chunks;
132              
133             }
134              
135 23     23   46 method _encode_length ($length) {
  23         37  
  23         38  
  23         35  
136              
137 23         162 return sprintf("%064s", sprintf("%x", $length));
138             }
139              
140 25     25   65 method _encode_offset ($offset) {
  25         42  
  25         36  
  25         39  
141              
142 25         141 return sprintf("%064s", sprintf("%x", $offset * 32));
143             }
144              
145 541     541   929 method _encoded {
146              
147 541         904 my @data = ($self->_static->@*, $self->_dynamic->@*);
148 541 100       1807 return scalar @data ? \@data : undef;
149             }
150              
151             =head2 is_dynamic
152              
153             Checks if the type signature is dynamic
154              
155             Usage:
156              
157             is_dynamic() -> 1/0
158              
159             =over 4
160              
161             =back
162              
163             Returns 1 for dynamic and 0 for static
164              
165             =cut
166              
167 359     359 1 601 method is_dynamic {
168              
169 359 100       597 return $self->signature =~ /(bytes|string)(?!\d+)|(\[\])/ ? 1 : 0;
170             }
171              
172             # get the first index where data is set to the encoded value
173             # skipping the prefixed indexes
174 38     38   77 method _get_initial_offset {
175              
176 38         64 my $offset = 0;
177 38         79 for my $param ($self->_instances->@*) {
178 88         262 my $encoded = $param->encode;
179 82 100       233 if ($param->is_dynamic) {
180 25         67 $offset += 1;
181             } else {
182 57         135 $offset += scalar $param->_encoded->@*;
183             }
184             }
185              
186 32         81 return $offset;
187             }
188              
189             =head2 fixed_length
190              
191             Check if that is a length specified for the given signature
192              
193             Usage:
194              
195             fixed_length() -> integer length or undef
196              
197             =over 4
198              
199             =back
200              
201             Integer length or undef in case of no length specified
202              
203             =cut
204              
205 42     42 1 77 method fixed_length {
206              
207 42 100       84 if ($self->signature =~ /[a-z](\d+)/) {
208 28         124 return $1;
209             }
210 14         44 return undef;
211             }
212              
213 66     66   123 method _static_size {
214              
215 66         130 return 1;
216             }
217              
218             # read the data at the encoded stack
219 14     14   31 method _read_stack_set_data {
220              
221 14         37 my @data = $self->data->@*;
222 14         28 my @offsets;
223 14         31 my $current_offset = 0;
224              
225             # Since at this point we don't information about the chunks of data it is_dynamic
226             # needed to get all the offsets in the static header, so the dynamic values can
227             # be retrieved based in between the current and the next offsets
228 14         29 for my $instance ($self->_instances->@*) {
229 39 100       88 if ($instance->is_dynamic) {
230 8         29 push @offsets, hex($data[$current_offset]) / 32;
231             }
232              
233 39         63 my $size = 1;
234 39 100       75 $size = $instance->_static_size unless $instance->is_dynamic;
235 39         74 $current_offset += $size;
236             }
237              
238 14         22 $current_offset = 0;
239 14         23 my %response;
240             # Dynamic data must to be set first since the full_size method
241             # will need to use the data offset related to the size of the item
242 14         29 for (my $i = 0; $i < $self->_instances->@*; $i++) {
243 39         65 my $instance = $self->_instances->[$i];
244 39 100       72 next unless $instance->is_dynamic;
245 8         20 my $offset_start = shift @offsets;
246 8   100     32 my $offset_end = $offsets[0] // scalar @data - 1;
247 8         38 my @range = @data[$offset_start .. $offset_end];
248 8         30 $instance->set_data(\@range);
249 8         15 $current_offset += scalar @range;
250 8         25 $response{$i} = $instance->decode();
251             }
252              
253 14         27 $current_offset = 0;
254              
255 14         29 for (my $i = 0; $i < $self->_instances->@*; $i++) {
256 39         75 my $instance = $self->_instances->[$i];
257              
258 39 100       78 if ($instance->is_dynamic) {
259 8         22 $current_offset++;
260 8         19 next;
261             }
262              
263 31         53 my $size = 1;
264 31 50       59 $size = $instance->_static_size unless $instance->is_dynamic;
265 31         103 my @range = @data[$current_offset .. $current_offset + $size - 1];
266 31         121 $instance->set_data(\@range);
267 31         49 $current_offset += $size;
268              
269 31         77 $response{$i} = $instance->decode();
270             }
271              
272 14         22 my @array_response;
273             # the given order of type signatures needs to be strict followed
274 14         28 push(@array_response, $response{$_}) for 0 .. scalar $self->_instances->@* - 1;
275 14         74 return \@array_response;
276             }
277              
278             1;
279              
280             __END__