Devahoy Logo
Published on
Solidity

การใช้ Custom Errors ในภาษา Solidity

solidity-custom-error
Discord

การทำ Error Handling น่าจะเป็น common ที่มีในทุกๆภาษา เพื่อจัดการกับ error case และ error message ซึ่ง Solidity ก็มีเช่นกัน ใน Solidity เรามักจะใช้ revert กับ require โดยวันนี้จะมาพูดถึงเรื่อง Custom Error ซึ่งจะใช้ในการทำ error ที่มันมากกว่าการใช้แค่ string เวลาเราจะใช้ revert('Error')

วิธีการทำ Custom Errors ในภาษา Solidity นั้นมีมาตั้งแต่ Solidity v0.8.4 ข้อดีคือลดค่า gas และก็ error message สื่อความหมาย ว่า error เพราะอะไร ข้อดีของ Custom Error คือมันค่อนข้าง dynamic เราสามารถใช้ type อื่นๆได้นอกเหนือจาก string

ตัวอย่าง

การใช้งาน custom error ง่ายๆ แค่ประกาศและตั้งชื่อเป็น error แบบนี้

error Unauthorized();

หรืออยากใช้ Error ร่วมกับ Parameters ก็สามารถกำหนดได้เหมือนการสร้าง function

error InsufficientBalance(uint256 available, uint256 required);

ทดลองสร้างโปรเจ็ค

ลองสร้างโปรเจ็คด้วย Hardhat ขึ้นมาแบบง่ายๆ เพื่อลองเทส Custom Error

# สร้างโฟลเดอร์ชื่อ custom-error
mkdir custom-error & cd custom-error

yarn init -y
yarn add hardhat -D

yarn hardhat
# หรือ npx hardhat

โดยผมเลือกเป็น Sample project ธรรมดาเลย

👷 Welcome to Hardhat v2.10.1 👷‍

✔ What do you want to do? · Create a JavaScript project
✔ Hardhat project root: · /Users/chai/my-folder
✔ Do you want to add a .gitignore? (Y/n) · y
✔ Do you want to install this sample project's dependencies with yarn (...)? (Y/n) · y

สร้างไฟล์ CustomError.sol ขึ้นมา มี function ง่ายๆ คือ hello() และ hi(uint256 amount) ที่จะ revert custom error

CustomError.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

contract CustomError {
    uint256 public unlockTime;
    address payable public owner;

    error Unauthorized();
    error InvalidAmount(uint256 amount, address _address);

    function hello() public pure {
        revert Unauthorized();
    }

    function hi(uint256 amount) public view returns (bool) {
        // 10 wei
        if (amount > 10) {
            revert InvalidAmount(amount, msg.sender);
        }
        return true;
    }
}

สร้างไฟล์ test/custom-error.test.js ขึ้นมา

const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { expect } = require('chai');

describe('CustomError', function () {
  async function deployContractFixture() {
    const [owner] = await ethers.getSigners();

    const CustomErrorContract = await ethers.getContractFactory('CustomError');
    const contract = await CustomErrorContract.deploy();
    return { contract, owner };
  }

  it('should revert with custom error "Unauthorized"', async () => {
    const { contract } = await loadFixture(deployContractFixture);

    await expect(contract.hello()).to.be.revertedWithCustomError(
      contract,
      'Unauthorized'
    );
  });

  it('should revert with custom error "InvalidAmount"', async () => {
    const { contract, owner } = await loadFixture(deployContractFixture);

    const TWENTY_WEI = 20;

    await expect(contract.hi(TWENTY_WEI))
      .to.be.revertedWithCustomError(contract, 'InvalidAmount')
      .withArgs(TWENTY_WEI, owner.address);
  });

  it('should say hi and return TRUE', async () => {
    const { contract } = await loadFixture(deployContractFixture);

    const FIVE_WEI = 5;

    const hi = await contract.hi(FIVE_WEI);
    await expect(hi).to.be.true;
  });
});

จากไฟล์เทสคือ

  • เราใช้ loadFixture ซึ่งเป็น helper ของ hardhat จัดการเรื่อง fixture ให้เรา
  • ใช้ revertedWithCustomError ซึ่งปกติ ถ้า revert เราใช้ revertedWith()
await expect(contract.hello()).to.be.revertedWithCustomError(
  contract,
  'Unauthorized'
);
  • และสำหรับ Custom Error ที่มีหลาย parameter ก็เลย เทสด้วย .withArgs เพื่อเช็คว่า argument ที่ส่งไป Custom Error ถูกต้องหรือไม่
await expect(contract.hi(TWENTY_WEI))
  .to.be.revertedWithCustomError(contract, 'InvalidAmount')
  .withArgs(TWENTY_WEI, owner.address);

ส่วน Test สุดท้าย ไม่มีอะไรมาก call hi() ถ้าส่ง wei ไปไม่ถึง 10 ก็ ไม่โดน revert ได้ return true กลับมา ทดลองรันไฟล์เทส

yarn hardhat test test/custom-error.test.js

# หรือ npx hardhat test test/custom-error.test.js

จะได้ผลลัพธ์

CustomError
  ✔ should revert with custom error "Unauthorized" (519ms)
  ✔ should revert with custom error "InvalidAmount"
  ✔ should say hi and return TRUE


3 passing (540ms)

เป็นอันเรียบร้อย 🎉

สรุป

บทความนี้ก็เป็นการแนะนำ Custom Error แบบคร่าวๆ ว่าการใช้งานทำยังไง และเราจะ design custom error แบบไหน ให้มันสื่อหรือเข้าใจง่าย รวมถึงตัวอย่างการเทส เงื่อนไขการ revert แบบคร่าวๆ โดยใช้ revertedWithCustomError หวังว่าบทความนี้จะมีประโยชน์ไม่มากก็น้อย

Reference

Authors
Discord