Skip to content

Latest commit

 

History

History
980 lines (812 loc) · 32.4 KB

StakingStakeModule.md

File metadata and controls

980 lines (812 loc) · 32.4 KB

Staking contract staking functionality module (StakingStakeModule.sol)

View Source: contracts/governance/Staking/modules/StakingStakeModule.sol

↗ Extends: IFunctionsList, StakingShared, CheckpointsShared, ApprovalReceiver

StakingStakeModule contract

Implements staking functionality*

Events

event TokensStaked(address indexed staker, uint256  amount, uint256  lockedUntil, uint256  totalStaked);
event ExtendedStakingDuration(address indexed staker, uint256  previousDate, uint256  newDate, uint256  amountStaked);

Functions


stake

Stake the given amount for the given duration of time.

function stake(uint96 amount, uint256 until, address stakeFor, address delegatee) external nonpayable whenNotPaused whenNotFrozen 

Arguments

Name Type Description
amount uint96 The number of tokens to stake.
until uint256 Timestamp indicating the date until which to stake.
stakeFor address The address to stake the tokens for or 0x0 if staking for oneself.
delegatee address The address of the delegatee or 0x0 if there is none.
Source Code
function stake(
        uint96 amount,
        uint256 until,
        address stakeFor,
        address delegatee
    ) external whenNotPaused whenNotFrozen {
        _stake(msg.sender, amount, until, stakeFor, delegatee, false);
    }

stakeWithApproval

Stake the given amount for the given duration of time.

function stakeWithApproval(address sender, uint96 amount, uint256 until, address stakeFor, address delegatee) external nonpayable onlyThisContract whenNotPaused whenNotFrozen 

Arguments

Name Type Description
sender address The sender of SOV.approveAndCall
amount uint96 The number of tokens to stake.
until uint256 Timestamp indicating the date until which to stake.
stakeFor address The address to stake the tokens for or 0x0 if staking for oneself.
delegatee address The address of the delegatee or 0x0 if there is none.
Source Code
function stakeWithApproval(
        address sender,
        uint96 amount,
        uint256 until,
        address stakeFor,
        address delegatee
    ) external onlyThisContract whenNotPaused whenNotFrozen {
        _stake(sender, amount, until, stakeFor, delegatee, false);
    }

_stake

Send sender's tokens to this contract and update its staked balance.

function _stake(address sender, uint96 amount, uint256 until, address stakeFor, address delegatee, bool timeAdjusted) internal nonpayable

Arguments

Name Type Description
sender address The sender of the tokens.
amount uint96 The number of tokens to send.
until uint256 The date until which the tokens will be staked.
stakeFor address The beneficiary whose stake will be increased.
delegatee address The address of the delegatee or stakeFor if default 0x0.
timeAdjusted bool Whether fixing date to stacking periods or not.
Source Code
function _stake(
        address sender,
        uint96 amount,
        uint256 until,
        address stakeFor,
        address delegatee,
        bool timeAdjusted
    ) internal {
        _stakeOptionalTokenTransfer(
            sender,
            amount,
            until,
            stakeFor,
            delegatee,
            timeAdjusted,
            true // transfer SOV
        );
    }

_stakeOptionalTokenTransfer

Send sender's tokens to this contract and update its staked balance.

function _stakeOptionalTokenTransfer(address sender, uint96 amount, uint256 until, address stakeFor, address delegatee, bool timeAdjusted, bool transferToken) internal nonpayable

Arguments

