File Coverage

blib/lib/Web3/Tiny/Contract.pm
Criterion Covered Total %
statement 9 62 14.5
branch 0 28 0.0
condition 0 8 0.0
subroutine 3 9 33.3
pod 0 3 0.0
total 12 110 10.9


line stmt bran cond sub pod time code
1             package Web3::Tiny::Contract;
2              
3 1     1   5 use strict;
  1         1  
  1         29  
4 1     1   3 use warnings;
  1         1  
  1         38  
5              
6 1     1   424 use Web3::Tiny::ABI qw(encode_call decode_result);
  1         2  
  1         624  
7              
8             our $VERSION = '0.01';
9              
10             sub _hex_to_bytes {
11 0     0     my ($val) = @_;
12 0 0 0       return '' unless defined $val && length($val);
13 0 0         if ($val =~ /^0x([0-9a-fA-F]*)$/) {
14 0           my $hex = $1;
15 0 0         $hex = '0' . $hex if length($hex) % 2;
16 0           return pack('H*', $hex);
17             }
18 0           return $val;
19             }
20              
21             # Web3::Tiny::Contract->new(
22             # rpc => $rpc, # a Web3::Tiny::RPC
23             # address => '0x...',
24             # abi => {
25             # balanceOf => { sig => 'balanceOf(address)', returns => ['uint256'] },
26             # transfer => { sig => 'transfer(address,uint256)', returns => ['bool'] },
27             # },
28             # )
29             sub new {
30 0     0 0   my ($class, %opts) = @_;
31 0           for my $req (qw(rpc address abi)) {
32 0 0         die "Web3::Tiny::Contract: '$req' is required\n" unless $opts{$req};
33             }
34             return bless {
35             rpc => $opts{rpc},
36             address => $opts{address},
37             abi => $opts{abi},
38 0           }, $class;
39             }
40              
41             sub _method {
42 0     0     my ($self, $name) = @_;
43 0           my $m = $self->{abi}{$name};
44 0 0         die "Web3::Tiny::Contract: unknown method '$name'\n" unless $m;
45 0           return $m;
46             }
47              
48             # call($method, @args) -> decoded return value(s) (a single scalar if
49             # only one return type, otherwise a list)
50             sub call {
51 0     0 0   my ($self, $method, @args) = @_;
52 0           my $m = $self->_method($method);
53 0           my $data = encode_call($m->{sig}, @args);
54              
55             my $result_hex = $self->{rpc}->call(
56             'eth_call',
57 0           { to => $self->{address}, data => '0x' . unpack('H*', $data) },
58             'latest',
59             );
60              
61 0   0       my $returns = $m->{returns} || [];
62 0 0         return () unless @$returns;
63              
64 0           my @decoded = decode_result($returns, _hex_to_bytes($result_hex));
65 0 0         return wantarray ? @decoded : $decoded[0];
66             }
67              
68             # send($wallet, $method, @args, \%tx_opts) -> "0x..." transaction hash
69             #
70             # %tx_opts may supply nonce/gas_price/gas/chain_id/value explicitly;
71             # any left out are looked up from the node (nonce, gas_price, chain_id)
72             # or estimated (gas).
73             sub send {
74 0     0 0   my $self = shift;
75 0           my $wallet = shift;
76 0           my $method = shift;
77 0 0         my %tx_opts = ref($_[-1]) eq 'HASH' ? %{ pop @_ } : ();
  0            
78 0           my @args = @_;
79              
80 0           my $m = $self->_method($method);
81 0           my $data = encode_call($m->{sig}, @args);
82 0           my $data_hex = '0x' . unpack('H*', $data);
83              
84 0           my $rpc = $self->{rpc};
85              
86             $tx_opts{nonce} = do {
87 0           my $n = $rpc->call('eth_getTransactionCount', $wallet->address, 'latest');
88 0           hex($n);
89 0 0         } unless defined $tx_opts{nonce};
90              
91             $tx_opts{gas_price} = do {
92 0           my $gp = $rpc->call('eth_gasPrice');
93 0           hex($gp);
94 0 0         } unless defined $tx_opts{gas_price};
95              
96             $tx_opts{chain_id} = do {
97 0           my $cid = $rpc->call('eth_chainId');
98 0           hex($cid);
99 0 0         } unless defined $tx_opts{chain_id};
100              
101             $tx_opts{gas} = do {
102             my $est = $rpc->call('eth_estimateGas', {
103             from => $wallet->address,
104             to => $self->{address},
105             data => $data_hex,
106 0 0         ( defined $tx_opts{value} ? (value => sprintf('0x%s', _to_hex($tx_opts{value}))) : () ),
107             });
108 0           hex($est);
109 0 0         } unless defined $tx_opts{gas};
110              
111             my $raw = $wallet->sign_transaction(
112             %tx_opts,
113             to => $self->{address},
114 0           data => $data_hex,
115             );
116              
117 0           return $rpc->call('eth_sendRawTransaction', $raw);
118             }
119              
120             sub _to_hex {
121 0     0     my ($n) = @_;
122 0           require Math::BigInt;
123 0 0 0       my $bi = ref($n) && $n->isa('Math::BigInt') ? $n : Math::BigInt->new("$n");
124 0           my $hex = $bi->as_hex;
125 0           $hex =~ s/^0x//;
126 0           return $hex;
127             }
128              
129             1;
130              
131             __END__