8-1. Reward 기능 테스트 & 수정

동동주·2025년 10월 31일


Todo:
0. 보상 로직 오류 수정
1. 테스트 코드 작성
2. 보상 로직 modifier 지정


0. 보상 로직 오류 수정

Rename distributeReward to updateReward

이건 오류는 아닌데 먼저 적자면
distributRewrd 함수명을 updateReward 로 변경해줬다.
(TinyBank.sol 파일에서 3개만 수정하면 됨)

git commit

❯ git commit -m "Rename distributeReward to updateReward"   

Division or modulo division by zero 오류

❯ npx hardhat test 를 할 경우

(Division or modulo division by zero)

오류가 나는 것을 볼 수 있다.
이는 updateReward(=distributRewrd였던 것) 함수 코드에

> uint256 reward = (blocks * rewardPerBlock * staked[to]) / totalStaked;

해당 부분에서 처음 staking을 하면 totalStaked 가 0이 된다.
이 때는 staking한 양도 없으므로 staked[to] 역시 0이 된다.
따라서 아래처럼 수정해주면

    function withdraw(uint256 _amount) external {
        require(staked[msg.sender] >= _amount, "insufficient staked token" );
        updateReward(msg.sender); //먼저 업데이트를 함
        stakingToken.transfer(_amount, msg.sender);
        staked[msg.sender] -= _amount;
        totalStaked -= _amount; //그 뒤에 totalStaked에서 제외됨
        emit Withdraw(_amount, msg.sender);
    }

잘 테스트가 된다.

git commit

❯ git commit -m "fix(updateReward): prevent division by zero when totalStaked is zero"   



1. 테스트 코드 작성

test/TinyBank.ts

describe("reward", async () => {
    it("should reward 1MT every blocks", async () => {
      const signer0 = signers[0];
      const stakingAmount = hre.ethers.parseUnits("50", DECIMALS);
      await myTokenC.approve(await tinyBankC.getAddress(), stakingAmount);
      await tinyBankC.stake(stakingAmount);

      //블럭 증가시키려면 tx 필요 (테스트환경에서는)
      //무의미한 tx 발생시키기
      const BLOCKS = 5n;
      const transferAmount = hre.ethers.parseUnits("1", DECIMALS);
      for(var i=0; i<BLOCKS; i++) {
        await myTokenC.transfer(transferAmount, signer0.address);
      }

      await tinyBankC.withdraw(stakingAmount);
      expect(await myTokenC.balanceOf(signer0.address)).equal(
        hre.ethers.parseUnits((BLOCKS+ MINTING_AMOUNT +1n).toString()) 
      );
    });
  });

코드 흐름

  • signer0의 토큰을 stake
    stakingAmount만큼 stake를 해주고, 이를 위해 권한을 가져옴 (approve)

  • stake - withdraw 사이에 블럭 생성
    현재 테스트환경은 일반적인 블럭체인 서비스들과 달리
    tx가 생겨야만 블럭이 증가된다.
    따라서 무의미한 tx를 BLOCKS = 5 만큼 생성해줬다.
    * 이후 총 계좌 잔액 계산에서 MINTING_AMOUNT와 같이 계산하기 위해 5n(빅넘버)로 지정함.

  • withdraw 후 예상 잔액 체크
    withdraw 를 실행하면 내부에서

    updateReward(msg.sender);

    가 실행되고, ( 만든 블록 수+ withdraw (=1n) 횟수 ) 만큼의 보상 + 초기 토큰 발행량 인지 확인함

git commit

❯ git add test/TinyBank.ts
❯ test(reward): Add test for updateReward function



2. 보상 로직 modifier 지정

+ modifier란?

solidity만의 특징으로, 함수의 동작을 변경할 수 있는 기능으로, 함수 전/후에 특정 행동을 넣어줄 수 있다. => [Solidity] modifier란? 에다가 따로 정리해둠.

contracts/TinyBank.sol

modifier updateReward(address to) {
        if (staked[to] > 0) {
        uint256 blocks = block.number - lastClaimedBlock[to];
        uint256 reward = (blocks * rewardPerBlock * staked[to]) / totalStaked ;
        stakingToken.mint(reward, to);
        }
        lastClaimedBlock[to] = block.number;
        _; //호출한 함수의 코드가 들어가는 위치 지정
    }
  • modifier로 바꾸면서 internal 지워주고(중복됨), 함수 실행할 부분 지정
    * modifier의 scope는 internal이다.

  • 또한 stake withdraw 함수의 external 옆에 modifier를 적어주고, 함수 내부에 있는 리워드 함수 호출 부분을 지운다.

 function withdraw(uint256 _amount) external updateReward(msg.sender) { 
 .
 .
 .
//updateReward(msg.sender); //삭제

git commit

❯ git log                                                     
❯ git commit -m "refactor: convert updateReward from function to modifier" 

괜찮은 커밋 메세지 남기기가 아직 너무 어렵다.. 이건 괜찮은걸까..?

profile
배운 내용 정리&기록, 스크랩

0개의 댓글