Dynamic Array 에서의 디코딩 문제

안녕하세요.
Caver-Java 1.5.6 을 사용해서 개발하다가 DynamicArray 의 디코딩 결과가 이상하게 찍혀서 문의드립니다.

  1. 테스트 컨트렉트 코드
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;

contract TestWeb3JDynamicArray {
    
    struct Node {
        string parent;
        string[] nodes;
    }

    mapping(string => Node) internal nodes;
    
    function init(string memory parent) public {
        
        string[] memory emptyNodeList;
        nodes[parent] = Node({
            parent: parent, 
            nodes: emptyNodeList
        });
        
    }
    
    function setting(string memory parent) public {
        
        Node storage node = nodes[parent];
        string[] memory values = new string[](2);
        values[0] = "abc";
        node.nodes.push(values[0]);
        
        values[1] = "def";
        node.nodes.push(values[1]);
    }
    
    function getNode(string memory parent) view public returns(string memory, string[] memory) {
        return (nodes[parent].parent, nodes[parent].nodes);
    }
}
  1. caver-java 테스트코드
import com.klaytn.caver.Caver;
import com.klaytn.caver.contract.Contract;
import com.klaytn.caver.utils.ChainId;
import okhttp3.Credentials;
import org.junit.jupiter.api.Test;
import org.web3j.abi.datatypes.Type;
import org.web3j.protocol.http.HttpService;

import java.nio.charset.StandardCharsets;
import java.util.List;

public class TestWeb3JDynamicArrayTests {

    // [testnet] & [mainnet] 과 동일
    public static final String accessKeyId = "엑세스키아이디";
    public static final String secretAccessKey = "시크릿";

    protected static final String url = "https://node-api.klaytnapi.com/v1/klaytn";
    public static final String abi = "[{\"constant\":false,\"inputs\":[{\"name\":\"parent\",\"type\":\"string\"}],\"name\":\"init\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"parent\",\"type\":\"string\"}],\"name\":\"setting\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"parent\",\"type\":\"string\"}],\"name\":\"getNode\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]";

    @Test
    public void test() throws Exception {
        getNode(abi, "0x2EE49DD49CCde12798761A566b5a4c859365822F", "0");
    }

    private void getNode(String abi, String contractAddress, String parent) throws Exception {
        Caver caver = getCaver();
        Contract contract = new Contract(caver, abi, contractAddress);
        List<Type> result = contract.call("getNode", parent);
        System.out.println("result(0) = " + result.get(0).getValue());
        System.out.println("result(1) = " + result.get(1).getValue());
    }

    private Caver getCaver() {
        HttpService httpService = new HttpService(url);
        String auth = Credentials.basic(accessKeyId, secretAccessKey, StandardCharsets.UTF_8);
        httpService.addHeader("Authorization", auth);
        httpService.addHeader("x-chain-id", ChainId.BAOBAB_TESTNET +"");
        Caver caver = new Caver(httpService);
        return caver;
    }
}

  1. 위 test() 실행 결과콘솔
result(0) = 0
result(1) = [�, def]

ide.klaytn.com 에서는 정상적으로 찍혀서 나옵니다.

  1. ide.klaytn.com 에서의 정상적인 출력
    image

어떤점이 잘못 되었을까요 ??

감사합니다.

안녕하세요.
현재 caver-java v1.6.3-rc.1버전이 배포되어있는데요.
혹시 이 버전으로 되는지 확인 한번 해주실 수 있을까요?

참고로, caver-java v1.6.0부터 smart contract의 function parameter 및 return value에 Struct type을 받을 수 있도록 지원하고 있습니다.

감사합니다.

1.6.0 부터 정상동작하는것 확인했습니다.
다만, 1.6.x 부터 web3 → klaytn 기반으로 전부 패키지를 수정해줘야하네요. ㅠㅠ

감사합니다.

안녕하세요.
말씀해주신 1.6.3-rc.1 로 버전업해서 보니 아래와 같은 곳에서 오류가 납니다.
길이가 0인 dynamicArray 에서 디코딩 하다가 오류가 닙니다.

  1. 오류로그
