determineSendOptions 함수로 GAS값을 자동으로 설졍하려고 하는데 오류가 발생합니다

안녕하세요.

caver-java 1.8.0을 사용해서 서비스를 운영하고 있는 개발자입니다.

caver-java에서 트랜잭션을 실행할때 getDefaultSendOptions를 사용해서 sendOptions을 넘기면 "determineSendOptions"함수를 통해 gas값을 자동으로 설정하는 것으로 알고 있습니다.

determineSendOptions함수 호출 전에 caver.wallet.add(keyring)을 통해서 caver의 wallet에 키링을 추가해주었고 트랜잭션 호출 전에 determineSendOptions를 호출할때 From에 keyring의 주소만 있는것을 화인하였습니다.

그리고 "estimateGas"함수로 잘 전달되어 온것도 확인하였는데 gas 비용이 필수값이라는 오류가 발생했습니다.
아래는 제가 Test할때 사용한 소스 코드입니다.

String testPrivateKey = "0xf651405d5aa1e02022b21f2fb8aa6494bb67a48d00d35beb8b394c83b60c231e0x000x166272e821dab0a5d1d560e8eb2a6d751da47e63";
AbstractKeyring deployerKeyring = createFromPrivateKey(testPrivateKey);

CaverUtil klaytnUtils = new CaverUtil(caver, contractAddress);
CaverUtil.getCaver().wallet.add(deployerKeyring);

CaverUtil.getDokdoNFT().setSender(deployerKeyring);

CallObject callObject = CallObject.createCallObject(CaverUtil.KIP17().getDefaultSendOptions().getFrom());
BigInteger estimateGas = estimateGas(klaytnUtils.KIP17(), "burn", callObject, Arrays.asList(new BigInteger("13")));
String gas = Numeric.toHexStringWithPrefix(estimateGas);

log.info("### gas : {}", gas);

위 함수를 실행시켰을 때 오류가

java.io.IOException: gas required exceeds allowance or always failing transaction

	at com.klaytn.caver.contract.ContractMethod.estimateGas(ContractMethod.java:1097)
	at com.klaytn.caver.contract.ContractMethod.estimateGas(ContractMethod.java:510)

이렇게 발생하였습니다.

determineSendOptions함수 호출해서 GAS비용을 자동으로 설정해주고 싶은데 제가 빠뜨린 부분이 있을까요??

안녕하세요.

위 예제 코드는 질문자분께서 caver의 함수들을 한번 감싸서 만드신 custom class로 보이는데요.

내부적인 동작에 관한 코드가 없이는 어디가 잘못되었는지 확인할 수가 없습니다.

자세한 검토가 필요하시다면 질문에 필요한 내부 코드를 공유부탁드리겠습니다.

감사합니다.

@Kale

Git주소로 공유드립니다.

감사합니다.

이건 컨트랙트 코드 인것 같은데요.

CallObject callObject = CallObject.createCallObject(CaverUtil.KIP17().getDefaultSendOptions().getFrom());
BigInteger estimateGas = estimateGas(klaytnUtils.KIP17(), "burn", callObject, Arrays.asList(new BigInteger("13")));

에서 estimateGas에 대한 함수를 주셔야할 것 같습니다.

@Kale

estimateGas 함수는 Contract에 있는 함수 그대로 사용했습니다

private static BigInteger estimateGas(KIP17 kip17, String functionName, CallObject callObject, List<Object> arguments) throws NoSuchMethodException, IOException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
        String gas = kip17.getMethod(functionName).estimateGas(arguments, callObject);
        BigDecimal bigDecimal = new BigDecimal(Numeric.toBigInt(gas));
        BigInteger gasInteger = bigDecimal.multiply(new BigDecimal(1.5)).toBigInteger();

        return gasInteger;
    }

이런식으로 사용했습니다.

저 함수는 callObject로 전달된 Gas값을 Gas를 측정하다 모두 소모를 했거나, 전달된 파라미터로 함수를 실행중에 에러가 발생한 것입니다.

Smart contract를 호출하는 부분에서 문제가 되는건 아닌지 확인부탁드리겠습니다.

@Kale

네 감사합니다.
확인하면서 궁금한점이 있어서 질문드립니다.

caver-java Git을 둘어보다가 Contract 내부 함수를 확인했습니다.

그때 “determineSendOptions” 부분에 가스비가 없으면 아래와 같이 설정되는것을 확인해서 질문드린거처럼 했습니다.

CallObject callObject = CallObject.createCallObject(sendOptions.getFrom());
BigInteger estimateGas = estimateGas(kip17, functionName, callObject, argument);
gas = Numeric.toHexStringWithPrefix(estimateGas);

