Developer Docs
AppGitHub
  • Welcome!
  • Smart contracts
    • Core
      • Introduction
      • Deep Dive
      • Smart Contract Reference
        • PWN Hub
          • Tags
        • PWN Config
        • PWN Vault
        • Loan Types
          • Simple Loan
        • Proposals
          • Simple Loan Proposal
            • Simple Proposal
            • List Proposal
            • Elastic Proposal
            • Elastic Chainlink Proposal
            • Dutch Proposal
        • PWN Utilized Credit
        • PWN LOAN
        • PWN Revoked Nonce
        • Peripheral Contracts
          • Acceptor Controller
            • World ID
          • State Fingerprint Computer
            • UniV3
            • Chicken Bonds
          • Pool Adapter
            • Aave
            • Compound
            • ERC4626
        • Miscellaneous
          • PWN Fee Calculator
          • PWN Signature Checker
          • PWN Errors
          • PWN Periphery
          • Timelock
    • PWN DAO
      • Governance
        • Optimistic
        • Token
      • Tokens
        • PWN
        • stPWN
        • vePWN
          • Stake
          • Power
          • Metadata
      • Epoch Clock
      • Miscellaneous
        • Errors
        • EpochPowerLib
    • Tools
      • PWN Safe
        • Architecture
        • Security considerations
        • Smart Contract Reference
          • PWN Safe Factory
          • ATR Module
            • Tokenized Asset Manager
            • Recipient Permission Manager
          • Whitelist
          • ATR Guard
            • Operators context
      • Token Bundler
      • PWN Deployer
    • Libraries
      • MultiToken
    • Contract Addresses
  • More documentation
    • PWN Docs
    • FAQ
    • Audits
    • Using PWN without front-end
  • Deprecated
    • PWN Beta
      • Architecture
      • PWN
        • Off-chain signed offer
        • Offer types
      • PWN Vault
      • PWN LOAN
Powered by GitBook
On this page
  • 1. Summary
  • 2. Important links
  • 3. Contract details
  • Features
  • Inherited contracts, implemented Interfaces and ERCs
  • Functions
  • Events
  • StakeData Struct
Edit on GitHub
  1. Smart contracts
  2. PWN DAO
  3. Tokens
  4. vePWN

Stake

PreviousvePWNNextPower

Last updated 5 months ago

1. Summary

The vePWN Stake contract is an abstract contract inherited by vePWN. It implements functions to manage stake in PWN DAO.

2. Important links

3. Contract details

  • VoteEscrowedPWNStake.sol is written in Solidity version 0.8.25

Features

  • Create, Split, Merge, Increase, Withdraw and Delegate stake

Inherited contracts, implemented Interfaces and ERCs

Functions

createStake

Overview

Function to create a stake.

This function takes two arguments:

  • uint256amount

  • uint256lockUpEpochs

Implementation

function createStake(uint256 amount, uint256 lockUpEpochs) external returns (uint256) {
    return createStakeOnBehalfOf(msg.sender, msg.sender, amount, lockUpEpochs);
}
createStakeOnBehalfOf

Overview

Function to create a stake of behalf of another account.

This function takes four arguments:

  • addressstaker

  • addressbeneficiary

  • uint256amount

  • uint256lockUpEpochs

Implementation

function createStakeOnBehalfOf(address staker, address beneficiary, uint256 amount, uint256 lockUpEpochs)
    public
    returns (uint256 stakeId)
{
    // max stake of total initial supply (100M) with decimals 1e26 < max uint88 (3e26)
    if (amount < 100 || amount > type(uint88).max) {
        revert Error.InvalidAmount();
    }
    // amount must be a multiple of 100 to prevent rounding errors when computing power
    if (amount % 100 > 0) {
        revert Error.InvalidAmount();
    }
    // lock up for <1; 5> + {10} years
    if (lockUpEpochs < EPOCHS_IN_YEAR) {
        revert Error.InvalidLockUpPeriod();
    }
    if (lockUpEpochs > 5 * EPOCHS_IN_YEAR && lockUpEpochs != 10 * EPOCHS_IN_YEAR) {
        revert Error.InvalidLockUpPeriod();
    }

    uint16 initialEpoch = epochClock.currentEpoch() + 1;

    // store power changes
    _updateTotalPower(uint104(amount), initialEpoch, uint8(lockUpEpochs), true);

    // create new stake
    stakeId = _createStake({
        owner: staker,
        beneficiary: beneficiary,
        initialEpoch: initialEpoch,
        amount: uint104(amount),
        lockUpEpochs: uint8(lockUpEpochs)
    });

    // transfer pwn token
    pwnToken.transferFrom(msg.sender, address(this), amount);

    // emit event
    emit StakeCreated(stakeId, staker, beneficiary, amount, lockUpEpochs);
}
splitStake

