Skip to content

Commit

Permalink
Merge b88c57f into 42e9a8c
Browse files Browse the repository at this point in the history
  • Loading branch information
alsco77 committed Aug 20, 2021
2 parents 42e9a8c + b88c57f commit 8ae4a85
Show file tree
Hide file tree
Showing 16 changed files with 528 additions and 95 deletions.
52 changes: 35 additions & 17 deletions contracts/governance/staking/GamifiedToken.sol
Expand Up @@ -30,8 +30,6 @@ abstract contract GamifiedToken is
ContextUpgradeable,
HeadlessStakingRewards
{
/// @notice address that signs user quests have been completed
address public immutable _signer;
/// @notice name of this token (ERC20)
string public override name;
/// @notice symbol of this token (ERC20)
Expand All @@ -48,8 +46,8 @@ abstract contract GamifiedToken is
/// @notice Timestamp at which the current season started
uint32 public seasonEpoch;

/// @notice A whitelisted questMaster who can add quests
address internal _questMaster;
/// @notice A whitelisted questMaster who can administer quests including signing user quests are completed.
address public questMaster;

/// @notice Tracks the cooldowns for all users
mapping(address => CooldownData) public stakersCooldowns;
Expand All @@ -65,38 +63,37 @@ abstract contract GamifiedToken is
event QuestComplete(address indexed user, uint256 indexed id);
event QuestExpired(uint16 indexed id);
event QuestSeasonEnded();
event QuestMaster(address oldQuestMaster, address newQuestMaster);

/***************************************
INIT
****************************************/

/**
* @param _signerArg Signer address is used to verify completion of quests off chain
* @param _nexus System nexus
* @param _rewardsToken Token that is being distributed as a reward. eg MTA
*/
constructor(
address _signerArg,
address _nexus,
address _rewardsToken
) HeadlessStakingRewards(_nexus, _rewardsToken) {
_signer = _signerArg;
}
constructor(address _nexus, address _rewardsToken)
HeadlessStakingRewards(_nexus, _rewardsToken)
{}

/**
* @param _nameArg Token name
* @param _symbolArg Token symbol
* @param _rewardsDistributorArg mStable Rewards Distributor
* @param _questMaster account that can sign user quests as completed
*/
function __GamifiedToken_init(
string memory _nameArg,
string memory _symbolArg,
address _rewardsDistributorArg
address _rewardsDistributorArg,
address _questMaster
) internal initializer {
__Context_init_unchained();
name = _nameArg;
symbol = _symbolArg;
seasonEpoch = SafeCast.toUint32(block.timestamp);
questMaster = _questMaster;
HeadlessStakingRewards._initialize(_rewardsDistributorArg);
}

Expand All @@ -109,7 +106,20 @@ abstract contract GamifiedToken is
}

function _questMasterOrGovernor() internal view {
require(_msgSender() == _questMaster || _msgSender() == _governor(), "Not verified");
require(_msgSender() == questMaster || _msgSender() == _governor(), "Not verified");
}

/***************************************
Admin
****************************************/

/**
* @dev Sets the quest master that can sign user quests as being completed
*/
function setQuestMaster(address _newQuestMaster) external questMasterOrGovernor() {
emit QuestMaster(questMaster, _newQuestMaster);

questMaster = _newQuestMaster;
}

