|
6 | 6 |
|
7 | 7 | use Farbcode\LaravelEvm\Contracts\AbiCodec; |
8 | 8 | use Web3\Contract; |
| 9 | +use kornrunner\Keccak; // keccak256 helper |
9 | 10 |
|
10 | 11 | class AbiCodecWeb3p implements AbiCodec |
11 | 12 | { |
12 | 13 | public function encodeFunction(array|string $abi, string $fn, array $args): string |
13 | 14 | { |
14 | | - $c = new Contract('', is_array($abi) ? json_encode($abi) : $abi); |
| 15 | + // web3p Contract::getData() returns void and fills internal state. To avoid dynamic property reliance |
| 16 | + // implement a lightweight encoder for common static call patterns. |
| 17 | + $abiArray = is_string($abi) ? json_decode($abi, true) : $abi; |
| 18 | + if (!is_array($abiArray)) { |
| 19 | + throw new \InvalidArgumentException('ABI must decode to array'); |
| 20 | + } |
| 21 | + $item = null; |
| 22 | + foreach ($abiArray as $entry) { |
| 23 | + if (($entry['type'] ?? '') === 'function' && ($entry['name'] ?? '') === $fn) { |
| 24 | + $item = $entry; break; |
| 25 | + } |
| 26 | + } |
| 27 | + if (!$item) { |
| 28 | + throw new \RuntimeException('Function '.$fn.' not found in ABI'); |
| 29 | + } |
| 30 | + $inputs = $item['inputs'] ?? []; |
| 31 | + // Build function selector |
| 32 | + $typesSig = implode(',', array_map(fn($i) => $i['type'], $inputs)); |
| 33 | + $signature = $fn.'('.$typesSig.')'; |
| 34 | + $hash = Keccak::hash($signature, 256); |
| 35 | + $selector = '0x'.substr($hash, 0, 8); |
| 36 | + // Encode arguments (very simplified: handles address, uint256, bytes32, bool, string) |
| 37 | + $encodedArgs = ''; |
| 38 | + foreach ($inputs as $idx => $in) { |
| 39 | + $type = $in['type']; |
| 40 | + $val = $args[$idx] ?? null; |
| 41 | + $encodedArgs .= $this->encodeValue($type, $val); |
| 42 | + } |
| 43 | + return $selector.$encodedArgs; |
| 44 | + } |
15 | 45 |
|
16 | | - return $c->getData($fn, ...$args); |
| 46 | + private function encodeValue(string $type, mixed $val): string |
| 47 | + { |
| 48 | + // Simplified static encoding (no dynamic types except string truncated) |
| 49 | + if (str_starts_with($type, 'uint')) { |
| 50 | + return str_pad(dechex((int) $val), 64, '0', STR_PAD_LEFT); |
| 51 | + } |
| 52 | + if ($type === 'address') { |
| 53 | + $clean = strtolower(preg_replace('/^0x/', '', (string) $val)); |
| 54 | + return str_pad($clean, 64, '0', STR_PAD_LEFT); |
| 55 | + } |
| 56 | + if ($type === 'bytes32') { |
| 57 | + $clean = strtolower(preg_replace('/^0x/', '', (string) $val)); |
| 58 | + return str_pad(substr($clean, 0, 64), 64, '0', STR_PAD_RIGHT); |
| 59 | + } |
| 60 | + if ($type === 'bool') { |
| 61 | + return str_pad($val ? '1' : '0', 64, '0', STR_PAD_LEFT); |
| 62 | + } |
| 63 | + if ($type === 'string') { |
| 64 | + // naive: hex of string truncated to 32 bytes |
| 65 | + $hex = bin2hex((string) $val); |
| 66 | + return str_pad(substr($hex, 0, 64), 64, '0', STR_PAD_RIGHT); |
| 67 | + } |
| 68 | + throw new \RuntimeException('Unsupported ABI type '.$type); |
17 | 69 | } |
18 | 70 |
|
19 | 71 | public function callStatic(array|string $abi, string $fn, array $args, callable $ethCall): mixed |
20 | 72 | { |
21 | 73 | $data = $this->encodeFunction($abi, $fn, $args); |
22 | | - |
23 | 74 | return $ethCall($data); |
24 | 75 | } |
25 | 76 | } |
0 commit comments