위 소스가 원본(Caver-java)의 소스인데 callObject에 GAS비용 설정하는 부분이 없었습니다.
제가 이해한거는 CallObject에 GAS비용을 설정해서 estimateGas를 호출해야한다는걸로 이해했습니다.

혹시 제가 이해한게 맞을까요??

다시한번 말씀드리지만 callObject로 전달된 Gas값을 Gas를 측정하다 모두 소모했던 경우를 들었던 것입니다.
callObject로 Gas를 넣었을 경우에 대한 내용이며 넣지않으면 플랫폼에서 자동으로 Gas값을 셋팅해서 사용합니다.

@Kale

호출된 부분에서 문제가 된거 같긴한데 호출된 부분을 봐도 어떤 부분이 오류 인지 몰라서 제가 호출한 부분을 드려서 문의드립니다ㅜㅜ

@Test
public void testBurn() throws Exception{
    String testPrivateKey = "0x07f2066d045c19f72479cf3079a8161244bb3a9d0899a3487b6ec00c13596360";
    AbstractKeyring deployerKeyring = KlaytnUtils.createFromPrivateKey(testPrivateKey);

    KlaytnUtils klaytnUtils = new KlaytnUtils(caverProvider.get(), contractAddress);
    klaytnUtils.getCaver().wallet.add(deployerKeyring);

    BigInteger tokenId = new BigInteger("13");

    TransactionReceipt.TransactionReceiptData receiptData = klaytnUtils.burn(deployerKeyring, tokenId);
    log.info("#### receiptData : {}", objectToString(receiptData));
}

-- klaytnUtils.burn 부분
public TransactionReceipt.TransactionReceiptData burn(AbstractKeyring keyring, BigInteger tokenId) throws Exception{
    kip17.setSender(keyring);
    TransactionReceipt.TransactionReceiptData receiptData = kip17.burn(tokenId);
    return receiptData;
}

-- setSender 부분
public void setSender(AbstractKeyring senderKering) {
    SendOptions sendOptions = this.getDefaultSendOptions();
    sendOptions.setFrom(senderKering.getAddress());

    if(!this.getCaver().wallet.isExisted(senderKering.getAddress())){
        this.getCaver().wallet.add(senderKering);
    }
}

-- KIP17.java (Contract 상속)
public TransactionReceipt.TransactionReceiptData burn(BigInteger tokenId) throws NoSuchMethodException, IOException, InstantiationException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, TransactionException {
    return burn(tokenId, this.getDefaultSendOptions());
}

public TransactionReceipt.TransactionReceiptData burn(BigInteger tokenId, SendOptions sendParam) throws NoSuchMethodException, IOException, InstantiationException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, TransactionException {
    SendOptions sendOption = determineSendOptions(this, sendParam, FUNCTION_BURN, Arrays.asList(tokenId));
    TransactionReceipt.TransactionReceiptData receiptData = this.getMethod(FUNCTION_BURN).send(Arrays.asList(tokenId), sendOption);
    return receiptData;
}

private static SendOptions determineSendOptions(KIP17 kip17, SendOptions sendOptions, String functionName, List<Object> argument) throws NoSuchMethodException, IOException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
    SendOptions newSendOptions = null;

    String from = kip17.getDefaultSendOptions().getFrom();
    String gas = kip17.getDefaultSendOptions().getGas();
    String value = kip17.getDefaultSendOptions().getValue();
    Boolean feeDelegation = kip17.getDefaultSendOptions().getFeeDelegation();
    String feePayer = kip17.getDefaultSendOptions().getFeePayer();
    String feeRatio = kip17.getDefaultSendOptions().getFeeRatio();

    if(sendOptions.getFrom() != null) {
        from = sendOptions.getFrom();
    }

    if(sendOptions.getGas() == null) {
        //If passed gas fields in sendOptions and defaultSendOptions is null, it estimate gas.
        if(gas == null) {
            CallObject callObject = CallObject.createCallObject(sendOptions.getFrom());
            BigInteger estimateGas = estimateGas(kip17, functionName, callObject, argument);
            gas = Numeric.toHexStringWithPrefix(estimateGas);
        }
    } else {
        gas = sendOptions.getGas();
    }

    ~~
}

private static BigInteger estimateGas(KIP17 kip17, String functionName, CallObject callObject, List<Object> arguments) throws NoSuchMethodException, IOException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
    String gas = kip17.getMethod(functionName).estimateGas(arguments, callObject);
    BigDecimal bigDecimal = new BigDecimal(Numeric.toBigInt(gas));
    BigInteger gasInteger = bigDecimal.multiply(new BigDecimal(1.5)).toBigInteger();

    return gasInteger;
}

이렇게 호출 했을 때 "gas required exceeds allowance or always failing transaction"에러가 발생했습니다