보다 더 생생한 기록

[Solidity][코드리뷰] 스테이킹 시스템 코드 분석 본문

블록체인

[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);
    }