hyperledger-web3j / web3j

Lightweight Java and Android library for integration with Ethereum clients
https://www.web3labs.com/web3j-sdk
Other
5.09k stars 1.68k forks source link

Solidity multidimensional array as return #2038

Open ismoli opened 6 months ago

ismoli commented 6 months ago

_Bugtitle

Impossible to call Solidity function that returns 2d array from contract

Steps To Reproduce

Simple solidity function: function get() view external returns (uint128[][] memory) {...} Generate wrapper, or construct ethCall manually.

Expected behavior

Function should be called successfully and return result

Actual behavior

Exception occurred

java.lang.UnsupportedOperationException: Unable to access parameterized type org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Uint128>>

    at org.web3j.abi.TypeDecoder.decodeArrayElements(TypeDecoder.java:694)
    at org.web3j.abi.TypeDecoder.decodeDynamicArray(TypeDecoder.java:442)
    at org.web3j.abi.DefaultFunctionReturnDecoder.build(DefaultFunctionReturnDecoder.java:100)
    at org.web3j.abi.DefaultFunctionReturnDecoder.decodeFunctionResult(DefaultFunctionReturnDecoder.java:52)
    at org.web3j.abi.FunctionReturnDecoder.decode(FunctionReturnDecoder.java:57)
    at org.web3j.tx.Contract.executeCall(Contract.java:313)
    at org.web3j.tx.Contract.executeCallMultipleValueReturn(Contract.java:357)

Environment

Web3j version 4.9.8

Additional context

So generated code is using new TypeReference<DynamicArray<DynamicArray<Uint128>>>() {} as output value which seems like not really working.

Is there any workaround? When constructing a call manually (as opposed to generating wrapper), what should I set for Types to 'Output parameters' to make it work?

alexdupre commented 4 months ago

I've just encountered another issue with multi-dimensional arrays as return value, but with the opposite generated code.

For a function defined as

function getStateOfRewards(address _rewardOwner) external view returns (RewardState[][] memory rewardStates);

the generated wrapper was:

final Function function = new Function(FUNC_GETSTATEOFREWARDS, 
                Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(160, _rewardOwner)), 
                Arrays.<TypeReference<?>>asList(new TypeReference<DynamicArray<RewardState>>() {}));

that didn't work because of the missing nested array. Changing it to:

final Function function = new Function(FUNC_GETSTATEOFREWARDS, 
                Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(160, _rewardOwner)), 
                Arrays.<TypeReference<?>>asList(new TypeReference<DynamicArray<DynamicArray<RewardState>>>() {}));

made it working.

Tested with today 4.12.0-SNAPSHOT.

kxl4126 commented 3 months ago

The current getTypeAsString for DynamicArray only supports DynamicStruct as a nested type. I just created another class extending DynamicArray and overrode it so it also supported DynamicArray as a nested type, which worked for me.

override fun getTypeAsString(): String {
        val type = if (value.isEmpty()) {
            if (StructType::class.java.isAssignableFrom(this.componentType)) {
                Utils.getStructType(this.componentType)
            } else {
                AbiTypes.getTypeAString(this.componentType)
            }
        } else if (StructType::class.java.isAssignableFrom((value[0] as Type<*>).javaClass) || (DynamicArray::class.java.isAssignableFrom((value[0] as Type<*>).javaClass))) {
            (value[0] as Type<*>).typeAsString
        } else {
            AbiTypes.getTypeAString(this.componentType)
        }

        return "$type[]"
    }
ApereLieZ commented 2 months ago

@kxl4126 Could you please provide a complete example of the implementation of DynamicArray and an example of its usage?