Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/mstable/mStable-contracts
Browse files Browse the repository at this point in the history
…into emissions-test-staking
  • Loading branch information
doncesarts committed Dec 6, 2021
2 parents 74cfe2b + bb8b871 commit 9a63aa7
Show file tree
Hide file tree
Showing 19 changed files with 360 additions and 163 deletions.
17 changes: 3 additions & 14 deletions contracts/buy-and-make/RevenueBuyBack.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,11 @@ contract RevenueBuyBack is IRevenueRecipient, Initializable, ImmutableModule {
/// @notice Uniswap V3 Router address
IUniswapV3SwapRouter public immutable UNISWAP_ROUTER;

/// @notice Account that can execute `buyBackRewards`. eg Operations account.
address public keeper;
/// @notice Mapping of mAssets to RevenueBuyBack config
mapping(address => RevenueBuyBackConfig) public massetConfig;
/// @notice Emissions Controller dial ids for all staking contracts that will receive reward tokens.
uint256[] public stakingDialIds;

modifier keeperOrGovernor() {
require(msg.sender == keeper || msg.sender == _governor(), "Only keeper or governor");
_;
}

/**
* @param _nexus mStable system Nexus address
* @param _rewardsToken Rewards token address that are purchased. eg MTA
Expand All @@ -94,13 +87,9 @@ contract RevenueBuyBack is IRevenueRecipient, Initializable, ImmutableModule {
}

/**
* @param _keeper Account that can execute `buyBackRewards`. eg Operations account.
* @param _stakingDialIds Emissions Controller dial ids for all staking contracts that will receive reward tokens.
*/
function initialize(address _keeper, uint16[] memory _stakingDialIds) external initializer {
require(_keeper != address(0), "Keeper is zero");
keeper = _keeper;

function initialize(uint16[] memory _stakingDialIds) external initializer {
for (uint256 i = 0; i < _stakingDialIds.length; i++) {
_addStakingContract(_stakingDialIds[i]);
}
Expand Down Expand Up @@ -128,7 +117,7 @@ contract RevenueBuyBack is IRevenueRecipient, Initializable, ImmutableModule {
* @notice Buys reward tokens, eg MTA, using mAssets like mUSD or mBTC from protocol revenue.
* @param _mAssets Addresses of mAssets that are to be sold for rewards. eg mUSD and mBTC.
*/
function buyBackRewards(address[] calldata _mAssets) external keeperOrGovernor {
function buyBackRewards(address[] calldata _mAssets) external onlyKeeperOrGovernor {
uint256 len = _mAssets.length;
require(len > 0, "Invalid args");

Expand Down Expand Up @@ -170,7 +159,7 @@ contract RevenueBuyBack is IRevenueRecipient, Initializable, ImmutableModule {
/**
* @notice donates purchased rewards, eg MTA, to staking contracts via the Emissions Controller.
*/
function donateRewards() external keeperOrGovernor {
function donateRewards() external onlyKeeperOrGovernor {
// STEP 1 - Get the voting power of the staking contracts
uint256 numberStakingContracts = stakingDialIds.length;
uint256[] memory votingPower = new uint256[](numberStakingContracts);
Expand Down
11 changes: 1 addition & 10 deletions contracts/buy-and-make/RevenueForwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,20 @@ contract RevenueForwarder is IRevenueRecipient, ImmutableModule {

IERC20 public immutable mAsset;

address public immutable keeper;
address public forwarder;

constructor(
address _nexus,
address _mAsset,
address _keeper,
address _forwarder
) ImmutableModule(_nexus) {
require(_mAsset != address(0), "mAsset is zero");
require(_keeper != address(0), "Keeper is zero");
require(_forwarder != address(0), "Forwarder is zero");

mAsset = IERC20(_mAsset);
keeper = _keeper;
forwarder = _forwarder;
}

modifier keeperOrGovernor() {
require(msg.sender == keeper || msg.sender == _governor(), "Only keeper or governor");
_;
}

/**
* @dev Simply transfers the mAsset from the sender to here
* @param _mAsset Address of mAsset
Expand All @@ -61,7 +52,7 @@ contract RevenueForwarder is IRevenueRecipient, ImmutableModule {
/**
* @dev Withdraws to forwarder
*/
function forward() external keeperOrGovernor {
function forward() external onlyKeeperOrGovernor {
uint256 amt = mAsset.balanceOf(address(this));
if (amt == 0) {
return;
Expand Down
62 changes: 62 additions & 0 deletions contracts/emissions/DisperseForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IDisperse } from "../interfaces/IDisperse.sol";
import { ImmutableModule } from "../shared/ImmutableModule.sol";

/**
* @title DisperseForwarder
* @author mStable
* @notice Transfers reward tokens to a list of off-chain calculated recipients and amounts.
* @dev Disperse contract on Mainnet and Polygon: 0xD152f549545093347A162Dce210e7293f1452150
* @dev VERSION: 1.0
* DATE: 2021-11-03
*/
contract DisperseForwarder is ImmutableModule {
using SafeERC20 for IERC20;

/// @notice Rewards token that is to be dispersed.
IERC20 public immutable REWARDS_TOKEN;
/// @notice Token Disperser contract. eg 0xD152f549545093347A162Dce210e7293f1452150
IDisperse public immutable DISPERSE;

/**
* @param _rewardsToken Bridged rewards token on the Polygon chain.
* @param _disperse Token disperser contract.
*/
constructor(address _nexus, address _rewardsToken, address _disperse)
ImmutableModule(_nexus) {
require(_rewardsToken != address(0), "Invalid Rewards token");
require(_disperse != address(0), "Invalid Disperse contract");

REWARDS_TOKEN = IERC20(_rewardsToken);
DISPERSE = IDisperse(_disperse);

}

/**
* @notice Transfers reward tokens to a list of recipients with amounts.
* @param recipients Array of address that are to receive token rewards.
* @param values Array of reward token amounts for each recipient including decimal places.
*/
function disperseToken(address[] memory recipients, uint256[] memory values) external onlyKeeperOrGovernor {
uint256 len = recipients.length;
require(values.length == len, "array mismatch");

// Calculate the total amount of rewards that will be dispersed.
uint256 total = 0;
for (uint256 i = 0; i < len; i++) {
total += values[i];
}

uint256 rewardBal = REWARDS_TOKEN.balanceOf(address(this));
require(rewardBal >= total, "Insufficient rewards");

// Approve only the amount to be dispersed. Any more and the funds in this contract can be stolen
// using the disperseTokenSimple function on the Disperse contract.
REWARDS_TOKEN.safeApprove(address(DISPERSE), total);

DISPERSE.disperseTokenSimple(REWARDS_TOKEN, recipients, values);
}
}
4 changes: 4 additions & 0 deletions contracts/emissions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ It’s possible the MTA rewards can be calculated but not transferred. Each new

![Polygon Bridge FRAX](../../docs/emissions/polygonBridge_frax.png)

### Distribution to Balancer Pool liquidity providers on Polygon

![Polygon Bridge Balancer](../../docs/emissions/polygonBridge_balancer.png)

### MTA Buy Back for Stakers

![Buy Back For Stakers](../../docs/emissions/buyBackForStakers.png)
Expand Down
64 changes: 64 additions & 0 deletions contracts/emissions/VotiumBribeForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IVotiumBribe } from "../interfaces/IVotiumBribe.sol";
import { ImmutableModule } from "../shared/ImmutableModule.sol";

/**
* @title VotiumBribeForwarder
* @author mStable
* @notice Uses reward tokens to bribe vlCVX holders to vote for a Curve gauge using Votium.
* @dev VotiumBribe contract on Mainnet, Polygon, Fantom: 0x19bbc3463dd8d07f55438014b021fb457ebd4595
* @dev VERSION: 1.0
* DATE: 2021-11-03
*/
contract VotiumBribeForwarder is ImmutableModule {
using SafeERC20 for IERC20;

/// @notice Rewards token that is to be deposit.
// solhint-disable-next-line var-name-mixedcase
IERC20 public immutable REWARDS_TOKEN;
/// @notice Token VotiumBribe contract. eg 0x19bbc3463dd8d07f55438014b021fb457ebd4595
// solhint-disable-next-line var-name-mixedcase
IVotiumBribe public immutable VOTIUM_BRIBE;
/// @notice Votium brive deposit choice index.
uint256 public choiceIndex;

/**
* @param _rewardsToken Bridged rewards token on the Polygon chain.
* @param _votiumBribe Token votium bribe contract.
*/
constructor(address _nexus, address _rewardsToken, address _votiumBribe)
ImmutableModule(_nexus) {
require(_rewardsToken != address(0), "Invalid Rewards token");
require(_votiumBribe != address(0), "Invalid VotiumBribe contract");
REWARDS_TOKEN = IERC20(_rewardsToken);
VOTIUM_BRIBE = IVotiumBribe(_votiumBribe);
}


/**
* @notice Deposits a bribe into Votium, choiceIndex must be set previously.
* @param amount amount of reward token to deposit including decimal places.
* @param proposal votium brive proposal
*/
function depositBribe(uint256 amount, bytes32 proposal) external onlyKeeperOrGovernor {
require(amount != 0, "Invalid amount");

uint256 rewardBal = REWARDS_TOKEN.balanceOf(address(this));
require(rewardBal >= amount, "Insufficient rewards");
// Approve only the amount to be bribe. Any more and the funds in this contract can be stolen
// using the depositBribe function on the VotiumBribe contract.
REWARDS_TOKEN.safeApprove(address(VOTIUM_BRIBE), amount);
VOTIUM_BRIBE.depositBribe(address(REWARDS_TOKEN), amount, proposal, choiceIndex);
}

/**
* @notice Updates the choice index used for the bribe.
* @param _choiceIndex the bribe choice index
*/
function updateChoiceIndex(uint256 _choiceIndex) public onlyKeeperOrGovernor {
choiceIndex = _choiceIndex;
}
}
8 changes: 8 additions & 0 deletions contracts/interfaces/IDisperse.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

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

interface IDisperse {
function disperseTokenSimple(IERC20 token, address[] memory recipients, uint256[] memory values) external;
}
8 changes: 8 additions & 0 deletions contracts/interfaces/IVotiumBribe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

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

interface IVotiumBribe {
function depositBribe(address _token, uint256 _amount, bytes32 _proposal, uint256 _choiceIndex) external ;
}
22 changes: 22 additions & 0 deletions contracts/shared/ImmutableModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ abstract contract ImmutableModule is ModuleKeys {
require(msg.sender == _governor(), "Only governor can execute");
}

/**
* @dev Modifier to allow function calls only from the Governor or the Keeper EOA.
*/
modifier onlyKeeperOrGovernor() {
_keeperOrGovernor();
_;
}

function _keeperOrGovernor() internal view {
require(msg.sender == _keeper() || msg.sender == _governor(), "Only keeper or governor");
}

/**
* @dev Modifier to allow function calls only from the Governance.
* Governance is either Governor address or Governance address.
Expand Down Expand Up @@ -62,6 +74,16 @@ abstract contract ImmutableModule is ModuleKeys {
return nexus.getModule(KEY_GOVERNANCE);
}

/**
* @dev Return Keeper address from the Nexus.
* This account is used for operational transactions that
* don't need multiple signatures.
* @return Address of the Keeper externally owned account.
*/
function _keeper() internal view returns (address) {
return nexus.getModule(KEY_KEEPER);
}

/**
* @dev Return SavingsManager Module address from the Nexus
* @return Address of the SavingsManager Module contract
Expand Down
3 changes: 3 additions & 0 deletions contracts/shared/ModuleKeys.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ contract ModuleKeys {
// keccak256("InterestValidator");
bytes32 internal constant KEY_INTEREST_VALIDATOR =
0xc10a28f028c7f7282a03c90608e38a4a646e136e614e4b07d119280c5f7f839f;
// keccak256("Keeper");
bytes32 internal constant KEY_KEEPER =
0x4f78afe9dfc9a0cb0441c27b9405070cd2a48b490636a7bdd09f355e33a5d7de;
}
4 changes: 4 additions & 0 deletions contracts/z_mocks/nexus/MockNexus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@ contract MockNexus is ModuleKeys {
function setRecollateraliser(address _recollateraliser) external {
modules[KEY_RECOLLATERALISER] = _recollateraliser;
}

function setKeeper(address _keeper) external {
modules[KEY_KEEPER] = _keeper;
}
}
Binary file added docs/emissions/polygonBridge_balancer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 20 additions & 10 deletions tasks/deployEmissionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { getSigner } from "./utils/signerFactory"
import {
deployBasicForwarder,
deployBridgeForwarder,
deployDisperseForwarder,
deployVotiumBribeForwarder,
deployEmissionsController,
deployL2BridgeRecipients,
deployL2EmissionsController,
Expand All @@ -22,10 +24,17 @@ task("deploy-emissions-polly", "Deploys L2EmissionsController and L2 Bridge Reci
const signer = await getSigner(hre, taskArgs.speed)

const l2EmissionsController = await deployL2EmissionsController(signer, hre)
console.log(`Set L2EmissionsController contract name in networkAddressFactory to ${l2EmissionsController.address}`)

await deployL2BridgeRecipients(signer, hre, l2EmissionsController.address)
const bridgeRecipient = await deployL2BridgeRecipients(signer, hre, l2EmissionsController.address)
console.log(`Set PmUSD bridgeRecipient to ${bridgeRecipient.address}`)

const disperseForwarder = await deployDisperseForwarder(signer, hre)
console.log(`Set PBAL bridgeRecipient to ${disperseForwarder.address}`)

const votiumBribeForwarder = await deployVotiumBribeForwarder(signer, hre)
console.log(`Set ? bridgeRecipient to ${votiumBribeForwarder.address}`)

console.log(`Set L2EmissionsController contract name in networkAddressFactory to ${l2EmissionsController.address}`)
})

task("deploy-emissions")
Expand All @@ -37,20 +46,21 @@ task("deploy-emissions")
console.log(`Set RewardsDistributor in the networkAddressFactory to ${emissionsController.address}`)
})

task("deploy-bridge-forwarders", "Deploys Polygon mUSD Vault and FRAX BridgeForwarders on mainnet")
task("deploy-bridge-forwarder", "Deploys a BridgeForwarder contract on mainnet for Polygon dials.")
.addParam(
"token",
"Token on the Polygon network that is configured with `bridgeRecipient`. eg mUSD, FRAX, BAL.",
undefined,
types.string,
)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const chain = getChain(hre)
const signer = await getSigner(hre, taskArgs.speed)

const l2Chain = chain === Chain.mainnet ? Chain.polygon : Chain.mumbai
const mUSDBridgeRecipientAddress = resolveAddress("mUSD", l2Chain, "bridgeRecipient")
await deployBridgeForwarder(signer, hre, mUSDBridgeRecipientAddress)

if (chain === Chain.mainnet) {
const fraxBridgeRecipientAddress = resolveAddress("FRAX", l2Chain, "bridgeRecipient")
await deployBridgeForwarder(signer, hre, fraxBridgeRecipientAddress)
}
const bridgeRecipientAddress = resolveAddress(taskArgs.token, l2Chain, "bridgeRecipient")
await deployBridgeForwarder(signer, hre, bridgeRecipientAddress)
})

task("deploy-basic-forwarder", "Deploys a basic rewards forwarder from the emissions controller.")
Expand Down
Loading

0 comments on commit 9a63aa7

Please sign in to comment.