Curve的投票权重业务与分析

Posted Zeke Luo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Curve的投票权重业务与分析相关的知识,希望对你有一定的参考价值。

Curve 介绍

Curve 是 DeFi 领先的**AMM** (自动做市商)。通过 Curve 的工厂启动了数百个流动资金池,并由 Curve 的 DAO 进行激励。用户依靠 Curve 的专有公式在 ERC-20 代币之间提供高流动性、低滑点、低费用的交易。

CurveToken

  • CRV CurveToken

Curve DAO 代币的主要目的是激励 Curve Finance 平台上的流动性提供者,以及让尽可能多的用户参与协议的治理。

目前,CRV 主要有三种用途:投票、质押和助推。这三件事将要求您投票锁定您的 CRV 并获得 veCRV。

  • -VeCRV 是投票凭证Token (锁仓到期,权重就会逐渐减少

 如何获取VeCRV

- 锁定CRV,获得才可以VeCRV,也才能参与投票。

veCRV 数量取决于你锁定 CRV 的时间,最短锁定时间为一周,最长锁定时间为四年您锁定 CRV 的时间越长,您拥有的投票权就越大,你可以投票锁定 1,000 CRV 一年以获得 250 veCRV 权重。

随着您的托管代币接近锁定到期,您的 veCRV 权重会逐渐减少。(锁定CRV,获得VeCRV, 才可以参与投票)

总结:锁定投票CRV,获得veCRV,锁定的CRV时间越长,拥有的投票权就越大

例如: 投票锁定1000crv, 一年以内获得250veCRV权重,随着代币接近锁仓到期,权重就会逐渐减少。

**实施细节**

用户投票权重从锁定时刻开始线性递减。总投票权也是如此. 为了避免周期性签到,每次用户充值、提现或更改锁定时间时,我们记录用户的斜率和偏差为线性函数在公共映射`user_point_history`中。我们还改变了总投票权的斜率和偏差并将其记录在`point_history`. 此外,当一个用户的锁定计划结束时,我们计划改变斜率在未来`slope_changes`。每个更改都涉及将`epoch` 增加。

这样我们就不必遍历所有用户来计算,应该多少改变,我们也不需要用户定期检查。但是,我们将用户锁定的结束时间限制为整周四舍五入的时间。

当用户存入和锁定治理令牌以及锁定时间到期时,斜率和偏差都会发生变化。所有可能的到期时间都四舍五入到整周,以使区块链的读取次数最多错过的周数成正比,而不是用户数量(可能很大)。

步骤优化

1.每次用户充值提现更改锁定时间时,就更新斜和偏差为线性函数,在公共映射`user_point_history`中

2.再改变总投票权的斜率和偏差并将其记录在`point_history`

3.当用户锁定技术结束时,改变写斜率还有偏差,`slope_changes`。每个更改都涉及将`epoch`1 增加

4.最后如果存入的锁定治理令牌锁定时间到期,斜率和偏差都会变化.然后更新可以看到用户的权重衰减

代码分析讲解:

(只分析部分重要的方法)-

    uint256 private constant DEPOSIT_FOR_TYPE = 0;
    uint256 private constant CREATE_LOCK_TYPE = 1;
    uint256 private constant INCREASE_LOCK_AMOUNT = 2;
    uint256 private constant INCREASE_UNLOCK_TIME = 3;

    uint256 public constant WEEK = 7 * 86400; // all future times are rounded by week
    uint256 public constant MAXTIME = 4 * 365 * 86400; // 4 years
    uint256 public constant MULTIPLIER = 10**18;

    address public token;
    uint256 public supply;

    mapping(address => LockedBalance) public locked;

    //everytime user deposit/withdraw/change_locktime, these values will be updated;
    uint256 public override epoch;
    mapping(uint256 => Point) public supplyPointHistory; // epoch -> unsigned point.
    mapping(address => mapping(uint256 => Point)) public userPointHistory; // user -> 
    Point[user_epoch]
    mapping(address => uint256) public userPointEpoch;
    mapping(uint256 => int256) public slopeChanges; // time -> signed slope change

    string public name;
    string public symbol;
    uint256 public decimals;

    //1.衰减主要的变量值
    supplyPointHistory[0] = Point(
            bias: 0,
            slope: 0,
            ts: block.timestamp,
            blk: block.number
        );
        decimals = 18;
        name = "Vote-escrowed BEND";
        symbol = "veBEND";
    

- 锁定CRV

执行方法createLockFor创建锁定CRV换取veCRV,锁定的最长期限为四年(此方法存入CRV,并且给用户生产veCrv,并且更新公式的系数)
   

function createLockFor(
        address _beneficiary,
        uint256 _value,
        uint256 _unlockTime
    ) external override 
        _createLock(_beneficiary, _value, _unlockTime);
    

     /***
     *@dev Deposit `_value` tokens for `msg.sender` and lock until `_unlockTime`
     *@param _value Amount to deposit
     *@param _unlockTime Epoch time when tokens unlock, rounded down to whole weeks
     */
    function _createLock(
        address _beneficiary,
        uint256 _value,
        uint256 _unlockTime
     ) internal nonReentrant 
        _unlockTime = (_unlockTime / WEEK) * WEEK; // Locktime is rounded down to weeks
        LockedBalance memory _locked = locked[_beneficiary];

        require(_value > 0, "Can't lock zero value");
        require(_locked.amount == 0, "Withdraw old tokens first");
        require(
            _unlockTime > block.timestamp,
            "Can only lock until time in the future"
        );
        require(
            _unlockTime <= block.timestamp + MAXTIME,
            "Voting lock can be 4 years max"
        );

        _depositFor(
            msg.sender,
            _beneficiary,
            _value,
            _unlockTime,
            _locked,
            CREATE_LOCK_TYPE
        );
    

一共6组时间锁定,最大四年,最小一个星期。

1周0.48%    1个月0.21%  3个月0.63%  6个月12.7%   1年25%   4年100%

维护系数会在每次调用lock,或者提取token操作,都会更新调用CheckPoint此方法,更新系数值(checkPoint比较重要的函数)

/***
     *@dev Record global and per-user data to checkpoint
     *@param _addr User's wallet address. No user checkpoint if 0x0
     *@param _oldLocked Pevious locked amount / end lock time for the user
     *@param _newLocked New locked amount / end lock time for the user
     */
    function _checkpoint(
        address _addr,
        LockedBalance memory _oldLocked,
        LockedBalance memory _newLocked
    ) internal 
        CheckpointParameters memory _st;
        _st.epoch = epoch;

        if (_addr != address(0)) 
            // Calculate slopes and biases
            // Kept at zero when they have to
            if (_oldLocked.end > block.timestamp && _oldLocked.amount > 0) 
                _st.userOldPoint.slope = _oldLocked.amount / int256(MAXTIME);
                _st.userOldPoint.bias =
                    _st.userOldPoint.slope *
                    int256(_oldLocked.end - block.timestamp);
            
            if (_newLocked.end > block.timestamp && _newLocked.amount > 0) 
                _st.userNewPoint.slope = _newLocked.amount / int256(MAXTIME);
                _st.userNewPoint.bias =
                    _st.userNewPoint.slope *
                    int256(_newLocked.end - block.timestamp);
            

            // Read values of scheduled changes in the slope
            // _oldLocked.end can be in the past and in the future
            // _newLocked.end can ONLY by in the FUTURE unless everything expired than zeros
            _st.oldDslope = slopeChanges[_oldLocked.end];
            if (_newLocked.end != 0) 
                if (_newLocked.end == _oldLocked.end) 
                    _st.newDslope = _st.oldDslope;
                 else 
                    _st.newDslope = slopeChanges[_newLocked.end];
                
            
        
        Point memory _lastPoint = Point(
            bias: 0,
            slope: 0,
            ts: block.timestamp,
            blk: block.number
        );
        if (_st.epoch > 0) 
            _lastPoint = supplyPointHistory[_st.epoch];
        
        uint256 _lastCheckPoint = _lastPoint.ts;
        // _initialLastPoint is used for extrapolation to calculate block number
        // (approximately, for *At methods) and save them
        // as we cannot figure that out exactly from inside the contract
        // Point memory _initialLastPoint = _lastPoint;
        uint256 _initBlk = _lastPoint.blk;
        uint256 _initTs = _lastPoint.ts;

        uint256 _blockSlope = 0; // dblock/dt
        if (block.timestamp > _lastPoint.ts) 
            _blockSlope =
                (MULTIPLIER * (block.number - _lastPoint.blk)) /
                (block.timestamp - _lastPoint.ts);
        
        // If last point is already recorded in this block, slope=0
        // But that's ok b/c we know the block in such case

        // Go over weeks to fill history and calculate what the current point is
        uint256 _ti = (_lastCheckPoint / WEEK) * WEEK;
        for (uint256 i; i < 255; i++) 
            // Hopefully it won't happen that this won't get used in 5 years!
            // If it does, users will be able to withdraw but vote weight will be broken
            _ti += WEEK;
            int256 d_slope = 0;
            if (_ti > block.timestamp) 
                // reach future time, reset to blok time
                _ti = block.timestamp;
             else 
                d_slope = slopeChanges[_ti];
            
            _lastPoint.bias =
                _lastPoint.bias -
                _lastPoint.slope *
                int256(_ti - _lastCheckPoint);
            _lastPoint.slope += d_slope;
            if (_lastPoint.bias < 0) 
                // This can happen
                _lastPoint.bias = 0;
            
            if (_lastPoint.slope < 0) 
                // This cannot happen - just in case
                _lastPoint.slope = 0;
            
            _lastCheckPoint = _ti;
            _lastPoint.ts = _ti;
            _lastPoint.blk =
                _initBlk +
                ((_blockSlope * (_ti - _initTs)) / MULTIPLIER);
            _st.epoch += 1;
            if (_ti == block.timestamp) 
                // history filled over, break loop
                _lastPoint.blk = block.number;
                break;
             else 
                supplyPointHistory[_st.epoch] = _lastPoint;
            
        
        epoch = _st.epoch;
        // Now supplyPointHistory is filled until t=now

        if (_addr != address(0)) 
            // If last point was in this block, the slope change has been applied already
            // But in such case we have 0 slope(s)
            _lastPoint.slope += _st.userNewPoint.slope - _st.userOldPoint.slope;
            _lastPoint.bias += _st.userNewPoint.bias - _st.userOldPoint.bias;
            if (_lastPoint.slope < 0) 
                _lastPoint.slope = 0;
            
            if (_lastPoint.bias < 0) 
                _lastPoint.bias = 0;
            
        
        // Record the changed point into history
        supplyPointHistory[_st.epoch] = _lastPoint;
        if (_addr != address(0)) 
            // Schedule the slope changes (slope is going down)
            // We subtract new_user_slope from [_newLocked.end]
            // and add old_user_slope to [_oldLocked.end]
            if (_oldLocked.end > block.timestamp) 
                // _oldDslope was <something> - _userOldPoint.slope, so we cancel that
                _st.oldDslope += _st.userOldPoint.slope;
                if (_newLocked.end == _oldLocked.end) 
                    _st.oldDslope -= _st.userNewPoint.slope; // It was a new deposit, not extension
                
                slopeChanges[_oldLocked.end] = _st.oldDslope;
            
            if (_newLocked.end > block.timestamp) 
                if (_newLocked.end > _oldLocked.end) 
                    _st.newDslope -= _st.userNewPoint.slope; // old slope disappeared at this point
                    slopeChanges[_newLocked.end] = _st.newDslope;
                
                // else we recorded it already in _oldDslope
            

            // Now handle user history
            uint256 _userEpoch = userPointEpoch[_addr] + 1;

            userPointEpoch[_addr] = _userEpoch;
            _st.userNewPoint.ts = block.timestamp;
            _st.userNewPoint.blk = block.number;
            userPointHistory[_addr][_userEpoch] = _st.userNewPoint;
        
    

- 用户withdraw(同时会去调用checkPoint更新系数)

/***
     *@dev Withdraw all tokens for `msg.sender`
     *@dev Only possible if the lock has expired
     */
    function withdraw() external override nonReentrant 
        LockedBalance memory _locked = LockedBalance(
            locked[msg.sender].amount,
            locked[msg.sender].end
        );

        require(block.timestamp >= _locked.end, "The lock didn't expire");
        uint256 _value = uint256(_locked.amount);

        LockedBalance memory _oldLocked = LockedBalance(
            locked[msg.sender].amount,
            locked[msg.sender].end
        );

        _locked.end = 0;
        _locked.amount = 0;
        locked[msg.sender] = _locked;
        uint256 _supplyBefore = supply;
        supply = _supplyBefore - _value;

        // _oldLocked can have either expired <= timestamp or zero end
        // _locked has only 0 end
        // Both can have >= 0 amount
        _checkpoint(msg.sender, _oldLocked, _locked);

        IERC20Upgradeable(token).safeTransfer(msg.sender, _value);

        emit Withdraw(msg.sender, _value, block.timestamp);
        emit Supply(_supplyBefore, _supplyBefore - _value);
    


 

- 计算当前用户的投票权重

/***
     *@notice Get the current voting power for `msg.sender`
     *@dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
     *@param _addr User wallet address
     *@param _t Epoch time to return voting power at
     *@return User voting power
     *@dev return the present voting power if _t is 0
     */
    function balanceOf(address _addr, uint256 _t)
        external
        view
        returns (uint256)
    
        if (_t == 0) 
            _t = block.timestamp;
        

        uint256 _epoch = userPointEpoch[_addr];
        if (_epoch == 0) 
            return 0;
         else 
            Point memory _lastPoint = userPointHistory[_addr][_epoch];
            **_lastPoint.bias -= _lastPoint.slope * int256(_t - _lastPoint.ts);**
            if (_lastPoint.bias < 0) 
                _lastPoint.bias = 0;
            
            return uint256(_lastPoint.bias);
        
    

以上调研的相关参考资料

Curve DAO: Vote-Escrowed CRV — Curve 1.0.0 documentation 官方文档

bend-incentive/VeBend.sol at main · BendDAO/bend-incentive · GitHub 源码

https://dao.curve.fi/locker 前端

https://curve.fi/files/stableswap-paper.pdf 白皮书

R语言临床预测模型的评价指标与验证指标实战:决策曲线分析(Decision Curve Analysis, DCA)

R语言临床预测模型的评价指标与验证指标实战:决策曲线分析(Decision Curve Analysis, DCA)

目录

以上是关于Curve的投票权重业务与分析的主要内容,如果未能解决你的问题,请参考以下文章

如何编写一个投票功能的智能合约

如何编写一个投票功能的智能合约

精EOS智能合约:system系统合约源码分析

基于区块链的投票系统的设计与实现

Solidity 智能合约开发工具准备第一篇

如何在 BSV 上训练人工智能模型