ERC 20 토큰 전송 시 트랜젝션 hash 값은 리턴되나 실제 hash 값을 확인하면 정상으로 처리가 안되고 있는데 원인이 무었을런지요?

안녕하세요.Eric 님께서 참고 할 소스 코드 주셔서 전송 기능의 소스를 만들었는데요.
다만, 해당 코드를 참고하여 토큰 전송 코드를 완료하였으나 tx hash 값을 리턴 받았으나 정상으로 처리는 되지 않았습니다.

  1. 아래는 해당 전체 코드 인데요. 소스 상에 문제가 될 내용이 있는지 추가 문의드립니다.
    소스 :
    try {
    int chainId = ChainId.BAOBAB_TESTNET;
    DefaultBlockParameter blockParameter = DefaultBlockParameterName.LATEST;
    BigInteger gasLimit = new DefaultGasProvider().getGasLimit();

         BigDecimal sendAmount = new BigDecimal(amount);
         BigInteger value = Convert.toPeb(sendAmount , KLAY).toBigInteger();
         List<Type> params = Arrays.asList(new Address(toAddress), new Uint256(value));
         List<TypeReference<?>> returnTypes = Arrays.<TypeReference<?>>asList(new TypeReference<Bool>() {});
    
         Function function = new Function("transfer", params, returnTypes);
         String functionCallData = FunctionEncoder.encode(function);
         BigInteger nonce = caver.klay().getTransactionCount(senderCredential.getAddress(), blockParameter).send().getValue();
    
    
         TxType tx = SmartContractExecutionTransaction
                 .create(senderCredential.getAddress(),
                         contractAddress,
                         BigInteger.ZERO,
                         functionCallData.getBytes(),
                         gasLimit)
                 .nonce(nonce)
                 .build();
    
         // This is your signed raw transaction
         String rawTx = tx.sign(senderCredential, chainId).getValueAsString();
    
         TransactionManager manager = new TransactionManager.Builder(caver, senderCredential)
                 .setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(caver, 1000, 10))
                 .setChaindId(chainId)
                 .build();
    
         KlayTransactionReceipt.TransactionReceipt receipt = manager.executeTransaction(rawTx);
         System.out.println(receipt.getTransactionHash());
    
     } catch (Exception e) {
         System.out.println("오류입니다.");
     }
    
  2. 아래의 인자 값 BigInteger.ZERO 값은 고정 값인가요?

         TxType tx = SmartContractExecutionTransaction
                 .create(senderCredential.getAddress(),
                         contractAddress,
                         BigInteger.ZERO,
                         functionCallData.getBytes(),
                         gasLimit)
                 .nonce(nonce)
                 .build();
    

참조되는 클래스의 함수 값에는 from, recipient, amount, payload, gasLimit 인데…
3번째 대응되는 것은amount 값인 수량이 아닌지…^^;;

함수를 실행하여 리턴된 hash 값을 참고로 드립니다.

*** 컨트랙트 발행은 아래의 코드로 발행하였습니다.**

pragma solidity ^0.5.0;

/**

  • @dev EIP에 정의된 ERC20 표준 인터페이스 추가 함수를 포함하지 않습니다;

  • 이들에 접근하려면 ERC20Detailed을 확인하세요.
    */
    interface IERC20 {
    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(address recipient, uint256 amount) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(address indexed owner, address indexed spender, uint256 value);
    }

library SafeMath {
/**
* @dev 두 부호 없는 정수의 합을 반환합니다.
* 오버플로우 발생 시 예외처리합니다.
*
* 솔리디티의 + 연산자를 대체합니다.
*
* 요구사항:
* - 덧셈은 오버플로우될 수 없습니다.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, “SafeMath: addition overflow”);

    return c;
}

/**
 * @dev 두 부호 없는 정수의 차를 반환합니다.
 * 결과가 음수일 경우 오버플로우입니다.
 *
 * 솔리디티의 `-` 연산자를 대체합니다.
 *
 * 요구사항:
 * - 뺄셈은 오버플로우될 수 없습니다.
 */
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b <= a, "SafeMath: subtraction overflow");
    uint256 c = a - b;

    return c;
}