java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64) ~[na:na]
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70) ~[na:na]
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248) ~[na:na]
	at java.base/java.util.Objects.checkIndex(Objects.java:372) ~[na:na]
	at java.base/java.util.ArrayList.get(ArrayList.java:458) ~[na:na]
	at com.klaytn.caver.abi.TypeDecoder.lambda$decodeDynamicArray$1(TypeDecoder.java:432) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.abi.TypeDecoder$$Lambda$1018/0000000013C830E0.apply(Unknown Source) ~[na:na]
	at com.klaytn.caver.abi.TypeDecoder.decodeArrayElements(TypeDecoder.java:772) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.abi.TypeDecoder.decodeDynamicArray(TypeDecoder.java:441) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.abi.DefaultFunctionReturnDecoder.build(DefaultFunctionReturnDecoder.java:98) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.abi.DefaultFunctionReturnDecoder.decodeFunctionResult(DefaultFunctionReturnDecoder.java:51) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.abi.FunctionReturnDecoder.decode(FunctionReturnDecoder.java:59) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.abi.ABI.decodeParameters(ABI.java:321) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.contract.ContractMethod.callFunction(ContractMethod.java:1051) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.contract.ContractMethod.call(ContractMethod.java:167) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.contract.Contract.call(Contract.java:290) ~[core-1.6.3-rc.1.jar:na]
	at com.klaytn.caver.contract.Contract.call(Contract.java:271) ~[core-1.6.3-rc.1.jar:na]
...
  1. 문제되는 코드블록
        BiFunction<List<T>, String, T> function =
                (elements, typeName) -> {
                    Class baseTypeCls = Array.class.isAssignableFrom(elements.get(0).getClass())
                            ? (Class<T>) elements.get(0).getClass()
                            : (Class<T>) AbiTypes.getType(elements.get(0).getTypeAsString());

                    return (T) new DynamicArray(baseTypeCls, elements);
                };

위 elements.get(0).getClass()) 에서 비어있는 케이스에서 오류가 납니다.

재현방법을 부연설명합니다.
컨트렉트 코드는 어제 올려드린 TestWeb3JDynamicArray 로 가능하며 init() 호출후 getNode() 를 호출하게 되면 문제가 재현이 됩니다.

@Kale 안녕하세요. 본 이슈 해결은 좀더 기다려야할까요? 진행상황이 궁금합니다.
감사합니다.

@yoe21c
안녕하세요.
알려주신 이슈는 한번 확인해보겠습니다.

컨트랙트 상의 코드만 봐서는 emptyNodeList에 해당하는 값이 아무것도 들어가있지 않아서 생기는 문제로 보이는데요.

Node struct에 nodes필드는 가변 배열타입이고 이 type은 size가 정해져있지 않아, 데이터가 배열에 들어가있을 경우에만 array에 들어간 데이터 수만큼 배열의 크기를 할당하게 됩니다.

하지만 init()만 실행했을 경우에는 emptyNodeList는 아무값도 들어가있지 않으니 nodes field도 초기화가 되어있지 않은 상태일 것이기 때문에 decoding이 안되는 것이라고 보여집니다.

init을 하실때 nodes 필드에 들어가는 데이터를 일단 초기화해서 사용해보시면 정상동작 할 것이라고 생각합니다.

감사합니다.

안녕하세요.
그 방법은 이미 알고 있지만 caver-java 에서 길이 0 인 dynamicarray 를 에러(NPE)로 다루는게 맞나 싶어서 문의드린 내용이었습니다. 배열에 아무것도 들어가 있지 않으면 빈 리스트를 리턴하면 되지 않을까 합니다. ide 상에서는 정상적으로 string [] 가 리턴이 되지만 caver-java 에서는 오류로 리턴을 하고 있으니 일관성이 없어보여서 질문드렸습니다.
감사합니다.

네. 확인 감사합니다.
일단 임시방편의 해결책을 말씀드린 부분은 개발을 진행 하셔야되는 상황이신 것같아서 이야기드렸던거구요.

이 이슈는 확인 이후에 이 게시글의 댓글로 공유 드리겠습니다.

감사합니다.

안녕하세요. @yoe21c
알려주신 IndexOutOfBoundsException 이슈를 해결하여 caver-java v1.6.3-rc.3을 배포하였습니다.
수정한 내용에 대해서는 아래 Github PR링크를 통해 확인하실 수 있습니다.

감사합니다.

1 Like

네. 감사합니다.
즐거운 하루 보내세요.