Skip to content

Commit

Permalink
Staking tests (#228)
Browse files Browse the repository at this point in the history
Staking tests

Co-authored-by: Nicholas Addison <[email protected]>
  • Loading branch information
superduck35 and naddison36 authored Sep 14, 2021
1 parent 5ffc10c commit f8dd268
Show file tree
Hide file tree
Showing 39 changed files with 3,213 additions and 15,032 deletions.
6 changes: 4 additions & 2 deletions contracts/governance/staking/GamifiedToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ abstract contract GamifiedToken is
/**
* @param _nexus System nexus
* @param _rewardsToken Token that is being distributed as a reward. eg MTA
* @param _questManager Centralised manager of quests
* @param _hasPriceCoeff true if raw staked amount is multiplied by price coeff to get staked amount. eg BPT Staked Token
*/
constructor(
address _nexus,
Expand Down Expand Up @@ -248,7 +250,7 @@ abstract contract GamifiedToken is
Balance memory _oldBalance,
uint256 _oldScaledBalance,
uint8 _newMultiplier
) internal updateReward(_account) {
) private updateReward(_account) {
// 1. Set the questMultiplier
_balances[_account].questMultiplier = _newMultiplier;

Expand Down Expand Up @@ -551,5 +553,5 @@ abstract contract GamifiedToken is
return string(bytesArray);
}

uint256[45] private __gap;
uint256[46] private __gap;
}
17 changes: 11 additions & 6 deletions contracts/governance/staking/QuestManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ contract QuestManager is IQuestManager, Initializable, ContextUpgradeable, Immut
Quest[] private _quests;
/// @notice Timestamp at which the current season started
uint32 public override seasonEpoch;
/// @notice Timestamp at which the contract was created
uint32 public startTime;

