블록 데이터에서의 ERC20과 ERC721 구분

iwin1203·2022년 10월 1일
0

블록체인

목록 보기
3/11

요약

  • 블록 데이터 상에서 ERC20과 721 구분법을 살펴보았다.

  • raw 데이터를 통해 구분하는 것이 가장 비용이 적게 발생하기 때문이다.

  • 로그(이벤트)를 통해 JSON RPC로 두 토큰을 구분할 수 있음을 이해하고 구현해보았다.

Geth JSON RPC api를 써보다가...


블록 정보를 JSON RPC로 받아오면 transactions 필드에서 다음과 같은 값들을 볼 수 있다.

eth_getBlockByNumber
{
        "blockHash": "0x570bacc6b30fcaff821fccc70126c684a1ea8d76b2ffaadb96504cdb176afc32",
        "blockNumber": "0xea5398",
        "from": "0x25eaff5b179f209cf186b1cdcbfa463a69df4c45",
        "gas": "0xf618",
        "gasPrice": "0x258325ddf",
        "maxFeePerGas": "0xe8d4a51000",
        "maxPriorityFeePerGas": "0x13e49ef00",
        "hash": "0x360481d167ae675925baf17f8911b7768a4e7805dd566da5eddf1d1fd872c326",
        "input": "0x",
        "nonce": "0x275da",
        "to": "0xf19a206f02a5a3d1476065423b4db1b301f4aa4f",
        "transactionIndex": "0x0",
        "value": "0x68bdb20d20f400",
        "type": "0x2",
        "accessList": [],
        "chainId": "0x1",
        "v": "0x0",
        "r": "0x524f25e68917bdbb39f878ad172454e4d8654070f346cebdf30445ae76b127e",
        "s": "0x6b9a5775c9dcbfe81b348aa6d4ac34efde871774d7d9c7efd7d2cc9319a16774"
    },
    {
        "blockHash": "0x570bacc6b30fcaff821fccc70126c684a1ea8d76b2ffaadb96504cdb176afc32",
        "blockNumber": "0xea5398",
        "from": "0xb05932b109f4f19cf0069e5d38c78b3a5b482970",
        "gas": "0x3d090",
        "gasPrice": "0x2540be400",
        "hash": "0x0564a0c209cf7d7dd6be15eff1f35d392694b1cdd2ec6c42f11af863fc105256",
        "input": "0xa9059cbb0000000000000000000000003f37fe1612c282ac0a8824936fc3c785fe1267340000000000000000000000000000000000000000000000000000000029b92700",
        "nonce": "0x7f1",
        "to": "0xdac17f958d2ee523a2206206994597c13d831ec7",
        "transactionIndex": "0x1",
        "value": "0x0",
        "type": "0x0",
        "v": "0x26",
        "r": "0xf702f56361117b157e05a34ff4376647ad3867c5a3e3548c518d868c4bfad274",
        "s": "0x3421e5686207239bcf17adc53b4db1b6c28f2056455c7be90f3c1079ed1ed401"
    },

...

그런데 문득 여기서 ERC20이나 721 토큰의 거래를 어떻게 찾아내지? 라는 생각이 들었다.

처음에는 트랜잭션 해시를 통해 abi를 뽑아내고 abi의 method들을 IERC 표준과 비교하는 방법을 떠올렸다. 그러나 이 방법은 매우 이더스캔 의존적이며 이더스캔 내에서도 소스코드가 verified 되어있어야 한다는 큰 단점이 있었다. 나는 JSON-RPC api 단에서의 탐지 방법을 원했다.



로그로 확인할 수 있지 않을까?


다음으로 든 생각은 로그를 통해 확인하는 것이었다. 다행히 구글링해보니 이 방법을 추천해주는 몇몇 레퍼런스를 찾을 수 있었다.


ERC20 표준 이벤트

ERC721 표준 이벤트


얼핏 보면 비슷해보이지만, Transfer의 경우 마지막 인자의 indexed 여부가 다르다.
Approval도 마찬가지다.

그럼, 트랜잭션 해시를 통해 트랜잭션 Receipt를 받아온 뒤 log를 확인해봄으로써 이게 그냥 송금이나 토큰과 관련 없는 트랜잭션인지, 아니면 ERC20 721 트랜잭션인지 확인할 수 있을 것이다!



