Todo:
0. 보상 로직 오류 수정
1. 테스트 코드 작성
2. 보상 로직 modifier 지정
이건 오류는 아닌데 먼저 적자면
distributRewrd 함수명을 updateReward 로 변경해줬다.
(TinyBank.sol 파일에서 3개만 수정하면 됨)
❯ git commit -m "Rename distributeReward to updateReward"
❯ 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 -m "fix(updateReward): prevent division by zero when totalStaked is zero"
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 add test/TinyBank.ts
❯ test(reward): Add test for updateReward function
solidity만의 특징으로, 함수의 동작을 변경할 수 있는 기능으로, 함수 전/후에 특정 행동을 넣어줄 수 있다. => [Solidity] modifier란? 에다가 따로 정리해둠.
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 log
❯ git commit -m "refactor: convert updateReward from function to modifier"
괜찮은 커밋 메세지 남기기가 아직 너무 어렵다.. 이건 괜찮은걸까..?