/**
 * @dev 두 부호 없는 정수의 곱을 반환합니다.
 * 오버플로우 발생 시 예외처리합니다.
 *
 * 솔리디티의 `*` 연산자를 대체합니다.
 *
 * 요구사항:
 * - 곱셈은 오버플로우될 수 없습니다.
 */
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    // 가스 최적화: 이는 'a'가 0이 아님을 요구하는 것보다 저렴하지만,
    // 'b'도 테스트할 경우 이점이 없어집니다.
    // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
    if (a == 0) {
        return 0;
    }

    uint256 c = a * b;
    require(c / a == b, "SafeMath: multiplication overflow");

    return c;
}

/**
 * @dev 두 부호 없는 정수의 몫을 반환합니다. 0으로 나누기를 시도할 경우
 * 예외처리합니다. 결과는 0의 자리에서 반올림됩니다.
 *
 * 솔리디티의 `/` 연산자를 대체합니다. 참고: 이 함수는
 * `revert` 명령코드(잔여 가스를 건들지 않음)를 사용하는 반면, 솔리디티는
 * 유효하지 않은 명령코드를 사용해 복귀합니다(남은 모든 가스를 소비).
 *
 * 요구사항:
 * - 0으로 나눌 수 없습니다.
 */
function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // 솔리디티는 0으로 나누기를 자동으로 검출하고 중단합니다.
    require(b > 0, "SafeMath: division by zero");
    uint256 c = a / b;
    // assert(a == b * c + a % b); // 이를 만족시키지 않는 경우가 없어야 합니다.

    return c;
}

/**
 * @dev 두 부호 없는 정수의 나머지를 반환합니다. (부호 없는 정수 모듈로 연산),
 * 0으로 나눌 경우 예외처리합니다.
 *
 * 솔리디티의 `%` 연산자를 대체합니다. 이 함수는 `revert`
 * 명령코드(잔여 가스를 건들지 않음)를 사용하는 반면, 솔리디티는
 * 유효하지 않은 명령코드를 사용해 복귀합니다(남은 모든 가스를 소비).
 *
 * 요구사항:
 * - 0으로 나눌 수 없습니다.
 */
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b != 0, "SafeMath: modulo by zero");
    return a % b;
}

}