Transfer 이벤트 로그 확인 #1


Transfer과 Approval의 경우 ERC20과 721의 시그니처가 동일하다.
시그니처에는 indexed 여부는 표시되지 않고 함수명과 인자 타입만 표시되기 때문이다.

Transfer(address,address,uint256)
Approval(address,address,uint256)


다음 코드로 시그니처를 확인해보았다.

Web3.utils.keccak256("Transfer(address,address,uint256)")

// 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

그리고 위에서 가져왔던 트랜잭션 리스트에서 해시들을 전부 뽑아 receipt를 가져왔다.

eth_getTransactionByHash 중 하나의 receipt
{
        "jsonrpc": "2.0",
        "id": 1,
        "result": {
            "blockHash": "0x570bacc6b30fcaff821fccc70126c684a1ea8d76b2ffaadb96504cdb176afc32",
            "blockNumber": "0xea5398",
            "contractAddress": null,
            "cumulativeGasUsed": "0xbc782",
            "effectiveGasPrice": "0x1aeeb67df",
            "from": "0xb11d83c6f375e51b2a4085a8ff315063640e2aae",
            "gasUsed": "0x216d7",
            "logs": [
                {
                    "address": "0x00000000006c3852cbef3e08e8df289169ede581",
                    "topics": [
                        "0x9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31",
                        "0x000000000000000000000000de72e54a672924d1ed17af7fb12958cf8ac09ebc",
                        "0x000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00"
                    ],
                    "data": "0x11c505251d46f6cb9f637fdc2f2f7c1420fa061deccb82ddb44eb3e3b8f9d599000000000000000000000000b11d83c6f375e51b2a4085a8ff315063640e2aae0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000160c404b2b49cbc3240055ceaee026df1e8497a0000000000000000000000000000000000000000000000000000000000000056600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020dcd3742c20000000000000000000000000000de72e54a672924d1ed17af7fb12958cf8ac09ebc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e35fa931a00000000000000000000000000008de9c5a032463c561423387a9648c5c7bcc5bc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000002c7240f1887cd0a4380207b8a774dbee332e4bd5",
                    "blockNumber": "0xea5398",
                    "transactionHash": "0xaef578940d86aee5146fe519e85e12523df7d841fe5cdc51cea21ce2929acbfd",
                    "transactionIndex": "0x7",
                    "blockHash": "0x570bacc6b30fcaff821fccc70126c684a1ea8d76b2ffaadb96504cdb176afc32",
                    "logIndex": "0x9",
                    "removed": false
                },
                {
                    "address": "0x160c404b2b49cbc3240055ceaee026df1e8497a0",
                    "topics": [
                        "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
                        "0x000000000000000000000000de72e54a672924d1ed17af7fb12958cf8ac09ebc",
                        "0x0000000000000000000000000000000000000000000000000000000000000000",
                        "0x0000000000000000000000000000000000000000000000000000000000000566"
                    ],
                    "data": "0x",
                    "blockNumber": "0xea5398",
                    "transactionHash": "0xaef578940d86aee5146fe519e85e12523df7d841fe5cdc51cea21ce2929acbfd",
                    "transactionIndex": "0x7",
                    "blockHash": "0x570bacc6b30fcaff821fccc70126c684a1ea8d76b2ffaadb96504cdb176afc32",
                    "logIndex": "0xa",
                    "removed": false
                },
                {
                    "address": "0x160c404b2b49cbc3240055ceaee026df1e8497a0",
                    "topics": [
                        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                        "0x000000000000000000000000de72e54a672924d1ed17af7fb12958cf8ac09ebc",
                        "0x000000000000000000000000b11d83c6f375e51b2a4085a8ff315063640e2aae",
                        "0x0000000000000000000000000000000000000000000000000000000000000566"
                    ],
                    "data": "0x",
                    "blockNumber": "0xea5398",
                    "transactionHash": "0xaef578940d86aee5146fe519e85e12523df7d841fe5cdc51cea21ce2929acbfd",
                    "transactionIndex": "0x7",
                    "blockHash": "0x570bacc6b30fcaff821fccc70126c684a1ea8d76b2ffaadb96504cdb176afc32",
                    "logIndex": "0xb",
                    "removed": false
                }
            ],
            "logsBloom": "0x000000000000000000000000000000200000000000000000000000000000008000010000400000000000000000000000000000000000000000000000002000000000000800000000000000080080000000000000000000002000000000000000000000000202000000000004000008000000000000000000000000100000000000000000000200000000000000000000000000000000000000000000000000008a0000000080000000000000000000000000000000000002000000000000000000000002000008000000000000000000000000400000000000000800000020000410000000000000000000000000004000000080000800000000000000000000",
            "status": "0x1",
            "to": "0x00000000006c3852cbef3e08e8df289169ede581",
            "transactionHash": "0xaef578940d86aee5146fe519e85e12523df7d841fe5cdc51cea21ce2929acbfd",
            "transactionIndex": "0x7",
            "type": "0x2"
        }
    },