Overview

Function to split a stake into two. Burns the original stPWN NFT and mints two new ones. The beneficiary of the new stake is also the stake owner.

This function takes three arguments:

  • uint256stakeId

  • addressstakeBeneficiary

  • uint256splitAmount - amount of PWN tokens to split into the first new stake

Implementation

function splitStake(uint256 stakeId, address stakeBeneficiary, uint256 splitAmount)
    external
    returns (uint256 newStakeId1, uint256 newStakeId2)
{
    address staker = msg.sender;
    Stake storage originalStake = _stakes[stakeId];
    uint16 originalInitialEpoch = originalStake.initialEpoch;
    uint104 originalAmount = originalStake.amount;
    uint8 originalLockUpEpochs = originalStake.lockUpEpochs;

    // split amount must be greater than 0
    if (splitAmount == 0) {
        revert Error.InvalidAmount();
    }
    // split amount must be less than stake amount
    if (splitAmount >= originalAmount) {
        revert Error.InvalidAmount();
    }
    // split amount must be a multiple of 100 to prevent rounding errors when computing power
    if (splitAmount % 100 > 0) {
        revert Error.InvalidAmount();
    }

    // delete original stake
    _deleteStake({ owner: staker, beneficiary: stakeBeneficiary, stakeId: stakeId });

    // create new stakes
    newStakeId1 = _createStake({
        owner: staker,
        beneficiary: staker,
        initialEpoch: originalInitialEpoch,
        amount: originalAmount - uint104(splitAmount),
        lockUpEpochs: originalLockUpEpochs
    });
    newStakeId2 = _createStake({
        owner: staker,
        beneficiary: staker,
        initialEpoch: originalInitialEpoch,
        amount: uint104(splitAmount),
        lockUpEpochs: originalLockUpEpochs
    });

    // emit event
    emit StakeSplit(stakeId, staker, originalAmount - uint104(splitAmount), splitAmount, newStakeId1, newStakeId2);
}
mergeStakes

Overview

Function to merge two stakes into one. Burns both stPWN NFTs and mints a new one. The new stake has lockup period of the first stake with a condition that the first stake lockup must be longer than or equal to the second one. The beneficiary of the new stake is also the stake owner.

This function takes four arguments:

  • uint256stakeId1

  • addressstakeBeneficiary1

  • uint256stakeId2

  • addressstakeBeneficiary2

Implementation

function mergeStakes(uint256 stakeId1, address stakeBeneficiary1, uint256 stakeId2, address stakeBeneficiary2)
    external
    returns (uint256 newStakeId)
{
    address staker = msg.sender;
    Stake storage stake1 = _stakes[stakeId1];
    Stake storage stake2 = _stakes[stakeId2];
    uint16 finalEpoch1 = stake1.initialEpoch + stake1.lockUpEpochs;
    uint16 finalEpoch2 = stake2.initialEpoch + stake2.lockUpEpochs;
    uint16 newInitialEpoch = epochClock.currentEpoch() + 1;

    // the first stake lockup end must be greater than or equal to the second stake lockup end
    // both stake lockup ends must be greater than the current epoch
    if (finalEpoch1 < finalEpoch2 || finalEpoch1 <= newInitialEpoch) {
        revert Error.LockUpPeriodMismatch();
    }

    uint8 newLockUpEpochs = uint8(finalEpoch1 - newInitialEpoch); // safe cast
    // only need to update second stake power changes if has different final epoch
    if (finalEpoch1 != finalEpoch2) {
        uint104 amount2 = stake2.amount;
        // clear second stake power changes if necessary
        if (finalEpoch2 > newInitialEpoch) {
            _updateTotalPower(amount2, newInitialEpoch, uint8(finalEpoch2 - newInitialEpoch), false);
        }
        // store new update power changes
        _updateTotalPower(amount2, newInitialEpoch, newLockUpEpochs, true);
    }

    // delete old stakes
    _deleteStake({ owner: staker, beneficiary: stakeBeneficiary1, stakeId: stakeId1 });
    _deleteStake({ owner: staker, beneficiary: stakeBeneficiary2, stakeId: stakeId2 });

    // create new stake
    uint104 newAmount = stake1.amount + stake2.amount;
    newStakeId = _createStake({
        owner: staker,
        beneficiary: staker,
        initialEpoch: newInitialEpoch,
        amount: newAmount,
        lockUpEpochs: newLockUpEpochs
    });

    // emit event
    emit StakeMerged(stakeId1, stakeId2, staker, newAmount, newLockUpEpochs, newStakeId);
}
increaseStake

Overview

Function to increase a stake. Both the amount of tokens and the lockup period of a stake can be increased. Old stake NFT is burned and a new one is created during the increase of a stake.

