File Coverage

blib/lib/Blockchain/Ethereum/ABI/Type.pm
Criterion Covered Total %
statement 104 107 97.2
branch 35 36 97.2
condition 2 2 100.0
subroutine 18 21 85.7
pod 4 7 57.1
total 163 173 94.2


line stmt bran cond sub pod time code
1             package Blockchain::Ethereum::ABI::Type;
2              
3 18     18   367954 use v5.26;
  18         90  
4 18     18   93 use strict;
  18         31  
  18         493  
5 18     18   91 use warnings;
  18         34  
  18         1539  
6              
7             # ABSTRACT: Type interface
8             our $AUTHORITY = 'cpan:REFECO'; # AUTHORITY
9             our $VERSION = '0.021'; # VERSION
10              
11 18     18   133 use Carp;
  18         48  
  18         1415  
12 18     18   9380 use Module::Load;
  18         30741  
  18         125  
13              
14             sub new {
15 211     211 0 215445 my ($class, %params) = @_;
16              
17 211         370 my $signature = $params{signature};
18 211         313 my $data = $params{data};
19              
20 211         812 my $self = {
21             signature => $signature,
22             data => $data,
23             static => [],
24             dynamic => [],
25             instances => [],
26             };
27              
28 211 100       467 if ($signature) {
29 185         246 my $module;
30 185 100       1220 if ($signature =~ /\[(\d+)?\]$/gm) {
    100          
    100          
    100          
    100          
    100          
31 22         46 $module = "Array";
32             } elsif ($signature =~ /^\(.*\)/) {
33 12         21 $module = "Tuple";
34             } elsif ($signature =~ /^address$/) {
35 58         76 $module = "Address";
36             } elsif ($signature =~ /^(u)?(int|bool)(\d+)?$/) {
37 66         154 $module = "Int";
38             } elsif ($signature =~ /^(?:bytes)(\d+)?$/) {
39 21         37 $module = "Bytes";
40             } elsif ($signature =~ /^string$/) {
41 5         12 $module = "String";
42             } else {
43 1         184 croak "Module not found for the given parameter signature $signature";
44             }
45              
46             # this is just to avoid `use module` for every new type included
47 184         305 my $package = "Blockchain::Ethereum::ABI::Type::$module";
48 184         514 load $package;
49              
50 184         8331 $self = bless $self, $package;
51 184         489 $self->_configure;
52             } else {
53 26         128 $self = bless $self, $class;
54             }
55              
56 210         802 return $self;
57             }
58              
59             sub _configure {
60 0     0   0 croak 'method _configure not implemented';
61             }
62              
63             sub encode {
64 0     0 0 0 croak 'method encode not implemented';
65             }
66              
67             sub decode {
68 0     0 0 0 croak 'method decode not implemented';
69             }
70              
71             sub _push_static {
72 144     144   251 my ($self, $data) = @_;
73              
74 144 100       486 push($self->{static}->@*, ref $data eq 'ARRAY' ? $data->@* : $data);
75             }
76              
77             sub _push_dynamic {
78 85     85   173 my ($self, $data) = @_;
79              
80 85 100       285 push($self->{dynamic}->@*, ref $data eq 'ARRAY' ? $data->@* : $data);
81             }
82              
83             sub pad_right {
84 16     16 1 51 my ($self, $data) = @_;
85              
86 16         30 my @chunks;
87 16         130 push(@chunks, $_ . '0' x (64 - length $_)) for unpack("(A64)*", $data);
88              
89 16         78 return \@chunks;
90             }
91              
92             sub pad_left {
93 61     61 1 4644 my ($self, $data) = @_;
94              
95 61         89 my @chunks;
96 61         336 push(@chunks, sprintf("%064s", $_)) for unpack("(A64)*", $data);
97              
98 61         195 return \@chunks;
99              
100             }
101              
102             sub _encode_length {
103 24     24   61 my ($self, $length) = @_;
104              
105 24         164 return sprintf("%064s", sprintf("%x", $length));
106             }
107              
108             sub _encode_offset {
109 26     26   122 my ($self, $offset) = @_;
110              
111 26         144 return sprintf("%064s", sprintf("%x", $offset * 32));
112             }
113              
114             sub _encoded {
115 705     705   1110 my ($self, $offset) = @_;
116              
117 705         1572 my @data = ($self->{static}->@*, $self->{dynamic}->@*);
118 705 100       2113 return scalar @data ? \@data : undef;
119             }
120              
121             sub is_dynamic {
122 498     498 1 617 my $self = shift;
123              
124 498 100       2276 return $self->{signature} =~ /(bytes|string)(?!\d+)|(\[\])/ ? 1 : 0;
125             }
126              
127             # get the first index where data is set to the encoded value
128             # skipping the prefixed indexes
129             sub _get_initial_offset {
130 42     42   89 my $self = shift;
131              
132 42         74 my $offset = 0;
133 42         123 for my $param ($self->{instances}->@*) {
134 108         295 my $encoded = $param->encode;
135 102 100       279 if ($param->is_dynamic) {
136 26         68 $offset += 1;
137             } else {
138 76         140 $offset += scalar $param->_encoded->@*;
139             }
140             }
141              
142 36         94 return $offset;
143             }
144              
145             sub fixed_length {
146 42     42 1 58 my $self = shift;
147              
148 42 100       154 if ($self->{signature} =~ /[a-z](\d+)/) {
149 28         101 return $1;
150             }
151 14         42 return undef;
152             }
153              
154             sub _static_size {
155 104     104   151 return 1;
156             }
157              
158             # read the data at the encoded stack
159             sub _read_stack_set_data {
160 17     17   24 my $self = shift;
161              
162 17         54 my @data = $self->{data}->@*;
163 17         23 my @offsets;
164 17         36 my $current_offset = 0;
165              
166             # Since at this point we don't information about the chunks of data it is_dynamic
167             # needed to get all the offsets in the static header, so the dynamic values can
168             # be retrieved based in between the current and the next offsets
169 17         40 for my $instance ($self->{instances}->@*) {
170 59 100       107 if ($instance->is_dynamic) {
171 9         28 push @offsets, hex($data[$current_offset]) / 32;
172             }
173              
174 59         100 my $size = 1;
175 59 100       81 $size = $instance->_static_size unless $instance->is_dynamic;
176 59         130 $current_offset += $size;
177             }
178              
179 17         23 $current_offset = 0;
180 17         25 my %response;
181             # Dynamic data must to be set first since the full_size method
182             # will need to use the data offset related to the size of the item
183 17         48 for (my $i = 0; $i < $self->{instances}->@*; $i++) {
184 59         74 my $instance = $self->{instances}->[$i];
185 59 100       76 next unless $instance->is_dynamic;
186 9         15 my $offset_start = shift @offsets;
187 9   100     46 my $offset_end = $offsets[0] // scalar @data - 1;
188 9         44 my @range = @data[$offset_start .. $offset_end];
189 9         17 $instance->{data} = \@range;
190 9         13 $current_offset += scalar @range;
191 9         32 $response{$i} = $instance->decode();
192             }
193              
194 17         26 $current_offset = 0;
195              
196 17         45 for (my $i = 0; $i < $self->{instances}->@*; $i++) {
197 59         86 my $instance = $self->{instances}->[$i];
198              
199 59 100       104 if ($instance->is_dynamic) {
200 9         18 $current_offset++;
201 9         21 next;
202             }
203              
204 50         61 my $size = 1;
205 50 50       97 $size = $instance->_static_size unless $instance->is_dynamic;
206 50         119 my @range = @data[$current_offset .. $current_offset + $size - 1];
207 50         91 $instance->{data} = \@range;
208 50         64 $current_offset += $size;
209              
210 50         116 $response{$i} = $instance->decode();
211             }
212              
213 17         26 my @array_response;
214             # the given order of type signatures needs to be strict followed
215 17         99 push(@array_response, $response{$_}) for 0 .. scalar $self->{instances}->@* - 1;
216 17         101 return \@array_response;
217             }
218              
219             1;
220              
221             __END__