web3p / web3.php

A php interface for interacting with the Ethereum blockchain and ecosystem. Native ABI parsing and smart contract interactions.
MIT License
1.16k stars 543 forks source link

Unable to abi->encodeParameters() for string[] #300

Open joowon-byun opened 1 year ago

joowon-byun commented 1 year ago

Problem

I found out web3.php is unable to encodeParams for string[]. To be more specific, it is unable to encode for an array of dynamic type. You can see that the php result is much shorter. (I splitted lines for readability)

encodeParameters result from go (klaytn) :

6312d9e2
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000011687474703a2f2f6d7975726c312e636f6d000000000000000000000000000000

encodeParameters result from php :

0x
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000011687474703a2f2f6d7975726c312e636f6d000000000000000000000000000000

How to reproduce

How to reproduce in go (Klaytn) : (Klaytn's ABI mechanism is exactly the same with Ethereum's)

package main

import (
    "fmt"
    "log"
    "math/big"
    "strings"
    "testing"

    "github.com/klaytn/klaytn/accounts/abi"
    "github.com/klaytn/klaytn/common"
)

func TestSignString(t *testing.T) {
    var images = []string{
        "http://myurl1.com",
    }
    abi, err := abi.JSON(strings.NewReader(abiExample))
    if err != nil {
        log.Fatal("failed to read abi")
    }

    packedData, err := abi.Pack("stringAbiTest", images)
    if err != nil {
        log.Fatal("failed to read abi")
    }
    fmt.Println(common.Bytes2Hex(packedData))
}

func TestSignInt(t *testing.T) {
    nums := []*big.Int{big.NewInt(123456)}
    abi, err := abi.JSON(strings.NewReader(abiExample))
    if err != nil {
        log.Fatal("failed to read abi")
    }

    packedData, err := abi.Pack("intAbiTest", nums)
    fmt.Println(common.Bytes2Hex(packedData))
    if err != nil {
        log.Fatal("failed to read abi")
    }
}

var abiExample = `[
    {
        "name":"stringAbiTest",
        "inputs":[
            {"name":"funcName","type":"string[]"}
        ],
        "type": "function"
    },
    {
        "name":"intAbiTest",
        "inputs":[
            {"name":"funcName","type":"uint256[]"}
        ],
        "type": "function"
    }
]
`

How to reproduce in php:

<?php

require('./vendor/autoload.php');

use Web3\Contracts\Ethabi;
use Web3\Contracts\Types\Address;
use Web3\Contracts\Types\Boolean;
use Web3\Contracts\Types\Bytes;
use Web3\Contracts\Types\DynamicBytes;
use Web3\Contracts\Types\Integer;
use Web3\Contracts\Types\Str;
use Web3\Contracts\Types\Uinteger;

$ethabi = new Ethabi([
    'address' => new Address,
    'bool' => new Boolean,
    'bytes' => new Bytes,
    'dynamicBytes' => new DynamicBytes,
    'int' => new Integer,
    'string' => new Str,
    'uint' => new Uinteger,
]);

$data = $ethabi->encodeParameters(
    ['string[]'],
    [
        ["http://myurl1.com"]
    ]
);

echo $data;

Where it went wrong

In go code, if the element type of an array is dynamic type, an offset is added to the abi. However, there is no such logic in web3p. (It can encode for static types such as int256[], but it cannot encode for dynamic types such as string[].)

Klaytn, Eth code :

        // calculate offset if any
        offset := 0
        offsetReq := isDynamicType(*t.Elem)
        if offsetReq {
            offset = getTypeSize(*t.Elem) * v.Len()
        }
        var tail []byte
        for i := 0; i < v.Len(); i++ {
            val, err := t.Elem.pack(v.Index(i))
            if err != nil {
                return nil, err
            }
            if !offsetReq {
                ret = append(ret, val...)
                continue
            }
            ret = append(ret, packNum(reflect.ValueOf(offset))...)
            offset += len(val)

web3.php code :

    protected function encodeWithOffset($type, $solidityType, $encoded, $offset)
     {
        if ($solidityType->isDynamicArray($type)) {
        ...
            if ($solidityType->isDynamicArray($nestedName)) {
                $previousLength = 2;

                for ($i=0; $i<count($encoded); $i++) {
                    if (isset($encoded[$i - 1])) {
                        $previousLength += abs($encoded[$i - 1][0]);
                    }
                    $result .= IntegerFormatter::format($offset + $i * $nestedStaticPartLength + $previousLength * 32);
                }
            }
            for ($i=0; $i<count($encoded); $i++) {
                $additionalOffset = floor(mb_strlen($result) / 2);
                $result .= $this->encodeWithOffset($nestedName, $solidityType, $encoded[$i], $offset + $additionalOffset);
            }
            return mb_substr($result, 64);
inmarelibero commented 1 year ago

@winnie-byun any news on this? I think it's a highly important problem

inmarelibero commented 1 year ago

@sc0Vu ping on this

inmarelibero commented 1 year ago

@sc0Vu

cyanerd commented 1 year ago

@inmarelibero, hello! have you find a solution for this? have same issue :(

2Cubes commented 1 year ago

You need concat function encode and params encode, like this

$function_encode = $ethabi->encodeFunctionSignature('stringAbiTest(string[])')

stringAbiTest(string[]) 6312d9e2
intAbiTest(uint256[]) 44c6a750