/**

  • @dev IERC20 인터페이스의 구현

  • 이 구현은 토큰이 생성되는 방식과 무관합니다. 이는

  • 파생 컨트랙트에 _mint를 이용한 공급 메커니즘이 추가되어야 한다는 의미입니다.

  • 일반적인 메커니즘은 ERC20Mintable을 참조하세요.

  • *자세한 내용은 가이드 [How to implement supply mechanisms]

  • (How to implement ERC20 supply mechanisms - Guides and Tutorials - OpenZeppelin Forum)를 참고하세요.*

  • 일반적인 OpenZeppelin 지침을 따랐습니다: 함수는 실패시 false를 반환하는 대신

  • 예외처리를 따릅니다. 그럼에도 이는 관습적이며

  • ERC20 애플리케이션의 기대에 반하지 않습니다.

  • 또한, transferFrom 호출 시 Approval 이벤트가 발생됩니다.

  • 이로부터 애플리케이션은 해당 이벤트를 수신하는 것만으로

  • 모든 계정에 대한 허용량(allowance)을 재구성 할 수 있습니다. 이는 스펙에서 요구되지 않으므로, EIP에 대한 다른 구현체는

  • 이러한 이벤트를 발생하지 않을 수 있습니다.

  • 마지막으로, 표준이 아닌 decreaseAllowanceincreaseAllowance

  • 함수가 추가되어 허용량 설정과 관련해 잘 알려진 문제를

  • 완화했습니다. IERC20.approve를 참조하세요.
    */
    contract MyERC20 is IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    // https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/token/ERC20/ERC20Detailed.sol 시작 부분을 참고
    string private _name;
    string private _symbol;
    uint8 private _decimals;

    constructor (string memory name, string memory symbol, uint8 decimals) public {
    _name = name;
    _symbol = symbol;
    _decimals = decimals;

     _mint(msg.sender, 100000 * 10 ** uint256(decimals)); // 주의!
    

    }

    /**

    • @dev 토큰 이름을 반환합니다.
      */
      function name() public view returns (string memory) {
      return _name;
      }

    /**

    • @dev 주로 이름을 줄여서 표현한 토큰 심볼을
    • 반환합니다.
      */
      function symbol() public view returns (string memory) {
      return _symbol;
      }

    /**

    • @dev 사용자 표현을 위한 소수 자릿수를 반환합니다.
    • 예를 들어, decimals2인 경우, 505` 토큰은
    • 사용자에게 5,05 (505 / 10 ** 2)와 같이 표시되어야 합니다.
    • 토큰은 보통 18의 값을 취하며, 이는 Ether와 Wei의 관계를
    • 모방한 것입니다.
    • 이 정보는 디스플레이 목적으로만 사용됩니다.

    • IERC20.balanceOfIERC20.transfer를 포함해
    • 컨트랙트의 산술 연산에 어떠한 영향을 주지 않습니다.
      */
      function decimals() public view returns (uint8) {
      return _decimals;
      }
      // https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/token/ERC20/ERC20Detailed.sol 끝 부분을 참고

    uint256 private _totalSupply;

    /**

    • @dev IERC20.totalSupply를 참조하세요.
      */
      function totalSupply() public view returns (uint256) {
      return _totalSupply;
      }

    /**

    • @dev IERC20.balanceOf를 참조하세요.
      */
      function balanceOf(address account) public view returns (uint256) {
      return _balances[account];
      }

    /**

    • @dev IERC20.transfer를 참조하세요.
    • 요구사항 :
      • recipient는 영 주소(0x0000…0)가 될 수 없습니다.
      • 호출자의 잔고는 적어도 amount 이상이어야 합니다.
        */
        function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
        }

    /**

    • @dev IERC20.allowance를 참조하세요.
      */
      function allowance(address owner, address spender) public view returns (uint256) {
      return _allowances[owner][spender];
      }

    /**

    • @dev IERC20.approve를 참조하세요.
    • 요구사항:
      • spender는 영 주소가 될 수 없습니다.
        */
        function approve(address spender, uint256 value) public returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
        }

    /**

    • @dev IERC20.transferFrom를 참조하세요.
    • 업데이트된 허용량을 나타내는 Approval 이벤트가 발생합니다. 이것은 EIP에서
    • 요구되는 바가 아닙니다. ERC20의 시작 부분에 있는 참고 사항을 참조하세요.
    • 요구사항:
      • senderrecipient는 영 주소가 될 수 없습니다.
      • sender의 잔고는 적어도 value 이상이어야 합니다.
      • 호출자는 sender의 토큰에 대해 최소한 amount 만큼의 허용량을
    • 가져야 합니다.
      */
      function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
      _transfer(sender, recipient, amount);
      _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
      return true;
      }

    /**

    • @dev 호출자에 의해 원자적(atomically)으로 spender에 승인된 허용량을 증가시킵니다.
    • 이것은 IERC20.approve에 기술된 문제에 대한 완화책으로 사용될 수 있는
    • approve의 대안입니다.
    • 업데이트된 허용량을 나타내는 Approval 이벤트가 발생합니다.
    • 요구사항:
      • spender는 영 주소가 될 수 없습니다.
        */
        function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
        return true;
        }

    /**

    • @dev 호출자에 의해 원자적으로 spender에 승인된 허용량을 감소시킵니다.
    • 이것은 IERC20.approve에 기술된 문제에 대한 완화책으로 사용될 수 있는
    • approve의 대안입니다.
    • 업데이트된 허용량을 나타내는 Approval 이벤트가 발생합니다.
    • 요구사항:
      • spender는 영 주소가 될 수 없습니다.
      • spender는 호출자에 대해 최소한 subtractedValue 만큼의 허용량을
    • 가져야 합니다.
      */
      function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
      _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
      return true;
      }

    /**

    • @dev amount만큼의 토큰을 sender에서 recipient로 옮깁니다.

    • 이는 transfer와 동일한 내부의(internal) 함수이며, 자동 토큰 수수료,

    • 차감 메커니즘 등의 구현에 사용 가능합니다.

    • Transfer 이벤트를 발생시킵니다.

    • 요구사항:

      • sender는 영 주소가 될 수 없습니다.
      • recipient은 영 주소가 될 수 없습니다.
      • sender의 잔고는 적어도 amount 이상이어야 합니다.
        */
        function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), “ERC20: transfer from the zero address”);
        require(recipient != address(0), “ERC20: transfer to the zero address”);

      _balances[sender] = _balances[sender].sub(amount);
      _balances[recipient] = _balances[recipient].add(amount);
      emit Transfer(sender, recipient, amount);
      }

    /** @dev amount만큼의 토큰을 생성하고 account에 할당합니다.

    • 전체 공급량을 증가시킵니다.

    • from이 영 주소로 설정된 Transfer 이벤트를 발생시킵니다.

    • 요구사항:

      • to는 영 주소가 될 수 없습니다.
        */
        function _mint(address account, uint256 amount) internal {
        require(account != address(0), “ERC20: mint to the zero address”);

      _totalSupply = _totalSupply.add(amount);
      _balances[account] = _balances[account].add(amount);
      emit Transfer(address(0), account, amount);
      }

    /**

    • @dev account로부터 amount만큼의 토큰을 파괴하고,
    • 전체 공급량을 감소시킵니다.
    • to가 영 주소로 설정된 Transfer 이벤트를 발생시킵니다.
    • 요구사항:
      • account는 영 주소가 될 수 없습니다.
      • account는 적어도 amount만큼의 토큰이 있어야 합니다.
        */
        function _burn(address account, uint256 value) internal {
        require(account != address(0), “ERC20: burn from the zero address”);

    _balances[account] = _balances[account].sub(value);
    _totalSupply = _totalSupply.sub(value);
    emit Transfer(account, address(0), value);
    }

    /**

    • @dev owner의 토큰에 대한 spender의 허용량을 amount만큼 설정합니다.

    • 이는 approve와 동일한 내부의(internal) 함수이며, 특정 하위 시스템에 대한

    • 자동 허용량 설정 등의 구현에 사용 가능합니다.

    • Approval 이벤트를 발생시킵니다.

    • 요구사항:

      • owner는 영 주소가 될 수 없습니다.
      • spender는 영 주소가 될 수 없습니다.
        */
        function _approve(address owner, address spender, uint256 value) internal {
        require(owner != address(0), “ERC20: approve from the zero address”);
        require(spender != address(0), “ERC20: approve to the zero address”);

      _allowances[owner][spender] = value;
      emit Approval(owner, spender, value);
      }

    /**

    • @dev account로부터 amount만큼의 토큰을 파괴하고,
    • 호출자의 허용량으로부터 amount만큼을 공제합니다.
    • _burn_approve를 참조하세요.
      */
      function _burnFrom(address account, uint256 amount) internal {
      _burn(account, amount);
      _approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
      }
      }
