블록체인
[Solidity][코드리뷰] 스테이킹 시스템 코드 분석
viviviviviid
2023. 1. 30. 02:51
부트캠프 4번째 프로젝트에 들어간 컨트랙트 코드분석
1. Deposit
function deposit(uint256 amount_) public {
require(amount_ > 0, "you must have non-zero token amount");
Staker storage staker = _stakers[msg.sender];
StakeToken(address(stakeToken)).transferFrom(msg.sender, address(this), amount_);
// 스테이킹을 위해 msg.sender(유저)로 부터 address(this)(컨트랙트)로 토큰을 전달
_update();
// 전체 스테이킹 밸런스의 변경이 있을때마다 update 실행
if (!_stakerList.contains(msg.sender)) {
// stakerList에 아직 msg.sender가 없다면, 리스트에 추가시켜줌
_stakerList.add(msg.sender);
staker.tookRewardTime = block.timestamp; // unixTime
// 추후 보상을 받을때 얼마나 스테이킹했는지 확인하기위해, 첫 예치 시점에 타임스탬프 찍어줌
}
staker.amount += amount_;
totalStaked += amount_;
emit Deposit(msg.sender, amount_, staker.tookRewardTime);
}
2. Update
1. 철수만 스테이킹 - 시간당 받을 수 있는 보상토큰을 철수만 받는다.
스테이킹 풀 | 철수 10000개 (100%) |
시간당 보상토큰 (10개) | 10개 |
2. 철수와 같은양을 영희도 스테이킹 - 시간당 받을 수 있는 보상토큰을 철수와 영희가 반반 나눠 갖는다.
스테이킹 풀 | 철수 10000개 (50%) | 영희 10000개 (50%) |
시간당 보상토큰 (10개) | 5개 | 5개 |
3. 영희가 2만개를 더 스테이킹 - 시간당 받을 수 있는 보상토큰을 철수가 25퍼센트, 영희가 75퍼센트로 나눠갖는다.
스테이킹 풀 | 철수 10000개 (25%) | 영희 30000개 (75%) |
시간당 보상토큰 (10개) | 2.5개 | 7.5개 |
4. 철수가 스테이킹한 수량을 전체 출금 - 시간당 받을 수 있는 보상토큰을 영희만 받는다.
스테이킹 풀 | 영희 30000개 (100%) |
시간당 보상토큰 (10개) | 10개 |
위의 순서에서 봤듯이, 예금과 출금같이 스테이킹 풀 비율에 영향을 주는 이벤트가 일어나면, 스테이커에게 돌아가는 보상토큰의 양이 달라진다.
즉, 이전까지 쌓인 보상토큰은 킵해두고, 새롭게 정해진 시간당 보상토큰양으로 쌓이기 시작.
이걸 위해 deposit과 withdraw에 update 함수가 실행되도록 한다.
function _update() public {
// 한명이라도 스테이킹 풀에 입금하거나 출금한다면, 전체적인 비율이 달라지게 된다.
// 즉 스테이킹풀에 영향을 주는 일이 일어날때마다 스테이커들의 보상토큰양을 수정해줘야한다.
for(uint i = 0; i < _stakerList.length(); i++) {
Staker storage staker = _stakers[_stakerList.at(i)];
uint rewardAmount = (staker.amount / totalStaked) * rewardPerSec;
// 전체 스테이킹 풀에서 본인의 비율이 어느정도인가 * 시간당 보상토큰
uint stakingPeriod = block.timestamp - staker.tookRewardTime;
// 업데이트를 한적 있다면 그때부터 스테이킹을 얼마나 시간이 흘렀는가
// 한적 없다면, 최초 예치부터 얼마나 시간이 흘렀는가
uint reward = rewardAmount * stakingPeriod;
// 새로운 보상토큰 비율로 받기전에, 이전까지 쌓여온 보상토큰들을 킵해줌
staker.tookRewardTime = block.timestamp;
// 새로운 보상토큰 비율로 받기위해, 현재 시간으로 타임스탬프를 찍어줌
staker.rewards += reward;
}
3. Claim
function claim() public {
require(_stakerList.contains(msg.sender), "staker does not exist");
Staker storage staker = _stakers[msg.sender];
require(staker.amount > 0, "should stake more than 0");
_update();
// 여태까지 쌓여온 보상토큰을 수령하는거기때문에, timestamp를 업데이트시켜줘야함.
uint claimAmount = staker.rewards;
staker.rewards = 0;
// 보상토큰 축적량 초기화
rewardToken.mint(msg.sender, claimAmount);
// 해당 스테이커에게 보상토큰 발행
emit Claim(msg.sender, claimAmount);
}
4. Withdraw
function withdraw() public {
require(_stakerList.contains(msg.sender), "you are not staker");
Staker storage staker = _stakers[msg.sender];
uint256 withdrawal = staker.amount;
require(withdrawal > 0, "cannot withdraw zero value");
_update();
// 출금이므로 업데이트 필요
claim();
// 스테이킹한 토큰도 출금하고, 그 동안 쌓여온 보상토큰도 수령하고
staker.amount -= withdrawal;
totalStaked -= withdrawal;
_stakerList.remove(msg.sender);
// 리스트에서 제거
stakeToken.transfer(msg.sender, withdrawal);
// 스테이킹한 토큰 출금
emit Withdraw(msg.sender, withdrawal);
}