Name Type Description
sender address The sender of the tokens.
amount uint96 The number of tokens to send.
until uint256 The date until which the tokens will be staked.
stakeFor address The beneficiary whose stake will be increased.
delegatee address The address of the delegatee or stakeFor if default 0x0.
timeAdjusted bool Whether fixing date to stacking periods or not.
transferToken bool Should transfer SOV - false for multiple iterations like in stakeBySchedule
Source Code
function _stakeOptionalTokenTransfer(
        address sender,
        uint96 amount,
        uint256 until,
        address stakeFor,
        address delegatee,
        bool timeAdjusted,
        bool transferToken
    ) internal {
        require(amount > 0, "amount needs to be bigger than 0"); // S01

        if (!timeAdjusted) {
            until = _timestampToLockDate(until);
        }
        require(
            until > block.timestamp,
            "Staking::_timestampToLockDate: staking period too short"
        ); // S02

        /// @dev Stake for the sender if not specified otherwise.
        if (stakeFor == address(0)) {
            stakeFor = sender;
        }
        // must wait a block before staking again for that same deadline
        _notSameBlockAsStakingCheckpoint(until, stakeFor);

        /// @dev Delegate for stakeFor if not specified otherwise.
        if (delegatee == address(0)) {
            delegatee = stakeFor;
        }

        /// @dev Do not stake longer than the max duration.
        if (!timeAdjusted) {
            uint256 latest = _timestampToLockDate(block.timestamp + MAX_DURATION);
            if (until > latest) until = latest;
        }

        uint96 previousBalance = _currentBalance(stakeFor, until);

        /// @dev Increase stake.
        _increaseStake(sender, amount, stakeFor, until, transferToken);

        // @dev Previous version wasn't working properly for the following case:
        //		delegate checkpoint wasn't updating for the second and next stakes for the same date
        //		if  first stake was withdrawn completely and stake was delegated to the staker
        //		(no delegation to another address).
        address previousDelegatee = delegates[stakeFor][until];

        if (previousDelegatee != delegatee) {
            // @dev only the user that stakes for himself is allowed to delegate VP to another address
            // which works with vesting stakes and prevents vulnerability of delegating VP to an arbitrary address from
            // any address

            if (delegatee != stakeFor) {
                require(
                    stakeFor == sender,
                    "Only stakeFor account is allowed to change delegatee"
                );
            } else if (sender != stakeFor && previousDelegatee != address(0)) {
                require(stakeFor == sender, "Only sender is allowed to change delegatee");
            }

            /// @dev Update delegatee.
            delegates[stakeFor][until] = delegatee;

            /// @dev Decrease stake on previous balance for previous delegatee.
            _decreaseDelegateStake(previousDelegatee, until, previousBalance);

            /// @dev Add previousBalance to amount.
            amount = add96(previousBalance, amount, "add amounts failed");
        }

        /// @dev Increase stake.
        _increaseDelegateStake(delegatee, until, amount);
        emit DelegateChanged(stakeFor, until, previousDelegatee, delegatee);
    }

extendStakingDuration

Extend the staking duration until the specified date.

function extendStakingDuration(uint256 previousLock, uint256 until) external nonpayable whenNotPaused whenNotFrozen 

Arguments

Name Type Description
previousLock uint256 The old unlocking timestamp.
until uint256 The new unlocking timestamp in seconds.
Source Code
function extendStakingDuration(uint256 previousLock, uint256 until)
        external
        whenNotPaused
        whenNotFrozen
    {
        previousLock = _timestampToLockDate(previousLock);
        until = _timestampToLockDate(until);

        _notSameBlockAsStakingCheckpoint(previousLock, msg.sender);

        /// @dev Do not exceed the max duration, no overflow possible.
        uint256 latest = _timestampToLockDate(block.timestamp + MAX_DURATION);
        if (until > latest) until = latest;

        require(previousLock < until, "must increase staking duration"); // S04

        /// @dev Update checkpoints.
        /// @dev TODO James: Can reading stake at block.number -1 cause trouble with multiple tx in a block?
        uint96 amount = _getPriorUserStakeByDate(msg.sender, previousLock, block.number - 1);
        require(amount > 0, "no stakes till the prev lock date"); // S05
        _decreaseUserStake(msg.sender, previousLock, amount);
        _increaseUserStake(msg.sender, until, amount);

        if (_isVestingContract(msg.sender)) {
            _decreaseVestingStake(previousLock, amount);
            _increaseVestingStake(until, amount);
        }

        _decreaseDailyStake(previousLock, amount);
        _increaseDailyStake(until, amount);

        /// @dev Delegate might change: if there is already a delegate set for the until date, it will remain the delegate for this position
        address delegateFrom = delegates[msg.sender][previousLock];
        delegates[msg.sender][previousLock] = address(0); //the previousLock delegates nullifying before reading that form `until` guards in case delegateTo == until
        address delegateTo = delegates[msg.sender][until];
        if (delegateTo == address(0)) {
            delegateTo = delegateFrom;
            delegates[msg.sender][until] = delegateFrom;
        }
        _decreaseDelegateStake(delegateFrom, previousLock, amount);
        _increaseDelegateStake(delegateTo, until, amount);

        emit ExtendedStakingDuration(msg.sender, previousLock, until, amount);
    }

_increaseStake

Send sender's tokens to this contract and update its staked balance.

function _increaseStake(address sender, uint96 amount, address stakeFor, uint256 until, bool transferToken) internal nonpayable

Arguments

Name Type Description
sender address The sender of the tokens.
amount uint96 The number of tokens to send.
stakeFor address The beneficiary whose stake will be increased.
until uint256 The date until which the tokens will be staked.
transferToken bool if false - token transfer should be handled separately
Source Code
function _increaseStake(
        address sender,
        uint96 amount,
        address stakeFor,
        uint256 until,
        bool transferToken
    ) internal {
        /// @dev Retrieve the SOV tokens.
        if (transferToken)
            require(
                SOVToken.transferFrom(sender, address(this), amount),
                "Should transfer tokens successfully"
            ); // IS10

        /// @dev Increase staked balance.
        uint96 balance = _currentBalance(stakeFor, until);
        balance = add96(balance, amount, "increaseStake: overflow"); // IS20

        /// @dev Update checkpoints.
        _increaseDailyStake(until, amount);
        _increaseUserStake(stakeFor, until, amount);

        if (_isVestingContract(stakeFor)) _increaseVestingStake(until, amount);

        emit TokensStaked(stakeFor, amount, until, balance);
    }