1개의 좋아요

트랜잭션을 생성하실 때 payload 값이 올바르게 처리되지 않았습니다. 인코딩된 함수값을 Numeric.hexStringToByteArray(String) 메소드로 처리하시면 올바른 payload 값이 나올겁니다. 이 부분은 제가 앞서 답변드린 부분에서 실수가 있었네요. String#getBytes()로 처리하면 문제가 발생할 수 있습니다.

트랜잭션에서 amount는 KLAY 전송량을 말합니다. transfer 함수를 실행하는데 KLAY는 필요없기 때문에 BigInteger.ZERO를 넣었습니다.

ERC20 transfer를 실행하는 코드를 다음과 같이 정리해봤습니다.

package com.example;

import com.klaytn.caver.Caver;
import com.klaytn.caver.crypto.KlayCredentials;
import com.klaytn.caver.methods.response.KlayTransactionReceipt;
import com.klaytn.caver.tx.gas.DefaultGasProvider;
import com.klaytn.caver.tx.manager.PollingTransactionReceiptProcessor;
import com.klaytn.caver.tx.manager.TransactionManager;
import com.klaytn.caver.tx.model.SmartContractExecutionTransaction;
import com.klaytn.caver.utils.ChainId;
import org.junit.Before;
import org.junit.Test;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.utils.Numeric;

