feat(StakeVault): add withdraw function and improve documentation

This commit introduces new functions for keeping track of the total amount staked and implements a function to check the available amount of tokens that can be withdrawn. Users are able to withdraw tokens that are not commited to stake by calling the withdraw functions. These changes enhance the overall usability and flexibility of the StakeVault contract.
This commit is contained in:
Ricardo Guilherme Schmidt 2024-10-01 18:30:10 -03:00
parent 769f32f718
commit c08fe417ba
No known key found for this signature in database
GPG Key ID: 54B4454CC123AD17
3 changed files with 243 additions and 84 deletions

View File

@ -8,14 +8,14 @@
| MAX_LOCKUP_PERIOD | 361 | 361 | 361 | 361 | 4 |
| MIN_LOCKUP_PERIOD | 264 | 264 | 264 | 264 | 12 |
| YEAR | 263 | 263 | 263 | 263 | 637 |
| accounts | 1597 | 1597 | 1597 | 1597 | 144273 |
| accounts | 1597 | 1597 | 1597 | 1597 | 144393 |
| calculateMPToMint | 740 | 740 | 740 | 740 | 1276 |
| currentEpoch | 384 | 1050 | 384 | 2384 | 54 |
| epochEnd | 627 | 627 | 627 | 2627 | 23675 |
| epochEnd | 627 | 627 | 627 | 2627 | 23695 |
| epochReward | 1425 | 2925 | 1425 | 5925 | 3 |
| executeAccount(address) | 151152 | 151152 | 151152 | 151152 | 2 |
| executeAccount(address,uint256) | 26562 | 72616 | 74122 | 217879 | 141860 |
| executeEpoch() | 23458 | 120800 | 121956 | 938985 | 23564 |
| executeAccount(address,uint256) | 26562 | 72609 | 74122 | 217879 | 141980 |
| executeEpoch() | 23458 | 120804 | 121956 | 938985 | 23584 |
| executeEpoch(uint256) | 23861 | 24497 | 23861 | 26090 | 7 |
| isVault | 540 | 948 | 540 | 2540 | 680 |
| lock | 23862 | 23862 | 23862 | 23862 | 1 |
@ -24,7 +24,7 @@
| migrationInitialize | 24602 | 24602 | 24602 | 24602 | 1 |
| newEpoch | 441 | 441 | 441 | 441 | 5 |
| owner | 2432 | 2432 | 2432 | 2432 | 13 |
| pendingMPToBeMinted | 364 | 364 | 364 | 364 | 46432 |
| pendingMPToBeMinted | 364 | 364 | 364 | 364 | 46472 |
| pendingReward | 386 | 1420 | 2386 | 2386 | 29 |
| previousManager | 275 | 275 | 275 | 275 | 13 |
| setVault | 46239 | 46239 | 46239 | 46239 | 139 |
@ -35,7 +35,7 @@
| startTime | 306 | 306 | 306 | 306 | 21 |
| totalSupply | 762 | 1943 | 2762 | 2762 | 22 |
| totalSupplyBalance | 407 | 1807 | 2407 | 2407 | 20 |
| totalSupplyMP | 362 | 362 | 362 | 2362 | 46453 |
| totalSupplyMP | 362 | 362 | 362 | 2362 | 46493 |
| unstake | 23819 | 23819 | 23819 | 23819 | 1 |
@ -44,7 +44,7 @@
| Deployment Cost | Deployment Size | | | | |
| 0 | 0 | | | | |
| Function Name | min | avg | median | max | # calls |
| getExpiredMP | 2427 | 2427 | 2427 | 2427 | 23725 |
| getExpiredMP | 2427 | 2427 | 2427 | 2427 | 23745 |
| transferOwnership | 28533 | 28533 | 28533 | 28533 | 1 |
@ -53,23 +53,23 @@
| Deployment Cost | Deployment Size | | | | |
| 0 | 0 | | | | |
| Function Name | min | avg | median | max | # calls |
| acceptMigration | 35280 | 35280 | 35280 | 35280 | 2 |
| leave | 35266 | 35266 | 35266 | 35266 | 1 |
| lock | 43329 | 96146 | 64421 | 204745 | 7 |
| owner | 362 | 362 | 362 | 362 | 679 |
| stake | 27265 | 282079 | 265719 | 351866 | 684 |
| stakedToken | 212 | 212 | 212 | 212 | 2 |
| unstake | 40167 | 95818 | 78688 | 233549 | 11 |
| acceptMigration | 35303 | 35303 | 35303 | 35303 | 2 |
| leave | 35327 | 35327 | 35327 | 35327 | 1 |
| lock | 43352 | 96169 | 64444 | 204768 | 7 |
| owner | 364 | 364 | 364 | 364 | 679 |
| stake | 49545 | 304906 | 288231 | 374378 | 684 |
| stakedToken | 257 | 257 | 257 | 257 | 2 |
| unstake | 40209 | 98514 | 82820 | 237681 | 11 |
| contracts/VaultFactory.sol:VaultFactory contract | | | | | |
|--------------------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost | Deployment Size | | | | |
| 0 | 0 | | | | |
| Function Name | min | avg | median | max | # calls |
| createVault | 696553 | 696553 | 696553 | 696553 | 683 |
| setStakeManager | 23710 | 26669 | 26076 | 30222 | 3 |
| stakeManager | 368 | 1868 | 2368 | 2368 | 4 |
| contracts/VaultFactory.sol:VaultFactory contract | | | | | |
|--------------------------------------------------|-----------------|---------|---------|---------|---------|
| Deployment Cost | Deployment Size | | | | |
| 0 | 0 | | | | |
| Function Name | min | avg | median | max | # calls |
| createVault | 1127500 | 1127500 | 1127500 | 1127500 | 683 |
| setStakeManager | 23710 | 26669 | 26076 | 30222 | 3 |
| stakeManager | 368 | 1868 | 2368 | 2368 | 4 |
| lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol:ERC20 contract | | | | | |
@ -77,16 +77,16 @@
| Deployment Cost | Deployment Size | | | | |
| 0 | 0 | | | | |
| Function Name | min | avg | median | max | # calls |
| approve | 46175 | 46239 | 46199 | 46367 | 679 |
| balanceOf | 561 | 2107 | 2561 | 2561 | 30744 |
| approve | 46175 | 46235 | 46199 | 46367 | 679 |
| balanceOf | 561 | 2108 | 2561 | 2561 | 30764 |
| script/Deploy.s.sol:Deploy contract | | | | | |
|-------------------------------------|-----------------|---------|---------|---------|---------|
| Deployment Cost | Deployment Size | | | | |
| 6118940 | 29538 | | | | |
| 6587720 | 31730 | | | | |
| Function Name | min | avg | median | max | # calls |
| run | 5316305 | 5316305 | 5316305 | 5316305 | 66 |
| run | 5755371 | 5755371 | 5755371 | 5755371 | 66 |
| script/DeployMigrationStakeManager.s.sol:DeployMigrationStakeManager contract | | | | | |
@ -117,9 +117,9 @@
| test/script/DeployBroken.s.sol:DeployBroken contract | | | | | |
|------------------------------------------------------|-----------------|---------|---------|---------|---------|
| Deployment Cost | Deployment Size | | | | |
| 4803693 | 23336 | | | | |
| 5272431 | 25528 | | | | |
| Function Name | min | avg | median | max | # calls |
| run | 4156127 | 4156127 | 4156127 | 4156127 | 1 |
| run | 4595193 | 4595193 | 4595193 | 4595193 | 1 |