/***************************************
Expand Down Expand Up @@ -237,7 +247,7 @@ abstract contract GamifiedToken is
require(_validQuest(_ids[i]), "Err: Invalid Quest");
require(!hasCompleted(_account, _ids[i]), "Err: Already Completed");
require(
SignatureVerifier.verify(_signer, _account, _ids[i], _signatures[i]),
SignatureVerifier.verify(questMaster, _account, _ids[i], _signatures[i]),
"Err: Invalid Signature"
);

Expand Down Expand Up @@ -476,19 +486,27 @@ abstract contract GamifiedToken is
* @param _account Address of user
* @param _rawAmount Raw amount of tokens to remove
* @param _exitCooldown Exit the cooldown?
* @param _finalise Has recollateralisation happened? If so, everything is cooled down
*/
function _burnRaw(
address _account,
uint256 _rawAmount,
bool _exitCooldown
bool _exitCooldown,
bool _finalise
) internal virtual updateReward(_account) {
require(_account != address(0), "ERC20: burn from zero address");

// 1. Get and update current balance
(Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
CooldownData memory cooldownData = stakersCooldowns[_msgSender()];
uint256 totalRaw = oldBalance.raw + cooldownData.units;
// 1.1 Update
// 1.1. If _finalise, move everything to cooldown
if (_finalise) {
_balances[_account].raw = 0;
stakersCooldowns[_account].units = SafeCast.toUint128(totalRaw);
cooldownData.units = SafeCast.toUint128(totalRaw);
}
// 1.2. Update
require(cooldownData.units >= _rawAmount, "ERC20: burn amount > balance");
unchecked {
stakersCooldowns[_account].units -= SafeCast.toUint128(_rawAmount);
Expand Down
3 changes: 1 addition & 2 deletions contracts/governance/staking/GamifiedVotingToken.sol
Expand Up @@ -55,10 +55,9 @@ abstract contract GamifiedVotingToken is Initializable, GamifiedToken {
);

constructor(
address _signer,
address _nexus,
address _rewardsToken
) GamifiedToken(_signer, _nexus, _rewardsToken) {}
) GamifiedToken(_nexus, _rewardsToken) {}

function __GamifiedVotingToken_init() internal initializer {}

Expand Down
22 changes: 8 additions & 14 deletions contracts/governance/staking/StakedToken.sol
Expand Up @@ -2,8 +2,6 @@
pragma solidity 0.8.6;
pragma abicoder v2;

import "hardhat/console.sol";

import { IStakedToken } from "./interfaces/IStakedToken.sol";
import { GamifiedVotingToken } from "./GamifiedVotingToken.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
Expand Down Expand Up @@ -66,21 +64,19 @@ contract StakedToken is IStakedToken, GamifiedVotingToken {
****************************************/

/**
* @param _signer Signer address is used to verify completion of quests off chain
* @param _nexus System nexus
* @param _rewardsToken Token that is being distributed as a reward. eg MTA
* @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
*/
constructor(
address _signer,
address _nexus,
address _rewardsToken,
address _stakedToken,
uint256 _cooldownSeconds,
uint256 _unstakeWindow
) GamifiedVotingToken(_signer, _nexus, _rewardsToken) {
) GamifiedVotingToken(_nexus, _rewardsToken) {
STAKED_TOKEN = IERC20(_stakedToken);
COOLDOWN_SECONDS = _cooldownSeconds;
UNSTAKE_WINDOW = _unstakeWindow;
Expand All @@ -90,13 +86,15 @@ contract StakedToken is IStakedToken, GamifiedVotingToken {
* @param _nameArg Token name
* @param _symbolArg Token symbol
* @param _rewardsDistributorArg mStable Rewards Distributor
* @param _questMaster account that signs user quests as completed
*/
function initialize(
string memory _nameArg,
string memory _symbolArg,
address _rewardsDistributorArg
address _rewardsDistributorArg,
address _questMaster
) external initializer {
__GamifiedToken_init(_nameArg, _symbolArg, _rewardsDistributorArg);
__GamifiedToken_init(_nameArg, _symbolArg, _rewardsDistributorArg, _questMaster);
safetyData = SafetyData({ collateralisationRatio: 1e18, slashingPercentage: 0 });
}

Expand Down Expand Up @@ -131,10 +129,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken {

function _assertNotContract() internal view {
if (_msgSender() != tx.origin) {
require(
whitelistedWrappers[_msgSender()],
"Transactions from non-whitelisted smart contracts not allowed"
);
require(whitelistedWrappers[_msgSender()], "Not a whitelisted contract");
}
}

Expand Down Expand Up @@ -257,7 +252,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken {
// Is the contract post-recollateralisation?
if (safetyData.collateralisationRatio != 1e18) {
// 1. If recollateralisation has occured, the contract is finished and we can skip all checks
_burnRaw(_msgSender(), _amount, false);
_burnRaw(_msgSender(), _amount, false, true);
// 2. Return a proportionate amount of tokens, based on the collateralisation ratio
STAKED_TOKEN.safeTransfer(
_recipient,
Expand Down Expand Up @@ -298,7 +293,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken {
bool exitCooldown = _exitCooldown || totalWithdraw == maxWithdrawal;

// 5. Settle the withdrawal by burning the voting tokens
_burnRaw(_msgSender(), totalWithdraw, exitCooldown);
_burnRaw(_msgSender(), totalWithdraw, exitCooldown, false);
// Log any redemption fee to the rewards contract
_notifyAdditionalReward(totalWithdraw - userWithdrawal);
// Finally transfer tokens back to recipient
Expand Down Expand Up @@ -382,7 +377,6 @@ contract StakedToken is IStakedToken, GamifiedVotingToken {
onlyGovernor
onlyBeforeRecollateralisation
{
require(safetyData.collateralisationRatio == 1e18, "Process already begun");
require(_newRate <= 5e17, "Cannot exceed 50%");

safetyData.slashingPercentage = SafeCast.toUint128(_newRate);
Expand Down
4 changes: 1 addition & 3 deletions contracts/governance/staking/StakedTokenBPT.sol
Expand Up @@ -24,7 +24,6 @@ contract StakedTokenBPT is StakedToken {
event BalRecipientChanged(address newRecipient);

/**
* @param _signer Signer address is used to verify completion of quests off chain
* @param _nexus System nexus
* @param _rewardsToken Token that is being distributed as a reward. eg MTA
* @param _stakedToken Core token that is staked and tracked (e.g. MTA)
Expand All @@ -33,14 +32,13 @@ contract StakedTokenBPT is StakedToken {
* @param _bal Balancer addresses, [0] = $BAL addr, [1] = designated recipient
*/
constructor(
address _signer,
address _nexus,
address _rewardsToken,
address _stakedToken,
uint256 _cooldownSeconds,
uint256 _unstakeWindow,
address[2] memory _bal
) StakedToken(_signer, _nexus, _rewardsToken, _stakedToken, _cooldownSeconds, _unstakeWindow) {
) StakedToken(_nexus, _rewardsToken, _stakedToken, _cooldownSeconds, _unstakeWindow) {
BAL = IERC20(_bal[0]);
balRecipient = _bal[1];
}
Expand Down
4 changes: 1 addition & 3 deletions contracts/governance/staking/StakedTokenMTA.sol
Expand Up @@ -15,21 +15,19 @@ contract StakedTokenMTA is StakedToken {
using SafeERC20 for IERC20;

/**
* @param _signer Signer address is used to verify completion of quests off chain
* @param _nexus System nexus
* @param _rewardsToken Token that is being distributed as a reward. eg MTA
* @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
*/
constructor(
address _signer,
address _nexus,
address _rewardsToken,
address _stakedToken,
uint256 _cooldownSeconds,
uint256 _unstakeWindow
) StakedToken(_signer, _nexus, _rewardsToken, _stakedToken, _cooldownSeconds, _unstakeWindow) {}
) StakedToken(_nexus, _rewardsToken, _stakedToken, _cooldownSeconds, _unstakeWindow) {}

/**
* @dev Allows a staker to compound their rewards IF the Staking token and the Rewards token are the same
Expand Down
7 changes: 3 additions & 4 deletions contracts/governance/staking/deps/SignatureVerifier.sol
Expand Up @@ -25,7 +25,6 @@
pragma solidity ^0.8.0;

library SignatureVerifier {

function verify(
address signer,
address account,
Expand All @@ -38,16 +37,16 @@ library SignatureVerifier {
return recoverSigner(ethSignedMessageHash, signature) == signer;
}

function getMessageHash(address account, uint256 id) public pure returns (bytes32) {
function getMessageHash(address account, uint256 id) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(account, id));
}

function getEthSignedMessageHash(bytes32 messageHash) public pure returns (bytes32) {
function getEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
}

function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
public
internal
pure
returns (address)
{
Expand Down
3 changes: 1 addition & 2 deletions contracts/rewards/staking/PlatformTokenVendorFactory.sol
@@ -1,4 +1,3 @@

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

Expand All @@ -17,7 +16,7 @@ library PlatformTokenVendorFactory {
return true;
}

/**
/**
* @notice Deploys a new PlatformTokenVendor contract
* @param _rewardsToken reward or platform rewards token. eg MTA or WMATIC
* @return address of the deployed PlatformTokenVendor contract
Expand Down
4 changes: 2 additions & 2 deletions contracts/shared/ImmutableModule.sol
Expand Up @@ -79,8 +79,8 @@ abstract contract ImmutableModule is ModuleKeys {
}

/**
* @dev Return Recollateraliser Module address from the Nexus
* @return Address of the Recollateraliser Module contract (Phase 2)
* @dev Return Liquidator Module address from the Nexus
* @return Address of the Liquidator Module contract
*/
function _liquidator() internal view returns (address) {
return nexus.getModule(KEY_LIQUIDATOR);
Expand Down
39 changes: 39 additions & 0 deletions contracts/z_mocks/governance/stakedTokenWrapper.sol
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { StakedToken } from "../../governance/staking/StakedToken.sol";

/**
* Used to test contract interactions with the StakedToken
*/
contract StakedTokenWrapper {
using SafeERC20 for IERC20;

IERC20 public rewardsToken;
StakedToken public stakedToken;

constructor(address _rewardsToken, address _stakedToken) {
stakedToken = StakedToken(_stakedToken);
rewardsToken = IERC20(_rewardsToken);
rewardsToken.safeApprove(_stakedToken, 2**256 - 1);
}

function stake(uint256 _amount) external {
stakedToken.stake(_amount);
}

function stake(uint256 _amount, address _delegatee) external {
stakedToken.stake(_amount, _delegatee);
}

function withdraw(
uint256 _amount,
address _recipient,
bool _amountIncludesFee,
bool _exitCooldown
) external {
stakedToken.withdraw(_amount, _recipient, _amountIncludesFee, _exitCooldown);
}
}
4 changes: 4 additions & 0 deletions contracts/z_mocks/nexus/MockNexus.sol
Expand Up @@ -39,4 +39,8 @@ contract MockNexus is ModuleKeys {
function setLiquidator(address _liquidator) external {
modules[KEY_LIQUIDATOR] = _liquidator;
}

function setRecollateraliser(address _recollateraliser) external {
modules[KEY_RECOLLATERALISER] = _recollateraliser;
}
}
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -20,7 +20,7 @@
"scripts": {
"prettify": "",
"lint": "yarn run lint-ts; yarn run lint-sol",
"lint:fix": "yarn pretty-quick --pattern '**/*.*(sol|json)' --staged --verbose && yarn prettier --write tasks/**/*.ts test/**/*.ts types/*.ts",
"lint:fix": "yarn pretty-quick --pattern '**/*.*(sol|json)' --staged --verbose && yarn pretty-quick --staged --write tasks/**/*.ts test/**/*.ts types/*.ts",
"lint-ts": "yarn eslint ./test --ext .ts --fix --quiet",
"lint-sol": "solhint 'contracts/**/*.sol'",
"compile-abis": "typechain --target=ethers-v5 --out-dir types/generated \"?(contracts|build)/!(build-info)/**/+([a-zA-Z0-9]).json\"",
Expand Down

0 comments on commit 8ae4a85

Please sign in to comment.