/// @notice A whitelisted questMaster who can administer quests including signing user quests are completed.
address public override questMaster;
Expand All @@ -47,7 +49,7 @@ contract QuestManager is IQuestManager, Initializable, ContextUpgradeable, Immut
* @param _questSignerArg account that can sign user quests as completed
*/
function initialize(address _questMaster, address _questSignerArg) external initializer {
seasonEpoch = SafeCast.toUint32(block.timestamp);
startTime = SafeCast.toUint32(block.timestamp);
questMaster = _questMaster;
_questSigner = _questSignerArg;
}
Expand Down Expand Up @@ -118,6 +120,8 @@ contract QuestManager is IQuestManager, Initializable, ContextUpgradeable, Immut
* @dev Adds a new stakedToken
*/
function addStakedToken(address _stakedToken) external override onlyGovernor {
require(_stakedToken != address(0), "Invalid StakedToken");

_stakedTokens.push(_stakedToken);

emit StakedTokenAdded(_stakedToken);
Expand Down Expand Up @@ -185,6 +189,7 @@ contract QuestManager is IQuestManager, Initializable, ContextUpgradeable, Immut
* A new season can only begin after 9 months has passed.
*/
function startNewQuestSeason() external override questMasterOrGovernor {
require(block.timestamp > (startTime + 39 weeks), "First season has not elapsed");
require(block.timestamp > (seasonEpoch + 39 weeks), "Season has not elapsed");

uint256 len = _quests.length;
Expand Down Expand Up @@ -220,14 +225,14 @@ contract QuestManager is IQuestManager, Initializable, ContextUpgradeable, Immut
bytes calldata _signature
) external override {
uint256 len = _ids.length;
require(len > 0, "No quest ids");
require(len > 0, "No quest IDs");

uint8 questMultiplier = checkForSeasonFinish(_account);

// For each quest
for (uint256 i = 0; i < len; i++) {
require(_validQuest(_ids[i]), "Err: Invalid Quest");
require(!hasCompleted(_account, _ids[i]), "Err: Already Completed");
require(_validQuest(_ids[i]), "Invalid Quest ID");
require(!hasCompleted(_account, _ids[i]), "Quest already completed");
require(
SignatureVerifier.verify(_questSigner, _account, _ids, _signature),
"Invalid Quest Signer Signature"
Expand Down Expand Up @@ -295,8 +300,8 @@ contract QuestManager is IQuestManager, Initializable, ContextUpgradeable, Immut
questMultiplier += quest.multiplier;

uint256 len2 = _stakedTokens.length;
for (uint256 i = 0; i < len2; i++) {
IStakedToken(_stakedTokens[i]).applyQuestMultiplier(_accounts[i], questMultiplier);
for (uint256 j = 0; j < len2; j++) {
IStakedToken(_stakedTokens[j]).applyQuestMultiplier(_accounts[j], questMultiplier);
}
}

Expand Down
15 changes: 4 additions & 11 deletions contracts/governance/staking/StakedToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ contract StakedToken is GamifiedVotingToken {
* @param _stakedToken Core token that is staked and tracked (e.g. MTA)
* @param _cooldownSeconds Seconds a user must wait after she initiates her cooldown before withdrawal is possible
* @param _unstakeWindow Window in which it is possible to withdraw, following the cooldown period
* @param _hasPriceCoeff true if raw staked amount is multiplied by price coeff to get staked amount. eg BPT Staked Token
*/
constructor(
address _nexus,
Expand Down Expand Up @@ -205,8 +206,9 @@ contract StakedToken is GamifiedVotingToken {
Balance memory oldBalance = _balances[_msgSender()];
// If we have missed the unstake window, or the user has chosen to exit the cooldown,
// then reset the timestamp to 0
bool exitCooldown = _exitCooldown ||
block.timestamp > (oldBalance.cooldownTimestamp + COOLDOWN_SECONDS + UNSTAKE_WINDOW);
bool exitCooldown = _exitCooldown || (
oldBalance.cooldownTimestamp > 0 &&
block.timestamp > (oldBalance.cooldownTimestamp + COOLDOWN_SECONDS + UNSTAKE_WINDOW) );
if (exitCooldown) {
emit CooldownExited(_msgSender());
}
Expand Down Expand Up @@ -431,15 +433,6 @@ contract StakedToken is GamifiedVotingToken {
_transferAndStake(_value, address(0), false);
}

/**
* @dev Does nothing, because there is no lockup here.
**/
function increaseLockLength(
uint256 /* _unlockTime */
) external virtual {
return;
}

/**
* @dev Backwards compatibility. Previously a lock would run out and a user would call this. Now, it will take 2 calls
* to exit in order to leave. The first will initiate the cooldown period, and the second will execute a full withdrawal.
Expand Down
25 changes: 13 additions & 12 deletions contracts/governance/staking/StakedTokenBPT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ contract StakedTokenBPT is StakedToken {
event BalClaimed();
event BalRecipientChanged(address newRecipient);
event PriceCoefficientUpdated(uint256 newPriceCoeff);
event FeesConverted(uint256 bpt, uint256 mta);

/***************************************
INIT
Expand Down Expand Up @@ -144,26 +145,23 @@ contract StakedTokenBPT is StakedToken {
// 1. Sell the BPT
uint256 stakingBalBefore = STAKED_TOKEN.balanceOf(address(this));
uint256 mtaBalBefore = REWARDS_TOKEN.balanceOf(address(this));
(address[] memory tokens, uint256[] memory balances, ) = balancerVault.getPoolTokens(
poolId
);
(address[] memory tokens, , ) = balancerVault.getPoolTokens(poolId);
require(tokens[0] == address(REWARDS_TOKEN), "MTA in wrong place");

// 1.1. Calculate minimum output amount, assuming bpt 80/20 gives ~4% max slippage
uint256[] memory minOut = new uint256[](1);
address[] memory exitToken = new address[](1);
// 1.1. Calculate minimum output amount
uint256[] memory minOut = new uint256[](2);
{
uint256 unitsPerToken = (balances[0] * 12e17) / STAKED_TOKEN.totalSupply();
minOut[0] = (pendingBPT * unitsPerToken) / 1e18;
exitToken[0] = address(REWARDS_TOKEN);
// 10% discount from the latest pcoeff
// e.g. 1e18 * 42000 / 11000 = 3.81e18
minOut[0] = (pendingBPT * priceCoefficient) / 11000;
}

// 1.2. Exits to here, from here. Assumes token is in position 0
balancerVault.exitPool(
poolId,
address(this),
payable(address(this)),
ExitPoolRequest(exitToken, minOut, bytes(abi.encode(0, pendingBPT - 1, 0)), false)
ExitPoolRequest(tokens, minOut, bytes(abi.encode(0, pendingBPT - 1, 0)), false)
);

// 2. Verify and update state
Expand All @@ -174,8 +172,11 @@ contract StakedTokenBPT is StakedToken {
);

// 3. Inform HeadlessRewards about the new rewards
uint256 mtaBalAfter = REWARDS_TOKEN.balanceOf(address(this));
pendingAdditionalReward += (mtaBalAfter - mtaBalBefore);
uint256 received = REWARDS_TOKEN.balanceOf(address(this)) - mtaBalBefore;
require(received >= minOut[0], "Must receive tokens");
super._notifyAdditionalReward(received);

emit FeesConverted(pendingBPT, received);
}

/**
Expand Down
Loading

0 comments on commit f8dd268

Please sign in to comment.