View File

@ -1,38 +1,38 @@
CreateVaultTest:testDeployment() (gas: 9774)
CreateVaultTest:test_createVault() (gas: 714022)
CreateVaultTest:test_createVault() (gas: 1145016)
ExecuteAccountTest:testDeployment() (gas: 28785)
ExecuteAccountTest:test_ExecuteAccountLimit() (gas: 1580255)
ExecuteAccountTest:test_ExecuteAccountMintMP() (gas: 5297777)
ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 1788412)
ExecuteAccountTest:test_ShouldNotMintMoreThanCap() (gas: 321397553)
ExecuteAccountTest:test_ExecuteAccountLimit() (gas: 2033714)
ExecuteAccountTest:test_ExecuteAccountMintMP() (gas: 6658154)
ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 2241871)
ExecuteAccountTest:test_ShouldNotMintMoreThanCap() (gas: 325025225)
ExecuteEpochTest:testDeployment() (gas: 28786)
ExecuteEpochTest:testNewDeployment() (gas: 30858)
ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterEpochEnd() (gas: 1366211)
ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1385045)
ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 1629507)
ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1395200)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochAfterEnd() (gas: 1936210)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochs() (gas: 2523553)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1479491)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2533729)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1489623)
ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterEpochEnd() (gas: 1819670)
ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1838504)
ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2082966)
ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1848659)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochAfterEnd() (gas: 2389669)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochs() (gas: 2977012)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1932950)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2987188)
ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1943082)
ExecuteEpochTest:test_ExecuteEpochNewEpoch() (gas: 1122339)
ExecuteEpochTest:test_ExecuteEpochShouldIncreaseEpoch() (gas: 92413)
ExecuteEpochTest:test_ExecuteEpochShouldIncreasePendingReward() (gas: 256446)
ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochBeforeEnd() (gas: 38984)
ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 149638)
LeaveTest:testDeployment() (gas: 28763)
LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 1327993)
LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 1781536)
LeaveTest:test_RevertWhen_SenderIsNotVault() (gas: 31964)
LockTest:testDeployment() (gas: 28763)
LockTest:test_NewLockupPeriod() (gas: 1329045)
LockTest:test_RevertWhen_InvalidNewLockupPeriod() (gas: 1301374)
LockTest:test_RevertWhen_InvalidUpdateLockupPeriod() (gas: 1545884)
LockTest:test_NewLockupPeriod() (gas: 1782527)
LockTest:test_RevertWhen_InvalidNewLockupPeriod() (gas: 1754856)
LockTest:test_RevertWhen_InvalidUpdateLockupPeriod() (gas: 1999366)
LockTest:test_RevertWhen_SenderIsNotVault() (gas: 31856)
LockTest:test_ShouldIncreaseBonusMP() (gas: 1311575)
LockTest:test_UpdateLockupPeriod() (gas: 1634540)
LockTest:test_ShouldIncreaseBonusMP() (gas: 1765057)
LockTest:test_UpdateLockupPeriod() (gas: 2088045)
MigrateTest:testDeployment() (gas: 28763)
MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 1292046)
MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 1745528)
MigrateTest:test_RevertWhen_SenderIsNotVault() (gas: 31976)
MigrationInitializeTest:testDeployment() (gas: 28763)
MigrationInitializeTest:test_RevertWhen_MigrationPending() (gas: 5181388)
@ -44,24 +44,24 @@ SetStakeManagerTest:test_RevertWhen_InvalidStakeManagerAddress() (gas: 63105)
SetStakeManagerTest:test_SetStakeManager() (gas: 41301)
StakeManagerTest:testDeployment() (gas: 28535)
StakeTest:testDeployment() (gas: 28741)
StakeTest:test_RevertWhen_InvalidLockupPeriod() (gas: 1080635)
StakeTest:test_RevertWhen_Restake() (gas: 1316847)
StakeTest:test_RevertWhen_RestakeWithLock() (gas: 1320861)
StakeTest:test_RevertWhen_InvalidLockupPeriod() (gas: 1556142)
StakeTest:test_RevertWhen_Restake() (gas: 1775486)
StakeTest:test_RevertWhen_RestakeWithLock() (gas: 1779500)
StakeTest:test_RevertWhen_SenderIsNotVault() (gas: 32018)
StakeTest:test_RevertWhen_StakeIsTooLow() (gas: 817762)
StakeTest:test_RevertWhen_StakeTokenTransferFails() (gas: 211363)
StakeTest:test_StakeWithLockBonusMP() (gas: 2357564)
StakeTest:test_StakeWithoutLockUpTimeMintsMultiplierPoints() (gas: 1322003)
StakedTokenTest:testStakeToken() (gas: 7616)
StakeTest:test_RevertWhen_StakeIsTooLow() (gas: 1251089)
StakeTest:test_RevertWhen_StakeTokenTransferFails() (gas: 229187)
StakeTest:test_StakeWithLockBonusMP() (gas: 3264505)
StakeTest:test_StakeWithoutLockUpTimeMintsMultiplierPoints() (gas: 1778561)
StakedTokenTest:testStakeToken() (gas: 7661)
UnstakeTest:testDeployment() (gas: 28785)
UnstakeTest:test_RevertWhen_AmountMoreThanBalance() (gas: 1297407)
UnstakeTest:test_RevertWhen_FundsLocked() (gas: 1342144)
UnstakeTest:test_RevertWhen_AmountMoreThanBalance() (gas: 1750908)
UnstakeTest:test_RevertWhen_FundsLocked() (gas: 1795687)
UnstakeTest:test_RevertWhen_SenderIsNotVault() (gas: 31857)
UnstakeTest:test_UnstakeShouldBurnMultiplierPoints() (gas: 6526945)
UnstakeTest:test_UnstakeShouldReturnFund_NoLockUp() (gas: 1319573)
UnstakeTest:test_UnstakeShouldReturnFund_WithLockUp() (gas: 1440205)
UnstakeTest:test_UnstakeShouldBurnMultiplierPoints() (gas: 6893844)
UnstakeTest:test_UnstakeShouldReturnFund_NoLockUp() (gas: 1776131)
UnstakeTest:test_UnstakeShouldReturnFund_WithLockUp() (gas: 1896763)
UserFlowsTest:testDeployment() (gas: 28763)
UserFlowsTest:test_PendingMPToBeMintedCannotBeGreaterThanTotalSupplyMP(uint8,uint128) (runs: 106, μ: 131402731, ~: 130856521)
UserFlowsTest:test_StakeWithLockUpTimeLocksStake() (gas: 1481301)
UserFlowsTest:test_StakedSupplyShouldIncreaseAndDecreaseAgain() (gas: 2494771)
UserFlowsTest:test_PendingMPToBeMintedCannotBeGreaterThanTotalSupplyMP(uint8,uint128) (runs: 106, μ: 134225064, ~: 133577563)
UserFlowsTest:test_StakeWithLockUpTimeLocksStake() (gas: 1937901)
UserFlowsTest:test_StakedSupplyShouldIncreaseAndDecreaseAgain() (gas: 3406943)
VaultFactoryTest:testDeployment() (gas: 9774)