This function takes four arguments:

  • uint256stakeId

  • addressstakeBeneficiary

  • uint256additionalAmount

  • uint256additionalEpochs

Implementation

function increaseStake(uint256 stakeId, address stakeBeneficiary, uint256 additionalAmount, uint256 additionalEpochs)
    external
    returns (uint256 newStakeId)
{
    address staker = msg.sender;
    Stake storage stake = _stakes[stakeId];

    // additional amount or additional epochs must be greater than 0
    if (additionalAmount == 0 && additionalEpochs == 0) {
        revert Error.NothingToIncrease();
    }
    if (additionalAmount > type(uint88).max) {
        revert Error.InvalidAmount();
    }
    // to prevent rounding errors when computing power
    if (additionalAmount % 100 > 0) {
        revert Error.InvalidAmount();
    }
    if (additionalEpochs > 10 * EPOCHS_IN_YEAR) {
        revert Error.InvalidLockUpPeriod();
    }

    uint16 newInitialEpoch = epochClock.currentEpoch() + 1;
    uint16 oldFinalEpoch = stake.initialEpoch + stake.lockUpEpochs;
    uint8 newLockUpEpochs = SafeCast.toUint8(
        oldFinalEpoch <= newInitialEpoch ? additionalEpochs : oldFinalEpoch + additionalEpochs - newInitialEpoch
    );
    // extended lockup must be in <1; 5> + {10} years
    if (newLockUpEpochs < EPOCHS_IN_YEAR) {
        revert Error.InvalidLockUpPeriod();
    }
    if (newLockUpEpochs > 5 * EPOCHS_IN_YEAR && newLockUpEpochs != 10 * EPOCHS_IN_YEAR) {
        revert Error.InvalidLockUpPeriod();
    }

    uint104 oldAmount = stake.amount;
    uint104 newAmount = oldAmount + uint104(additionalAmount); // safe cast

    { // avoid stack too deep
        bool amountAdditionOnly = additionalEpochs == 0;

        // clear old power changes if adding epochs
        if (!amountAdditionOnly && newLockUpEpochs > additionalEpochs) {
            _updateTotalPower(oldAmount, newInitialEpoch, newLockUpEpochs - uint8(additionalEpochs), false);
        }

        // store new power changes
        uint104 amount = amountAdditionOnly ? uint104(additionalAmount) : newAmount;
        _updateTotalPower(amount, newInitialEpoch, newLockUpEpochs, true);
    }

    // delete original stake
    _deleteStake({ owner: staker, beneficiary: stakeBeneficiary, stakeId: stakeId });

    // create new stake
    newStakeId = _createStake({
        owner: staker,
        beneficiary: staker,
        initialEpoch: newInitialEpoch,
        amount: newAmount,
        lockUpEpochs: newLockUpEpochs
    });

    // transfer additional PWN tokens
    if (additionalAmount > 0) {
        pwnToken.transferFrom(staker, address(this), additionalAmount);
    }

    // emit event
    emit StakeIncreased(
        stakeId, staker, additionalAmount, newAmount, additionalEpochs, newLockUpEpochs, newStakeId
    );
}
withdrawStake

Overview

Function to withdraw a stake. Burns the stake NFT and transfers the underlying PWN tokens to the caller.

This function takes two arguments:

  • uint256stakeId

  • addressstakeBeneficiary

Implementation

function withdrawStake(uint256 stakeId, address stakeBeneficiary) external {
    address staker = msg.sender;
    Stake storage stake = _stakes[stakeId];

    // stake must be unlocked
    if (stake.initialEpoch + stake.lockUpEpochs > epochClock.currentEpoch()) {
        revert Error.WithrawalBeforeLockUpEnd();
    }

    // delete stake
    _deleteStake({ owner: staker, beneficiary: stakeBeneficiary, stakeId: stakeId });

    // transfer pwn tokens to the staker
    pwnToken.transfer(staker, stake.amount);

    // emit event
    emit StakeWithdrawn(stakeId, staker, stake.amount);
}
delegateStakePower

Overview

Function to delegate stakes voting power to another account.

This function takes three arguments:

  • uint256stakeId

  • addresscurrentBeneficiary

  • addressnewBeneficiary

Implementation

function delegateStakePower(uint256 stakeId, address currentBeneficiary, address newBeneficiary) external {
    address staker = msg.sender;

    // power already delegated to the new beneficiary
    if (currentBeneficiary == newBeneficiary) {
        revert Error.SameBeneficiary();
    }

    // staker must be stake owner
    _checkIsStakeOwner(staker, stakeId);

    // remove token from current beneficiary first to avoid duplicates
    _removeStakeFromBeneficiary(stakeId, currentBeneficiary);
    _addStakeToBeneficiary(stakeId, newBeneficiary);

    // emit event
    emit StakePowerDelegated(stakeId, currentBeneficiary, newBeneficiary);
}
getStake