stakesBySchedule

DO NOT USE this misspelled function. Use stakeBySchedule function instead. This function cannot be deprecated while we have non-upgradeable vesting contracts.

function stakesBySchedule(uint256 amount, uint256 cliff, uint256 duration, uint256 intervalLength, address stakeFor, address delegatee) external nonpayable whenNotPaused whenNotFrozen 

Arguments

Name Type Description
amount uint256
cliff uint256
duration uint256
intervalLength uint256
stakeFor address
delegatee address
Source Code
function stakesBySchedule(
        uint256 amount,
        uint256 cliff,
        uint256 duration,
        uint256 intervalLength,
        address stakeFor,
        address delegatee
    ) external whenNotPaused whenNotFrozen {
        _stakeBySchedule(amount, cliff, duration, intervalLength, stakeFor, delegatee);
    }

stakeBySchedule

Stake tokens according to the vesting schedule.

function stakeBySchedule(uint256 amount, uint256 cliff, uint256 duration, uint256 intervalLength, address stakeFor, address delegatee) external nonpayable whenNotPaused whenNotFrozen 

Arguments

Name Type Description
amount uint256 The amount of tokens to stake.
cliff uint256 The time interval to the first withdraw.
duration uint256 The staking duration.
intervalLength uint256 The length of each staking interval when cliff passed.
stakeFor address The address to stake the tokens for or 0x0 if staking for oneself.
delegatee address The address of the delegatee or 0x0 if there is none.
Source Code
function stakeBySchedule(
        uint256 amount,
        uint256 cliff,
        uint256 duration,
        uint256 intervalLength,
        address stakeFor,
        address delegatee
    ) external whenNotPaused whenNotFrozen {
        _stakeBySchedule(amount, cliff, duration, intervalLength, stakeFor, delegatee);
    }

_stakeBySchedule

Stake tokens according to the vesting schedule.

function _stakeBySchedule(uint256 amount, uint256 cliff, uint256 duration, uint256 intervalLength, address stakeFor, address delegatee) internal nonpayable

Arguments

Name Type Description
amount uint256 The amount of tokens to stake.
cliff uint256 The time interval to the first withdraw.
duration uint256 The staking duration.
intervalLength uint256 The length of each staking interval when cliff passed.
stakeFor address The address to stake the tokens for or 0x0 if staking for oneself.
delegatee address The address of the delegatee or 0x0 if there is none.
Source Code
function _stakeBySchedule(
        uint256 amount,
        uint256 cliff,
        uint256 duration,
        uint256 intervalLength,
        address stakeFor,
        address delegatee
    ) internal {
        require(amount > 0, "Invalid amount");
        require(duration <= MAX_DURATION, "Invalid duration");
        require(intervalLength > 0, "Invalid interval length");
        require(intervalLength % TWO_WEEKS == 0, "Invalid interval length");
        if (delegatee != stakeFor && delegatee != address(0)) {
            require(
                stakeFor == msg.sender,
                "Only stakeFor account is allowed to change delegatee"
            );
        }
        /**
         * @dev Stake them until lock dates according to the vesting schedule.
         * Note: because staking is only possible in periods of 2 weeks,
         * the total duration might end up a bit shorter than specified
         * depending on the date of staking.
         * */
        uint256 start = _timestampToLockDate(block.timestamp + cliff);
        uint256 end = _timestampToLockDate(block.timestamp + duration);
        require(start <= end, "Invalid schedule");
        uint256 numIntervals;
        if (start < end) {
            numIntervals = (end - start) / intervalLength + 1;
        } else {
            numIntervals = 1;
        }
        uint256 stakedPerInterval = amount / numIntervals;

        /// @dev transferring total SOV amount before staking
        require(
            SOVToken.transferFrom(msg.sender, address(this), amount),
            "Should transfer tokens successfully"
        ); // SS10
        /// @dev stakedPerInterval might lose some dust on rounding. Add it to the first staking date.
        if (numIntervals >= 1) {
            _stakeOptionalTokenTransfer(
                msg.sender,
                uint96(amount - stakedPerInterval * (numIntervals - 1)),
                start,
                stakeFor,
                delegatee,
                true,
                false
            );
        }
        /// @dev Stake the rest in 4 week intervals.
        for (uint256 i = start + intervalLength; i <= end; i += intervalLength) {
            /// @dev Stakes for itself, delegates to the owner.
            _notSameBlockAsStakingCheckpoint(i, stakeFor); // must wait a block before staking again for that same deadline
            _stakeOptionalTokenTransfer(
                msg.sender,
                uint96(stakedPerInterval),
                i,
                stakeFor,
                delegatee,
                true,
                false
            );
        }
    }