View File

@ -9,56 +9,161 @@ import { StakeManager } from "./StakeManager.sol";
/**
* @title StakeVault
* @author Ricardo Guilherme Schmidt <ricardo3@status.im>
* @notice Secures user stake
* @notice A contract to secure user stakes and manage staking with StakeManager.
* @dev This contract is owned by the user and allows staking, unstaking, and withdrawing tokens.
*/
contract StakeVault is Ownable {
error StakeVault__NoEnoughAvailableBalance();
error StakeVault__InvalidDestinationAddress();
error StakeVault__MigrationNotAvailable();
error StakeVault__StakingFailed();
error StakeVault__UnstakingFailed();
StakeManager private stakeManager;
ERC20 public immutable STAKED_TOKEN;
uint256 public amountStaked = 0;
event Staked(address from, address to, uint256 _amount, uint256 time);
/**
* @dev Emitted when tokens are staked.
* @param from The address from which tokens are transferred.
* @param to The address receiving the staked tokens (this contract).
* @param amount The amount of tokens staked.
* @param time The time period for which tokens are staked.
*/
event Staked(address indexed from, address indexed to, uint256 amount, uint256 time);
modifier validDestination(address _destination) {
if (_destination == address(0)) {
revert StakeVault__InvalidDestinationAddress();
}
_;
}
/**
* @notice Initializes the contract with the owner, staked token, and stake manager.
* @param _owner The address of the owner.
* @param _stakedToken The ERC20 token to be staked.
* @param _stakeManager The address of the StakeManager contract.
*/
constructor(address _owner, ERC20 _stakedToken, StakeManager _stakeManager) {
_transferOwnership(_owner);
STAKED_TOKEN = _stakedToken;
stakeManager = _stakeManager;
}
/**
* @notice Stake tokens for a specified time.
* @param _amount The amount of tokens to stake.
* @param _time The time period to stake for.
*/
function stake(uint256 _amount, uint256 _time) external onlyOwner {
bool success = STAKED_TOKEN.transferFrom(msg.sender, address(this), _amount);
if (!success) {
revert StakeVault__StakingFailed();
}
stakeManager.stake(_amount, _time);
emit Staked(msg.sender, address(this), _amount, _time);
_stake(_amount, _time, msg.sender);
}
/**
* @notice Stake tokens from a specified address for a specified time.
* @param _amount The amount of tokens to stake.
* @param _time The time period to stake for.
* @param _from The address from which tokens will be transferred.
*/
function stake(uint256 _amount, uint256 _time, address _from) external onlyOwner {
_stake(_amount, _time, _from);
}
/**
* @notice Extends the lock time of the stake.
* @param _time The additional time to lock the stake.
*/
function lock(uint256 _time) external onlyOwner {
stakeManager.lock(_time);
}
/**
* @notice Unstake a specified amount of tokens and send to the owner.
* @param _amount The amount of tokens to unstake.
*/
function unstake(uint256 _amount) external onlyOwner {
stakeManager.unstake(_amount);
bool success = STAKED_TOKEN.transfer(msg.sender, _amount);
if (!success) {
revert StakeVault__UnstakingFailed();
}
_unstake(_amount, msg.sender);
}
/**
* @notice Unstake a specified amount of tokens and send to a destination address.
* @param _amount The amount of tokens to unstake.
* @param _destination The address to receive the unstaked tokens.
*/
function unstake(uint256 _amount, address _destination) external onlyOwner validDestination(_destination) {
_unstake(_amount, _destination);
}
/**
* @notice Withdraw tokens from the contract.
* @param _token The ERC20 token to withdraw.
* @param _amount The amount of tokens to withdraw.
*/
function withdraw(ERC20 _token, uint256 _amount) external onlyOwner {
_withdraw(_token, _amount, msg.sender);
}
/**
* @notice Withdraw tokens from the contract to a destination address.
* @param _token The ERC20 token to withdraw.
* @param _amount The amount of tokens to withdraw.
* @param _destination The address to receive the tokens.
*/
function withdraw(
ERC20 _token,
uint256 _amount,
address _destination
)
external
onlyOwner
validDestination(_destination)
{
_withdraw(_token, _amount, _destination);
}
/**
* @notice Withdraw Ether from the contract to the owner address.
* @param _amount The amount of Ether to withdraw.
*/
function withdraw(uint256 _amount) external onlyOwner {
_withdraw(_amount, payable(msg.sender));
}
/**
* @notice Withdraw Ether from the contract to a destination address.
* @param _amount The amount of Ether to withdraw.
* @param _destination The address to receive the Ether.
*/
function withdraw(
uint256 _amount,
address payable _destination
)
external
onlyOwner
validDestination(_destination)
{
_withdraw(_amount, _destination);
}
/**
* @notice Reject migration, leave staking contract and withdraw all tokens to the owner.
*/
function leave() external onlyOwner {
stakeManager.migrateTo(false);
STAKED_TOKEN.transferFrom(address(this), msg.sender, STAKED_TOKEN.balanceOf(address(this)));
_leave(msg.sender);
}
/**
* @notice Reject migration, leave staking contract and withdraw all tokens to a destination address.
* @param _destination The address to receive the tokens.
*/
function leave(address _destination) external onlyOwner validDestination(_destination) {
_leave(_destination);
}
/**
* @notice Opt-in migration to a new StakeManager contract.
* @dev Updates the stakeManager to the migrated contract.
*/
function acceptMigration() external onlyOwner {
StakeManager migrated = stakeManager.migrateTo(true);
@ -66,7 +171,61 @@ contract StakeVault is Ownable {
stakeManager = migrated;
}
/**
* @notice Returns the staked token.
* @return The ERC20 staked token.
*/
function stakedToken() external view returns (ERC20) {
return STAKED_TOKEN;
}
/**
* @notice Returns the available amount of a token that can be withdrawn.
* @param _token The ERC20 token to check.
* @return The amount of token available for withdrawal.
*/
function availableWithdraw(ERC20 _token) external view returns (uint256) {
if (_token == STAKED_TOKEN) {
return STAKED_TOKEN.balanceOf(address(this)) - amountStaked;
}
return _token.balanceOf(address(this));
}
function _stake(uint256 _amount, uint256 _time, address _source) internal {
amountStaked += _amount;
bool success = STAKED_TOKEN.transferFrom(_source, address(this), _amount);
if (!success) {
revert StakeVault__StakingFailed();
}
stakeManager.stake(_amount, _time);
emit Staked(_source, address(this), _amount, _time);
}
function _unstake(uint256 _amount, address _destination) internal {
stakeManager.unstake(_amount);
bool success = STAKED_TOKEN.transfer(_destination, _amount);
amountStaked -= _amount;
if (!success) {
revert StakeVault__UnstakingFailed();
}
}
function _leave(address _destination) internal {
stakeManager.migrateTo(false);
STAKED_TOKEN.transferFrom(address(this), _destination, STAKED_TOKEN.balanceOf(address(this)));
amountStaked = 0;
}
function _withdraw(ERC20 _token, uint256 _amount, address _destination) internal {
if (_token == STAKED_TOKEN && STAKED_TOKEN.balanceOf(address(this)) - amountStaked < _amount) {
revert StakeVault__NoEnoughAvailableBalance();
}
_token.transfer(_destination, _amount);
}
function _withdraw(uint256 _amount, address payable _destination) internal {
_destination.transfer(_amount);
}
}