hyperledger-web3j/web3j

Solidity multidimensional array as return

Opened this issue · 3 comments

Bug_title

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?

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.

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[]"
    }

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