balanceOf

Get the number of staked tokens held by the user account.

function balanceOf(address account) external view
returns(balance uint96)

Arguments

Name Type Description
account address The address of the account to get the balance of.

Returns

The number of tokens held.

Source Code
function balanceOf(address account) external view returns (uint96 balance) {
        for (uint256 i = kickoffTS; i <= block.timestamp + MAX_DURATION; i += TWO_WEEKS) {
            balance = add96(balance, _currentBalance(account, i), "Staking::balanceOf: overflow"); // S12
        }
    }

getCurrentStakedUntil

Get the current number of tokens staked for a day.

function getCurrentStakedUntil(uint256 lockedTS) external view
returns(uint96)

Arguments

Name Type Description
lockedTS uint256 The timestamp to get the staked tokens for.
Source Code
function getCurrentStakedUntil(uint256 lockedTS) external view returns (uint96) {
        uint32 nCheckpoints = numTotalStakingCheckpoints[lockedTS];
        return nCheckpoints > 0 ? totalStakingCheckpoints[lockedTS][nCheckpoints - 1].stake : 0;
    }

getStakes

Get list of stakes for a user account.

function getStakes(address account) external view
returns(dates uint256[], stakes uint96[])

Arguments

Name Type Description
account address The address to get stakes.

Returns

The arrays of dates and stakes.

Source Code
function getStakes(address account)
        external
        view
        returns (uint256[] memory dates, uint96[] memory stakes)
    {
        uint256 latest = _timestampToLockDate(block.timestamp + MAX_DURATION);

        /// @dev Calculate stakes.
        uint256 count = 0;
        /// @dev We need to iterate from first possible stake date after deployment to the latest from current time.
        for (uint256 i = kickoffTS + TWO_WEEKS; i <= latest; i += TWO_WEEKS) {
            if (_currentBalance(account, i) > 0) {
                count++;
            }
        }
        dates = new uint256[](count);
        stakes = new uint96[](count);

        /// @dev We need to iterate from first possible stake date after deployment to the latest from current time.
        uint256 j = 0;
        for (uint256 i = kickoffTS + TWO_WEEKS; i <= latest; i += TWO_WEEKS) {
            uint96 balance = _currentBalance(account, i);
            if (balance > 0) {
                dates[j] = i;
                stakes[j] = balance;
                j++;
            }
        }
    }

_getToken

undefined

Overrides default ApprovalReceiver._getToken function to register SOV token on this contract.

function _getToken() internal view
returns(address)
Source Code
function _getToken() internal view returns (address) {
        return address(SOVToken);
    }

_getSelectors

undefined

Overrides default ApprovalReceiver._getSelectors function to register stakeWithApproval selector on this contract.

function _getSelectors() internal pure
returns(bytes4[])
Source Code
function _getSelectors() internal pure returns (bytes4[] memory) {
        bytes4[] memory selectors = new bytes4[](1);
        selectors[0] = this.stakeWithApproval.selector;
        return selectors;
    }

timestampToLockDate

Unstaking is possible every 2 weeks only. This means, to calculate the key value for the staking checkpoints, we need to map the intended timestamp to the closest available date.

function timestampToLockDate(uint256 timestamp) external view
returns(uint256)

Arguments

Name Type Description
timestamp uint256 The unlocking timestamp.

Returns

The actual unlocking date (might be up to 2 weeks shorter than intended).

Source Code
function timestampToLockDate(uint256 timestamp) external view returns (uint256) {
        return _timestampToLockDate(timestamp);
    }

getFunctionsList

⤾ overrides IFunctionsList.getFunctionsList

function getFunctionsList() external pure
returns(bytes4[])
Source Code
function getFunctionsList() external pure returns (bytes4[] memory) {
        bytes4[] memory functionsList = new bytes4[](10);
        functionsList[0] = this.stake.selector;
        functionsList[1] = this.stakeWithApproval.selector;
        functionsList[2] = this.extendStakingDuration.selector;
        functionsList[3] = this.stakesBySchedule.selector;
        functionsList[4] = this.stakeBySchedule.selector;
        functionsList[5] = this.balanceOf.selector;
        functionsList[6] = this.getCurrentStakedUntil.selector;
        functionsList[7] = this.getStakes.selector;
        functionsList[8] = this.timestampToLockDate.selector;
        functionsList[9] = this.receiveApproval.selector;
        return functionsList;
    }

Contracts