본문 바로가기
카테고리 없음

업그레이더블 컨트랙트 hardhat testCode.02

by gun_poo 2023. 8. 29.

Test 항목

  1. Initial Deployment
  2. Role-Based Access Control
  3. Proxy Upgrade Functionality
  4. New Functionality in V2
  5. Edge Case Testing
  6. Stress Testing
  7. Maximum Supply Test

세부 항목

  • Initial Deployment
    • 배포 후 토큰 세부 정보가 올바르게 설정되어야 함
  • Role-Based Access Control
    • MINTER_ROLE 권한이 없는 발신자가 민팅을 시도하면 되돌려야 함
    • 소유자가 아닌 사람이 일시 중지 및 해제를 시도하면 되돌려야 함
    • 일시 중지 상태에서 토큰 전송을 시도하면 되돌려야 함
    • 잘못된 매개변수로 전송 함수를 호출하면 되돌려야 함
  • Proxy Upgrade Functionality
    • 업그레이드는 잔액과 상태에 영향을 미치지 않아야 함
    • 업그레이드 후 올바른 구현체를 가리켜야 함
  • New Functionality in V2
    • 토큰은 정확하게 소각되어야 함
    • 높은 가스 비용을 처리할 수 있어야 함
  • Edge Case Testing
    • 매우 작은 양의 민트를 처리할 수 있어야함
  • Stress Testing
    • 여러 번의 민트 작업을 처리할 수 있어야함
  • Maximum Supply Test
    • 최대 토큰 공급량 처리 테스트

 

1.  Initial Deployment

토큰 세부 정보 체크

describe("Initial Deployment", function () {
      it("should set the token details after deployment", async function () {
        const { erc20ImplInstance } = await loadFixture(deployFixture);
        expect(await erc20ImplInstance.name()).to.equal("BittoToken");
        expect(await erc20ImplInstance.symbol()).to.equal("BITTO");
        expect((await erc20ImplInstance.totalSupply()).toString()).to.equal(
          ethers.parseUnits("10000", 18)
        );
      });
    });

2. Role-Based Access Control

MINTER_ROLE 권한이 없는 발신자가 민팅을 시도하면 되돌려야 함

it("should revert minting when sender does not have MINTER_ROLE", async function () {
        const { owner, recipient } = await getSigners();
        const { erc20ImplInstance } = await loadFixture(deployFixture);
        const MINTER_ROLE = await erc20ImplInstance.MINTER_ROLE();
        const mintAmount = ethers.parseEther("1000");

        const expectedErrorMessage =
          `AccessControl: account ${owner.address.toLowerCase()} is missing role ${MINTER_ROLE.toLowerCase()}`.toLowerCase();

        await expect(
          erc20ImplInstance.connect(owner).mint(recipient.address, mintAmount)
        ).to.be.revertedWith(new RegExp(expectedErrorMessage, "i"));
      });

contractInstance.connect(address).function(); 컨트랙트인스턴스에 함수를 address로 실행

 

소유자가 아닌 사람이 일시 중지 및 해제를 시도하면 되돌려야 함

it("should revert pausing and unpausing when sender is not the owner", async () => {
        const { minter } = await getSigners();
        const { erc20ImplInstance } = await loadFixture(deployFixture);

        // Try to pause the contract by non-owner
        await expect(
          erc20ImplInstance.connect(minter).pause()
        ).to.be.revertedWith("Ownable: caller is not the owner");

        // Try to unpause the contract by non-owner
        await expect(
          erc20ImplInstance.connect(minter).unpause()
        ).to.be.revertedWith("Ownable: caller is not the owner");
      });

 

일시 중지 상태에서 토큰 전송을 시도하면 되돌려야 함

it("should revert token transfer when paused", async () => {
        const { owner, recipient } = await getSigners();
        const { erc20ImplInstance } = await loadFixture(deployFixture);

        let transferAmount = ethers.parseEther("500");

        // Pause Contract
        await erc20ImplInstance.pause();

        // Attempt Transfer and expect it to be reverted.
        await expect(
          erc20ImplInstance.transfer(recipient.address, transferAmount)
        ).to.be.reverted;
      });

 

잘못된 매개변수로 전송 함수를 호출하면 되돌려야 함

it("should revert when transfer is called with invalid parameters", async () => {
        const { erc20ImplInstance } = await loadFixture(deployFixture);

        // Try to call a function with invalid parameters
        await expect(
          erc20ImplInstance.transfer(
            "0x0000000000000000000000000000000000000000",
            ethers.parseEther("10")
          )
        ).to.be.reverted;
      });

 

3. Proxy Upgrade Functionality

업그레이드는 잔액과 상태에 영향을 미치지 않아야 함

 it("Upgrade should not affect balance and state", async () => {
        const { owner, minter, recipient } = await getSigners();
        const {
          erc20ImplInstance,
          erc20ImplV2Address,
          transparentUpgradeableInstance,
          erc20ImplV2Instance,
        } = await loadFixture(deployFixture);

        // Mint tokens to minter
        const mintAmount = ethers.parseEther("1000");
        await erc20ImplInstance
          .connect(minter)
          .mint(recipient.address, mintAmount);

        // Check minter balance before upgrade
        const minterBalanceBeforeUpgrade = await erc20ImplInstance.balanceOf(
          recipient.address
        );

        expect(minterBalanceBeforeUpgrade).to.equal(mintAmount);

        // Upgrade the proxy to V2 implementation
        await transparentUpgradeableInstance.upgradeTo(erc20ImplV2Address);

        const minterBalanceAfterUpgrade = await erc20ImplV2Instance.balanceOf(
          recipient.address
        );
        expect(mintAmount).to.equal(minterBalanceAfterUpgrade);
      });