import java.math.BigInteger;
import java.util.Arrays;

public class SmartContractExecution {

    private static final int CHAIN_ID = ChainId.BAOBAB_TESTNET;
    private static final DefaultGasProvider GAS_PROVIDER = new DefaultGasProvider();
    private static final BigInteger GAS_LIMIT = GAS_PROVIDER.getGasLimit();

    private Caver caver;
    private KlayCredentials sender;

    // Sample ERC20 contract address
    private String contractAddress = "0x069dc518E1d41A7C9F7bEbAA222f4652a6B27307";
    private long amount = 1_000_000;
    private String recipient = "0xRECIPIENT_ADDRESS";

    @Before
    public void setup() {
        sender = KlayCredentials.create("0xYOUR_PRIVATE_KEY");
        caver = Caver.build("https://api.baobab.klaytn.net:8651");
    }

    @Test
    public void erc20_ManualTransfer() {
        try {
            Function function = new Function(
                    "transfer",
                    Arrays.asList(
                            new Address(recipient),
                            new Uint256(amount)
                    ),
                    Arrays.asList(new TypeReference<Bool>() {
                    }));

            // Using web3j FunctionEncoder is fine
            String functionCallData = FunctionEncoder.encode(function);

            // Allow TransactionManager to handle nonce and signing
            TransactionManager manager = new TransactionManager.Builder(caver, sender)
                    .setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(caver, 1000, 10))
                    .setChaindId(CHAIN_ID)
                    .build();

            KlayTransactionReceipt.TransactionReceipt receipt = manager.executeTransaction(
                    SmartContractExecutionTransaction.create(sender.getAddress(),
                            contractAddress,
                            BigInteger.ZERO, // value should set to zero unless you are transferring KLAY to the contract
                            Numeric.hexStringToByteArray(functionCallData), // use Numeric.hexStringToByteArray for safe conversion
                            GAS_LIMIT)
            );

            System.out.println(receipt.getStatus()); // 0x1
        } catch (Exception e) {
            // handle exceptions
        }

    }

    @Test
    public void erc20_TransferUsingGeneratedClass() {
        try {
            // Alternative
            MyERC20 contract = MyERC20.load(contractAddress, caver, sender, CHAIN_ID, GAS_PROVIDER);
            KlayTransactionReceipt.TransactionReceipt receipt = contract.transfer(recipient, new BigInteger(Long.toString(amount))).send();
            System.out.println(receipt.getStatus());
        } catch (Exception e) {

        }
    }
}

2개의 좋아요

FYI, 위 코드 샘플에서 사용한 컨트랙트 코드는 여기에서, 그리고 MyERC20의 코드는 여기에서 확인하실 수 있습니다.

2개의 좋아요

친절하게 설명 해주셔서 감사합니다… 해결되었어요…^^

2개의 좋아요