File Coverage

blib/lib/Blockchain/Ethereum/ABI/Type.pm
Criterion Covered Total %
statement 112 114 98.2
branch 33 34 97.0
condition 8 8 100.0
subroutine 26 28 92.8
pod 6 8 75.0
total 185 192 96.3


line stmt bran cond sub pod time code
1             package Blockchain::Ethereum::ABI::Type;
2              
3 5     5   60 use v5.26;
  5         18  
4 5     5   31 use strict;
  5         18  
  5         106  
5 5     5   25 use warnings;
  5         13  
  5         138  
6              
7 5     5   41 use Carp;
  5         13  
  5         295  
8 5     5   2369 use Module::Load;
  5         6239  
  5         40  
9 5     5   297 use constant NOT_IMPLEMENTED => 'Method not implemented';
  5         10  
  5         7705  
10              
11             sub new {
12 169     169 0 572 my ($class, %params) = @_;
13              
14 169         393 my $self = bless {}, $class;
15 169         490 $self->{signature} = $params{signature};
16 169         315 $self->{data} = $params{data};
17              
18 169         508 $self->_configure;
19              
20 169         775 return $self;
21             }
22              
23             # to be implemented by the child classes that need it
24       116     sub _configure { }
25              
26             sub encode {
27 0     0 1 0 croak NOT_IMPLEMENTED;
28             }
29              
30             sub decode {
31 0     0 1 0 croak NOT_IMPLEMENTED;
32             }
33              
34             sub _static {
35 663   100 663   2068 return shift->{static} //= [];
36             }
37              
38             sub _push_static {
39 122     122   230 my ($self, $data) = @_;
40 122 100       212 push($self->_static->@*, ref $data eq 'ARRAY' ? $data->@* : $data);
41             }
42              
43             sub _dynamic {
44 607   100 607   1806 return shift->{dynamic} //= [];
45             }
46              
47             sub _push_dynamic {
48 66     66   126 my ($self, $data) = @_;
49 66 100       115 push($self->_dynamic->@*, ref $data eq 'ARRAY' ? $data->@* : $data);
50             }
51              
52             sub _signature {
53 715     715   4333 return shift->{signature};
54             }
55              
56             sub _data {
57 234     234   1345 return shift->{data};
58             }
59              
60             sub fixed_length {
61 42     42 1 66 my $self = shift;
62 42 100       85 if ($self->_signature =~ /[a-z](\d+)/) {
63 28         119 return $1;
64             }
65 14         55 return undef;
66             }
67              
68             sub pad_right {
69 16     16 1 40 my ($self, $data) = @_;
70              
71 16         37 my @chunks;
72 16         117 push(@chunks, $_ . '0' x (64 - length $_)) for unpack("(A64)*", $data);
73              
74 16         93 return \@chunks;
75             }
76              
77             sub pad_left {
78 42     42 1 3456 my ($self, $data) = @_;
79              
80 42         70 my @chunks;
81 42         256 push(@chunks, sprintf("%064s", $_)) for unpack("(A64)*", $data);
82              
83 42         200 return \@chunks;
84              
85             }
86              
87             sub _encode_length {
88 23     23   52 my ($self, $length) = @_;
89 23         140 return sprintf("%064s", sprintf("%x", $length));
90             }
91              
92             sub _encode_offset {
93 25     25   57 my ($self, $offset) = @_;
94 25         123 return sprintf("%064s", sprintf("%x", $offset * 32));
95             }
96              
97             sub _encoded {
98 541     541   807 my $self = shift;
99 541         898 my @data = ($self->_static->@*, $self->_dynamic->@*);
100 541 100       1963 return scalar @data ? \@data : undef;
101             }
102              
103             sub is_dynamic {
104 359 100   359 0 629 return shift->_signature =~ /(bytes|string)(?!\d+)|(\[\])/ ? 1 : 0;
105             }
106              
107             sub new_type {
108 144     144 1 438 my ($self, %params) = @_;
109              
110 144         260 my $signature = $params{signature};
111              
112 144         223 my $module;
113 144 100       1058 if ($signature =~ /\[(\d+)?\]$/gm) {
    100          
    100          
    100          
    100          
    100          
114 20         49 $module = "Array";
115             } elsif ($signature =~ /^\(.*\)/) {
116 12         24 $module = "Tuple";
117             } elsif ($signature =~ /^address$/) {
118 22         43 $module = "Address";
119             } elsif ($signature =~ /^(u)?(int|bool)(\d+)?$/) {
120 63         118 $module = "Int";
121             } elsif ($signature =~ /^(?:bytes)(\d+)?$/) {
122 21         45 $module = "Bytes";
123             } elsif ($signature =~ /^string$/) {
124 5         12 $module = "String";
125             } else {
126 1         94 croak "Module not found for the given parameter signature $signature";
127             }
128              
129             # this is just to avoid `use module` for every new type included
130 143         233 my $_package = __PACKAGE__;
131 143         534 my $package = sprintf("%s::%s", $_package, $module);
132 143         449 load $package;
133             return $package->new(
134             signature => $signature,
135 143         7844 data => $params{data});
136             }
137              
138             sub _instances {
139 391   100 391   8215 return shift->{instances} //= [];
140             }
141              
142             # get the first index where data is set to the encoded value
143             # skipping the prefixed indexes
144             sub _get_initial_offset {
145 38     38   75 my $self = shift;
146 38         58 my $offset = 0;
147 38         80 for my $param ($self->_instances->@*) {
148 88         277 my $encoded = $param->encode;
149 82 100       236 if ($param->is_dynamic) {
150 25         69 $offset += 1;
151             } else {
152 57         112 $offset += scalar $param->_encoded->@*;
153             }
154             }
155              
156 32         98 return $offset;
157             }
158              
159             sub _static_size {
160 66     66   128 return 1;
161             }
162              
163             # read the data at the encoded stack
164             sub _read_stack_set_data {
165 14     14   27 my $self = shift;
166              
167 14         31 my @data = $self->_data->@*;
168 14         21 my @offsets;
169 14         23 my $current_offset = 0;
170              
171             # Since at this point we don't information about the chunks of data it is_dynamic
172             # needed to get all the offsets in the static header, so the dynamic values can
173             # be retrieved based in between the current and the next offsets
174 14         38 for my $instance ($self->_instances->@*) {
175 39 100       82 if ($instance->is_dynamic) {
176 8         31 push @offsets, hex($data[$current_offset]) / 32;
177             }
178              
179 39         58 my $size = 1;
180 39 100       67 $size = $instance->_static_size unless $instance->is_dynamic;
181 39         74 $current_offset += $size;
182             }
183              
184 14         24 $current_offset = 0;
185 14         19 my %response;
186             # Dynamic data must to be set first since the full_size method
187             # will need to use the data offset related to the size of the item
188 14         37 for (my $i = 0; $i < $self->_instances->@*; $i++) {
189 39         68 my $instance = $self->_instances->[$i];
190 39 100       81 next unless $instance->is_dynamic;
191 8         30 my $offset_start = shift @offsets;
192 8   100     30 my $offset_end = $offsets[0] // scalar @data - 1;
193 8         71 my @range = @data[$offset_start .. $offset_end];
194 8         23 $instance->{data} = \@range;
195 8         14 $current_offset += scalar @range;
196 8         30 $response{$i} = $instance->decode();
197             }
198              
199 14         22 $current_offset = 0;
200              
201 14         39 for (my $i = 0; $i < $self->_instances->@*; $i++) {
202 39         75 my $instance = $self->_instances->[$i];
203              
204 39 100       78 if ($instance->is_dynamic) {
205 8         14 $current_offset++;
206 8         19 next;
207             }
208              
209 31         50 my $size = 1;
210 31 50       54 $size = $instance->_static_size unless $instance->is_dynamic;
211 31         99 my @range = @data[$current_offset .. $current_offset + $size - 1];
212 31         63 $instance->{data} = \@range;
213 31         54 $current_offset += $size;
214              
215 31         106 $response{$i} = $instance->decode();
216             }
217              
218 14         25 my @array_response;
219             # the given order of type signatures needs to be strict followed
220 14         81 push(@array_response, $response{$_}) for 0 .. scalar $self->_instances->@* - 1;
221 14         85 return \@array_response;
222             }
223              
224             =pod
225              
226             =encoding UTF-8
227              
228             =head1 NAME
229              
230             Blockchain::Ethereum::ABI::Type - Interface for solidity variable types
231              
232             =head1 SYNOPSIS
233              
234             Allows you to define and instantiate a solidity variable type:
235              
236             my $type = Blockchain::Ethereum::ABI::Type->new;
237             $type->new_type(
238             signature => $signature,
239             data => $value
240             );
241              
242             $type->encode();
243             ...
244              
245             In most cases you don't want to use this directly, use instead:
246              
247             =over 4
248              
249             =item * B: L
250              
251             =item * B: L
252              
253             =back
254              
255             =head1 METHODS
256              
257             =head2 new_type
258              
259             Create a new L instance based
260             in the given signature.
261              
262             Usage:
263              
264             new_type(signature => signature, data => value) -> L
265              
266             =over 4
267              
268             =item * C<%params> signature and data key values
269              
270             =back
271              
272             Returns an new instance of one of the sub modules for L
273              
274             =head2 encode
275              
276             Encodes the given data to the type of the signature
277              
278             Usage:
279              
280             encode() -> encoded string
281              
282             =over 4
283              
284             =back
285              
286             =head2 decode
287              
288             Decodes the given data to the type of the signature
289              
290             Usage:
291              
292             decoded() -> check the child classes for return type
293              
294             =over 4
295              
296             =back
297              
298             check the child classes for return type
299              
300             =head2 fixed_length
301              
302             Check if that is a length specified for the given signature
303              
304             Usage:
305              
306             fixed_length() -> integer length or undef
307              
308             =over 4
309              
310             =back
311              
312             Integer length or undef in case of no length specified
313              
314             =head2 pad_right
315              
316             Pads the given data to right 32 bytes with zeros
317              
318             Usage:
319              
320             pad_right("1") -> "100000000000..0"
321              
322             =over 4
323              
324             =item * C<$data> data to be padded
325              
326             =back
327              
328             Returns the padded string
329              
330             =head2 pad_left
331              
332             Pads the given data to left 32 bytes with zeros
333              
334             Usage:
335              
336             pad_left("1") -> "0000000000..1"
337              
338             =over 4
339              
340             =item * C<$data> data to be padded
341              
342             =back
343              
344             Returns the padded string
345              
346             =head1 AUTHOR
347              
348             Reginaldo Costa, C<< >>
349              
350             =head1 BUGS
351              
352             Please report any bugs or feature requests to L
353              
354             =head1 SUPPORT
355              
356             You can find documentation for this module with the perldoc command.
357              
358             perldoc Blockchain::Ethereum::ABI::Type
359              
360             =head1 LICENSE AND COPYRIGHT
361              
362             This software is Copyright (c) 2022 by REFECO.
363              
364             This is free software, licensed under:
365              
366             The MIT License
367              
368             =cut
369              
370             1;