총 발행량과 업그레이드 이전의 밸런스 체크 확인

총 발행량과 업그레이드 이후의 밸런스 체크 확인

 

업그레이드 후 올바른 구현체를 가리켜야 함

it("should point to correct implementation after upgrade", async () => {
        const {
          transparentUpgradeableInstance,
          erc20ImplV2Address,
          erc20Proxy,
        } = await loadFixture(deployFixture);

        await transparentUpgradeableInstance.upgradeTo(erc20ImplV2Address);

        // Check the implementation address after upgrade
        const currentImplementation = await erc20Proxy.getImplementation();

        expect(currentImplementation).to.equal(erc20ImplV2Address);
      });

4. New Functionality in V2

토큰은 정확하게 소각되어야 함

it("should burn tokens correctly", async () => {
        const { owner } = await getSigners();
        const {
          transparentUpgradeableInstance,
          erc20ImplV2Instance,
          erc20ImplV2Address,
        } = await loadFixture(deployFixture);

        await transparentUpgradeableInstance.upgradeTo(erc20ImplV2Address);

        let initialBalance = await erc20ImplV2Instance.balanceOf(owner.address);
        let burnAmount = ethers.parseEther("100");

        await erc20ImplV2Instance.burn(burnAmount);
        let finalBalance = await erc20ImplV2Instance.balanceOf(owner.address);
        // BigInt 타입으로 변환해서 계산
        let initialBalanceBigInt = BigInt(initialBalance.toString());
        let burnAmountBigInt = BigInt(burnAmount.toString());
        let finalBalanceBigInt = BigInt(finalBalance.toString());
        let expectedFinalBalanceBigInt =
          initialBalanceBigInt - burnAmountBigInt;

        expect(finalBalanceBigInt).to.equal(expectedFinalBalanceBigInt);
      });

높은 가스 비용을 처리할 수 있어야 함

it("should handle high gas prices", async () => {
        const {
          transparentUpgradeableInstance,
          erc20ImplV2Instance,
          erc20ImplV2Address,
        } = await loadFixture(deployFixture);
        await transparentUpgradeableInstance.upgradeTo(erc20ImplV2Address);
        let burnAmount = ethers.parseEther("100");

        // High gas price simulation
        const overrides = {
          gasPrice: ethers.parseUnits("100", "gwei"),
        };

        // Attempt to burn tokens with high gas price
        await expect(erc20ImplV2Instance.burn(burnAmount, overrides)).to.not.be
          .reverted;
      });

의미가 있나?

5. Edge Case Testing

매우 작은 양의 민트를 처리할 수 있어야함

// Edge Case Testing
      it("should handle small mint amounts", async () => {
        const { minter, recipient } = await getSigners();
        const {
          transparentUpgradeableInstance,
          erc20ImplV2Instance,
          erc20ImplV2Address,
        } = await loadFixture(deployFixture);
        await transparentUpgradeableInstance.upgradeTo(erc20ImplV2Address);

        let mintAmount = ethers.parseUnits("0.0000000001", "ether");

        // Attempt to mint a very small amount of tokens
        await expect(
          erc20ImplV2Instance
            .connect(minter)
            .mint(recipient.address, mintAmount)
        ).to.not.be.reverted;
      });

6. Stress Testing

여러 번의 민트 작업을 처리할 수 있어야함

// Stress Testing
      it("should handle many mint operations", async () => {
        const { minter, recipient } = await getSigners();
        const {
          transparentUpgradeableInstance,
          erc20ImplV2Instance,
          erc20ImplV2Address,
        } = await loadFixture(deployFixture);
        await transparentUpgradeableInstance.upgradeTo(erc20ImplV2Address);

        let mintAmount = ethers.parseUnits("1", "ether");

        // Attempt to mint tokens many times in a loop
        for (let i = 0; i < 100; i++) {
          await expect(
            erc20ImplV2Instance
              .connect(minter)
              .mint(recipient.address, mintAmount)
          ).to.not.be.reverted;
        }
      });

7. Maximum Supply Test

최대 토큰 공급량 처리 테스트

it("should handle maximum token supply", async () => {
        const { minter, recipient } = await getSigners();
        const {
          transparentUpgradeableInstance,
          erc20ImplV2Instance,
          erc20ImplV2Address,
        } = await loadFixture(deployFixture);
        await transparentUpgradeableInstance.upgradeTo(erc20ImplV2Address);

        //2의 256승에서 1을 뺀 결과. 이 값은 uint256에서 가능한 최대값
        let maxSupplyAmount = ethers.parseUnits(
          "115792089237316195423570985008687907853269984665640564039457584007913129639935",
          "wei"
        );

        // Attempt to mint the maximum possible amount of tokens
        await expect(
          erc20ImplV2Instance
            .connect(minter)
            .mint(recipient.address, maxSupplyAmount)
        ).to.be.reverted;
      });

최대 공급량이 있는데 배포시 초기 공급량 때문에 revert가 떠야함

 

 

추가 테스트가 생기면 추가 예정

댓글