Overview

Function to get information about a stake.

This function takes one argument:

  • uint256stakeId

Implementation

function getStake(uint256 stakeId) public view returns (StakeData memory stakeData) {
    Stake storage stake = _stakes[stakeId];
    uint16 currentEpoch = epochClock.currentEpoch();

    stakeData.stakeId = stakeId;
    stakeData.owner = stakedPWN.ownerOf(stakeId);
    stakeData.initialEpoch = stake.initialEpoch;
    stakeData.lockUpEpochs = stake.lockUpEpochs;
    stakeData.remainingEpochs = (stakeData.initialEpoch + stakeData.lockUpEpochs >= currentEpoch)
        ? uint8(stakeData.initialEpoch + stakeData.lockUpEpochs - currentEpoch) : 0;
    stakeData.currentMultiplier = (stakeData.initialEpoch <= currentEpoch && stakeData.remainingEpochs > 0)
        ? uint8(uint104(_power(100, stakeData.remainingEpochs))) : 0;
    stakeData.amount = stake.amount;
}
getStakes

Overview

Function to get information about multiple stakes.

This function takes one argument:

  • uint256[] calldatastakeIds

Implementation

function getStakes(uint256[] calldata stakeIds) external view returns (StakeData[] memory stakeData) {
    stakeData = new StakeData[](stakeIds.length);
    for (uint256 i; i < stakeIds.length; ++i) {
        stakeData[i] = getStake(stakeIds[i]);
    }
}

Events

event StakeCreated(uint256 indexed stakeId, address indexed staker, address indexed beneficiary, uint256 amount, uint256 lockUpEpochs);
event StakeSplit(uint256 indexed stakeId, address indexed staker, uint256 amount1, uint256 amount2, uint256 newStakeId1, uint256 newStakeId2);
event StakeMerged(uint256 indexed stakeId1, uint256 indexed stakeId2, address indexed staker, uint256 amount, uint256 lockUpEpochs, uint256 newStakeId);
event StakeIncreased(uint256 indexed stakeId, address indexed staker, uint256 additionalAmount, uint256 newAmount, uint256 additionalEpochs, uint256 newEpochs, uint256 newStakeId);
event StakeWithdrawn(uint256 indexed stakeId, address indexed staker, uint256 amount);
event StakePowerDelegated(uint256 indexed stakeId, address indexed originalBeneficiary, address indexed newBeneficiary);
StakeCreated

StakeCreated event is emitted when a stake is created.

This event has five parameters:

  • uint256 indexedstakeId

  • address indexedstaker

  • address indexedbeneficiary

  • uint256amount

  • uint256lockUpEpochs

StakeSplit

StakeSplit event is emitted when a stake is split into two.

This event has six parameters:

  • uint256 indexedstakeId

  • address indexedstaker

  • uint256amount1

  • uint256amount2

  • uint256newStakeId1

  • uint256newStakeId2

StakeMerged

StakeMerged event is emitted when two stakes are merged into one.

This event has six parameters:

  • uint256 indexedstakeId1

  • uint256 indexedstakeId2

  • address indexedstaker

  • uint256amount

  • uint256lockUpEpochs

  • uint256newStakeId

StakeIncreased

StakeIncreased event is emitted when a stake is increased. Both the amount of tokens and the lockup period can be increased. Old stake NFT is burned and a new one is created during the increase of a stake.

This event has seven parameters:

  • uint256 indexedstakeId

  • address indexedstaker

  • uint256additionalAmount

  • uint256newAmount

  • uint256additionalEpochs

  • uint256newEpochs

  • uint256newStakeId

StakeWithdrawn

StakeWithdrawn event is emitted when a stake is withdrawn.

This event has three parameters:

  • uint256 indexedstakeId

  • address indexedstaker

  • uint256amount

StakePowerDelegated

StakePowerDelegated event is emitted when stake power is transferred between beneficiaries. When a stake is created, the originalBeneficiary is zero address. When a stake is deleted, the newBeneficiary is zero address.

This event has three parameters:

  • uint256 indexedstakeId

  • address indexedoriginalBeneficiary

  • address indexednewBeneficiary

StakeData Struct

struct StakeData {
    uint256 stakeId;
    address owner;
    uint16 initialEpoch;
    uint8 lockUpEpochs;
    uint8 remainingEpochs;
    uint8 currentMultiplier;
    uint104 amount;
}

Checkout struct definition below to learn more about the return type of this function.

Checkout struct definition below to learn more about the return type of this function.

VoteEscrowedPWNBase
StakeData
StakeData
pwn_dao/src/token/vePWN/VoteEscrowedPWNStake.sol at main · PWNDAO/pwn_daoGitHub
Logo