```

로그가 잘 출력되는 걸 확인했다.


이 로그들 중에서 방금 구한 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
을 topic의 첫 번째 원소로 가지고 있는 경우를 찾아보았다.


다음과 같은 로그가 발견되었다.

{
   "address":"0xd31a59c85ae9d8edefec411d448f90841571b89c",
   "topics":[
      "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
      "0x0000000000000000000000000000000000000000000000000000000000000000",
      "0x000000000000000000000000afc418c0ec9434ed02a667165ac6733ebd25e252"
   ],
   "data":"0x000000000000000000000000000000000000000000000000000000028fa6ae00",
   "blockNumber":"0xea5398",
   "transactionHash":"0xefbdb637964d57df2150f20d96f03bfcb15995a4eb76bc23373ae1f67af68067",
   "transactionIndex":"0x5",
   "blockHash":"0x570bacc6b30fcaff821fccc70126c684a1ea8d76b2ffaadb96504cdb176afc32",
   "logIndex":"0x7",
   "removed":false
}

topics 부분만 상세히 보자.

   "topics":[
      "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
      "0x0000000000000000000000000000000000000000000000000000000000000000",
      "0x000000000000000000000000afc418c0ec9434ed02a667165ac6733ebd25e252"
   ],

topics[0]은 위에서 우리가 구한 Transfer의 signature이다.
signature를 제외한 topic은 두 개이다.

따라서 위 topics를 통해 내릴 수 있는 결론은
Transfer(address,address,uint256) 이고 앞의 두 인자에 indexed가 붙는다.
결과적으로 Transfer(address indexed,address indexed,uint256) 가 된다.
얘는 ERC20의 이벤트 표준과 일치한다.


이제 이더스캔에서 확인해보자. 해당 address를 따라가면,

ERC20 토큰 맞다!



Transfer 이벤트 로그 확인 #2


시그니처가 동일한, 다른 receipt를 가져왔다.

{
   "address":"0x570d4dab720d00c2ea702afee208e7b16099786d",
   "topics":[
      "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
      "0x000000000000000000000000679f1c7de559cf077af7bff879e8e8fed76a0b58",
      "0x000000000000000000000000dcd9eb477943518ff5911d405373cfb3b2711ff5",
      "0x00000000000000000000000000000000000000000000000000000000000003c8"
   ],
   "data":"0x",
   "blockNumber":"0xea5398",
   "transactionHash":"0x34e77f5ebd0cd8b82efadcf05fd59a7bc9026a234215e3faf299255a20a0fd80",
   "transactionIndex":"0x4",
   "blockHash":"0x570bacc6b30fcaff821fccc70126c684a1ea8d76b2ffaadb96504cdb176afc32",
   "logIndex":"0x5",
   "removed":false
}

얘는 위 경우와 달리 topics의 길이가 4 (signature한개 + indexed 세개)이다.
그럼 Transfer(address indexed,address indexed, uint256 indexed)가 된다.

이는 ERC721의 이벤트 형태와 동일하다.


마찬가지로 address를 따라가보면

ERC721 맞다.



정리


Transfer 이벤트에 대해, tx receipt의 로그의 topic을 통해 ERC20과 721을 구분할 수 있음을 보였다.
Approval 이벤트도 같은 결과를 보인다.

물론 이 방법은 완전히 이벤트 종속적이기에 이벤트를 emit하지 않는 경우에는 전혀 사용할 수 없는 방법이다. 또, (그럴 일은 적겠지만) 유저가 메서드 오버로딩을 위해 ERC20에 ERC721과 동일한 이벤트 형식을 emit하게 만들어버리면 무용지물이 된다.



레퍼런스

0개의 댓글