체인의정석

스마트컨트렉트 디버깅 하기) erc721 burn에 대한 테스트 오류 해결 본문

블록체인/NFT & BRIDGE

스마트컨트렉트 디버깅 하기) erc721 burn에 대한 테스트 오류 해결

체인의정석 2022. 2. 14. 16:24
728x90
반응형

테스트 코드를 한창 쓰고 있는데 

     Error: VM Exception while processing transaction: reverted with reason string 'ERC721: owner query for nonexistent token'

위와 같은 에러가 났다.

문제는 해당 에러는 여기저기에 다 분포되어 있기 때문에 컨트렉트와 테스트 스트립트 곳곳에 로그를 찍으면서 모두 확인해보았다.

import "hardhat/console.sol";

.
.
.
.

    function ownerOf(uint256 tokenId) public view returns (address) {
        address owner = _tokenOwner[tokenId];
        console.log("owner OF >>>>", tokenId);
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        console.log("pass owner OF >>>", tokenId);
        return owner;
    }

위와 같이 하드햇에서는 콘솔을 찍을 수가 있는데 예전에 머리속으로 다 돌려보면서 에러를 찾던때를 생각해보니 정말 세상 좋아졌다는게 느껴진다. 아무튼 모든 곳에 다 콘솔을 찍어가며 어느 레벨에서 오류가 나는지 봤는데 함수가 끝까지 다 통과될때까지 에러가 안났다.

 

결국 정체는 마지막에 내가 붙인

        await expect(bridge.connect(addr2).withdraw(addr1.address, werc721.address, tokenData, tokenType721))
        .to.emit(bridge, "Withdraw")
        .withArgs(addr2.address, addr1.address, exampleERC721.address, werc721.address, tokenData, tokenType721, originChain, wrapperChain, 1);
        expect(await werc721.ownerOf(tokenId)).to.equal(ethers.constants.AddressZero);

여기에서의 expect 부분이였다. burn을 진행하고 나서 0x0000 주소로 리턴을 시켜야 하는데 oxoooo 주소로 조회를 하면 내부적으로 맨위의 에러가 리턴되게 되기 때문이다.

 

따라서 에러가 발생하는 경우 통과하는것으로 테스트 코드를 바꾸기로 하였다.

 

        await expect(bridge.connect(addr2).withdraw(addr1.address, werc721.address, tokenData, tokenType721))
        .to.emit(bridge, "Withdraw")
        .withArgs(addr2.address, addr1.address, exampleERC721.address, werc721.address, tokenData, tokenType721, originChain, wrapperChain, 1);
        expect(await werc721.ownerOf(tokenId))
        .to.be.revertedWith(
          "ERC721: owner query for nonexistent token"
        );

위와 같이 테스트 코드를 바꾸면 토큰 id를 조회했을 때 0x0000 이 리턴되는게 아닌 위의 에러메세지가 리턴되는것을 확인하면 된다.

burn에 대한 테스트는 위와 같이 하면 된다는걸 배웠다.

 

위와 같이 해도 에러가 또 발생했는데, 그 이유는 테스트코드를 작성할때 실패하는 케이스는 성공하는 케이스와 따로 분리하여 체크해줘야 하기 때문이다. 최종적으로 아래와 같이 실행을 한 후 실패를 해야 할때 실패를 리턴해 주도록 만들어야 제대로 된 테스트가 가능하다.

 

    describe("withdraw", async () => {
      it("Should withdraw correctly", async () => {
        let tokenData = abiCoder.encode(["uint"],[tokenId]);
        await expect(bridge.connect(addr2).withdraw(addr1.address, werc721.address, tokenData, tokenType721))
        .to.emit(bridge, "Withdraw")
        .withArgs(addr2.address, addr1.address, exampleERC721.address, werc721.address, tokenData, tokenType721, originChain, wrapperChain, 1);
      })

      it("Should return error while finding burned token", async () => {
        await expect(werc721.ownerOf(tokenId))
        .to.be.revertedWith(
          "ERC721: owner query for nonexistent token"
        );
      })
    })

 

 

또 하나 , 배운점은 테스트 코드에 로그를찍어 위치를 파악할 때는 최상단의 테스트 코드 레벨에서 부터 어디서 에러가 나는지 보고 컨트렉트 레벨 , 상속받거나 호출하는 컨트렉트 레벨 순서대로 에러 위치를 파악해야 한다는 점이다.

 

만약 그랬다면 , 위치를 더 빨리 파악해서 테스트 코드 수정 기간이 짧았을 텐데 다음부터는 테스트 코드 상의 에러 위치부터 파악한 후 컨트렉트 레벨까지 들어가서 디버깅을 해야겠다.

728x90
반응형
Comments