diff --git a/contracts/buy-and-make/RevenueBuyBack.sol b/contracts/buy-and-make/RevenueBuyBack.sol index 0427ecd2..5cffcd2c 100644 --- a/contracts/buy-and-make/RevenueBuyBack.sol +++ b/contracts/buy-and-make/RevenueBuyBack.sol @@ -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 @@ -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]); } @@ -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"); @@ -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); diff --git a/contracts/buy-and-make/RevenueForwarder.sol b/contracts/buy-and-make/RevenueForwarder.sol index 29f1285c..d28d39a4 100644 --- a/contracts/buy-and-make/RevenueForwarder.sol +++ b/contracts/buy-and-make/RevenueForwarder.sol @@ -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 @@ -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; diff --git a/contracts/emissions/DisperseForwarder.sol b/contracts/emissions/DisperseForwarder.sol new file mode 100644 index 00000000..01bf2c4c --- /dev/null +++ b/contracts/emissions/DisperseForwarder.sol @@ -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); + } +} diff --git a/contracts/emissions/README.md b/contracts/emissions/README.md index e0f6bb18..9add6eb1 100644 --- a/contracts/emissions/README.md +++ b/contracts/emissions/README.md @@ -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) diff --git a/contracts/emissions/VotiumBribeForwarder.sol b/contracts/emissions/VotiumBribeForwarder.sol new file mode 100644 index 00000000..702ff1ad --- /dev/null +++ b/contracts/emissions/VotiumBribeForwarder.sol @@ -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; + } +} diff --git a/contracts/interfaces/IDisperse.sol b/contracts/interfaces/IDisperse.sol new file mode 100644 index 00000000..5e6b8fc2 --- /dev/null +++ b/contracts/interfaces/IDisperse.sol @@ -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; +} diff --git a/contracts/interfaces/IVotiumBribe.sol b/contracts/interfaces/IVotiumBribe.sol new file mode 100644 index 00000000..1dcf9dee --- /dev/null +++ b/contracts/interfaces/IVotiumBribe.sol @@ -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 ; +} diff --git a/contracts/shared/ImmutableModule.sol b/contracts/shared/ImmutableModule.sol index 1b408aca..8c3ab790 100644 --- a/contracts/shared/ImmutableModule.sol +++ b/contracts/shared/ImmutableModule.sol @@ -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. @@ -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 diff --git a/contracts/shared/ModuleKeys.sol b/contracts/shared/ModuleKeys.sol index cfd7dd84..ff28b798 100644 --- a/contracts/shared/ModuleKeys.sol +++ b/contracts/shared/ModuleKeys.sol @@ -45,4 +45,7 @@ contract ModuleKeys { // keccak256("InterestValidator"); bytes32 internal constant KEY_INTEREST_VALIDATOR = 0xc10a28f028c7f7282a03c90608e38a4a646e136e614e4b07d119280c5f7f839f; + // keccak256("Keeper"); + bytes32 internal constant KEY_KEEPER = + 0x4f78afe9dfc9a0cb0441c27b9405070cd2a48b490636a7bdd09f355e33a5d7de; } diff --git a/contracts/z_mocks/nexus/MockNexus.sol b/contracts/z_mocks/nexus/MockNexus.sol index 54182e9f..a53a7df6 100644 --- a/contracts/z_mocks/nexus/MockNexus.sol +++ b/contracts/z_mocks/nexus/MockNexus.sol @@ -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; + } } diff --git a/docs/emissions/polygonBridge_balancer.png b/docs/emissions/polygonBridge_balancer.png new file mode 100644 index 00000000..45f4122b Binary files /dev/null and b/docs/emissions/polygonBridge_balancer.png differ diff --git a/tasks/deployEmissionsController.ts b/tasks/deployEmissionsController.ts index bb4d95ca..0574430c 100644 --- a/tasks/deployEmissionsController.ts +++ b/tasks/deployEmissionsController.ts @@ -7,6 +7,8 @@ import { getSigner } from "./utils/signerFactory" import { deployBasicForwarder, deployBridgeForwarder, + deployDisperseForwarder, + deployVotiumBribeForwarder, deployEmissionsController, deployL2BridgeRecipients, deployL2EmissionsController, @@ -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") @@ -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.") diff --git a/tasks/utils/emissions-utils.ts b/tasks/utils/emissions-utils.ts index c75f83a9..ae7235ae 100644 --- a/tasks/utils/emissions-utils.ts +++ b/tasks/utils/emissions-utils.ts @@ -6,6 +6,10 @@ import { BasicRewardsForwarder__factory, BridgeForwarder, BridgeForwarder__factory, + DisperseForwarder, + DisperseForwarder__factory, + VotiumBribeForwarder, + VotiumBribeForwarder__factory, EmissionsController, EmissionsController__factory, L2BridgeRecipient, @@ -167,35 +171,59 @@ export const deployL2BridgeRecipients = async ( signer: Signer, hre: HardhatRuntimeEnvironment, l2EmissionsControllerAddress: string, -): Promise => { +): Promise => { const mtaAddress = MTA.address const constructorArguments = [mtaAddress, l2EmissionsControllerAddress] - const mUSDBridgeRecipient = await deployContract( - new L2BridgeRecipient__factory(signer), - "mUSD Vault Bridge Recipient", - [mtaAddress, l2EmissionsControllerAddress], - ) - console.log(`Set PmUSD bridgeRecipient to ${mUSDBridgeRecipient.address}`) + const bridgeRecipient = await deployContract(new L2BridgeRecipient__factory(signer), "L2BridgeRecipient", [ + mtaAddress, + l2EmissionsControllerAddress, + ]) + await verifyEtherscan(hre, { - address: mUSDBridgeRecipient.address, + address: bridgeRecipient.address, constructorArguments, contract: "contracts/emissions/L2BridgeRecipient.sol:L2BridgeRecipient", }) - const fraxBridgeRecipient = await deployContract( - new L2BridgeRecipient__factory(signer), - "FRAX Farm Bridge Recipient", - [mtaAddress, l2EmissionsControllerAddress], - ) - console.log(`Set PFRAX bridgeRecipient to ${fraxBridgeRecipient.address}`) + return bridgeRecipient +} + +export const deployDisperseForwarder = async (signer: Signer, hre: HardhatRuntimeEnvironment): Promise => { + const chain = getChain(hre) + const nexusAddress = resolveAddress("Nexus", chain) + const disperseAddress = resolveAddress("Disperse", chain) + const mtaAddress = MTA.address + const constructorArguments = [nexusAddress, disperseAddress, mtaAddress] + + const disperseForwarder = await deployContract(new DisperseForwarder__factory(signer), "DisperseForwarder", constructorArguments) + await verifyEtherscan(hre, { - address: fraxBridgeRecipient.address, + address: disperseForwarder.address, constructorArguments, - contract: "contracts/emissions/L2BridgeRecipient.sol:L2BridgeRecipient", + contract: "contracts/emissions/DisperseForwarder.sol:DisperseForwarder", + }) + + return disperseForwarder +} + +export const deployVotiumBribeForwarder = async (signer: Signer, hre: HardhatRuntimeEnvironment): Promise => { + + const chain = getChain(hre) + const nexusAddress = resolveAddress("Nexus", chain) + const votiumBribeAddress = resolveAddress("VotiumBribe", chain) + const mtaAddress = MTA.address + const constructorArguments = [nexusAddress, mtaAddress, votiumBribeAddress] + + const votiumBribeForwarder = await deployContract(new VotiumBribeForwarder__factory(signer), "VotiumBribeForwarder", constructorArguments) + + await verifyEtherscan(hre, { + address: votiumBribeForwarder.address, + constructorArguments, + contract: "contracts/emissions/VotiumBribeForwarder.sol:VotiumBribeForwarder", }) - return [mUSDBridgeRecipient, fraxBridgeRecipient] + return votiumBribeForwarder } export const deployBridgeForwarder = async ( @@ -214,16 +242,16 @@ export const deployBridgeForwarder = async ( const emissionsControllerAddress = _emissionsControllerAddress || resolveAddress("EmissionsController", chain) const constructorArguments = [nexusAddress, mtaAddress, tokenBridgeAddress, rootChainManagerAddress, bridgeRecipientAddress] - const bridgeForrwarderImpl = await deployContract( + const bridgeForwarderImpl = await deployContract( new BridgeForwarder__factory(signer), "mUSD Vault Bridge Forwarder", constructorArguments, ) // Deploy proxy and initialize - const initializeData = bridgeForrwarderImpl.interface.encodeFunctionData("initialize", [emissionsControllerAddress]) + const initializeData = bridgeForwarderImpl.interface.encodeFunctionData("initialize", [emissionsControllerAddress]) const proxy = await deployContract(new AssetProxy__factory(signer), "AssetProxy", [ - bridgeForrwarderImpl.address, + bridgeForwarderImpl.address, proxyAdminAddress, initializeData, ]) @@ -237,7 +265,7 @@ export const deployBridgeForwarder = async ( await sleep(10000) await verifyEtherscan(hre, { - address: bridgeForrwarderImpl.address, + address: bridgeForwarderImpl.address, constructorArguments, contract: "contracts/emissions/BridgeForwarder.sol:BridgeForwarder", }) @@ -256,7 +284,6 @@ export const deployRevenueBuyBack = async ( const mtaAddress = MTA.address const uniswapRouterAddress = resolveAddress("UniswapRouterV3", chain) const emissionsControllerAddress = _emissionsControllerAddress || resolveAddress("EmissionsController", chain) - const devOpsAddress = resolveAddress("OperationsSigner", chain) // Deploy RevenueBuyBack const constructorArguments: [string, string, string, string] = [ @@ -266,7 +293,7 @@ export const deployRevenueBuyBack = async ( emissionsControllerAddress, ] const revenueBuyBack = await new RevenueBuyBack__factory(signer).deploy(...constructorArguments) - await revenueBuyBack.initialize(devOpsAddress, [0, 1]) + await revenueBuyBack.initialize([0, 1]) await verifyEtherscan(hre, { address: revenueBuyBack.address, diff --git a/tasks/utils/tokens.ts b/tasks/utils/tokens.ts index b9400322..49f44fee 100644 --- a/tasks/utils/tokens.ts +++ b/tasks/utils/tokens.ts @@ -472,6 +472,17 @@ export const BAL: Token = { quantityFormatter: "USD", } +export const PBAL: Token = { + symbol: "BAL", + address: "0x9a71012B13CA4d3D0Cdc72A177DF3ef03b0E76A3", + chain: Chain.polygon, + decimals: 18, + quantityFormatter: "USD", + bridgeForwarder: DEAD_ADDRESS, + // The DisperseForwarder contract on Polygon + bridgeRecipient: DEAD_ADDRESS, +} + export const RBAL: Token = { symbol: "BAL", address: "0x0Aa94D9Db9dA74Bb86A437E28EE4ecf22365843E", @@ -538,5 +549,6 @@ export const tokens = [ mBPT, RmBPT, BAL, + PBAL, RBAL, ] diff --git a/test-fork/emissions/emissions-mainnet-deployment.spec.ts b/test-fork/emissions/emissions-mainnet-deployment.spec.ts index 87bfd28e..9082f5dc 100644 --- a/test-fork/emissions/emissions-mainnet-deployment.spec.ts +++ b/test-fork/emissions/emissions-mainnet-deployment.spec.ts @@ -7,8 +7,8 @@ import { resolveAddress } from "tasks/utils/networkAddressFactory" import { deployBasicForwarder, deployBridgeForwarder, deployEmissionsController, deployRevenueBuyBack } from "tasks/utils/emissions-utils" import { expect } from "chai" import { BN, simpleToExactAmount } from "@utils/math" -import { currentWeekEpoch, increaseTime, increaseTimeTo } from "@utils/time" -import { MAX_UINT256, ONE_WEEK } from "@utils/constants" +import { currentWeekEpoch, increaseTime } from "@utils/time" +import { MAX_UINT256, ONE_HOUR, ONE_WEEK } from "@utils/constants" import { assertBNClose } from "@utils/assertions" import { alUSD, BUSD, Chain, DAI, FEI, GUSD, HBTC, mBTC, MTA, mUSD, RAI, TBTCv2, USDC, WBTC } from "tasks/utils/tokens" import { @@ -18,6 +18,8 @@ import { IERC20__factory, InitializableRewardsDistributionRecipient__factory, IUniswapV3Quoter__factory, + Nexus, + Nexus__factory, RevenueBuyBack, SavingsManager, SavingsManager__factory, @@ -25,11 +27,15 @@ import { import { Account } from "types/common" import { encodeUniswapPath } from "@utils/peripheral/uniswap" import { btcFormatter, usdFormatter } from "tasks/utils/quantity-formatters" +import { keccak256 } from "@ethersproject/keccak256" +import { toUtf8Bytes } from "ethers/lib/utils" const voter1VotingPower = BN.from("44461750008245826445414") const voter2VotingPower = simpleToExactAmount(27527.5) const voter3VotingPower = BN.from("78211723319712171214037") +const keeperKey = keccak256(toUtf8Bytes("Keeper")) + context("Fork test Emissions Controller on mainnet", () => { let ops: Signer let governor: Signer @@ -38,6 +44,7 @@ context("Fork test Emissions Controller on mainnet", () => { let voter3: Account let treasury: Account let emissionsController: EmissionsController + let nexus: Nexus let mta: IERC20 const setRewardsDistribution = async (recipientAddress: string) => { @@ -45,7 +52,7 @@ context("Fork test Emissions Controller on mainnet", () => { await recipient.setRewardsDistribution(emissionsController.address) } - before("reset block number", async () => { + const setup = async () => { await network.provider.request({ method: "hardhat_reset", params: [ @@ -66,10 +73,13 @@ context("Fork test Emissions Controller on mainnet", () => { voter3 = await impersonateAccount("0x530deFD6c816809F54F6CfA6FE873646F6EcF930") // 82,538.415914215331337512 stkBPT treasury = await impersonateAccount("0x3dd46846eed8d147841ae162c8425c08bd8e1b41") + nexus = Nexus__factory.connect(resolveAddress("Nexus"), governor) mta = IERC20__factory.connect(MTA.address, treasury.signer) - }) + } + describe("Deploy contracts", () => { it("Emissions Controller", async () => { + await setup() emissionsController = await deployEmissionsController(ops, hre) expect(await emissionsController.getDialRecipient(0), "dial 0 Staked MTA").to.eq("0x8f2326316eC696F6d023E37A9931c2b2C177a3D7") @@ -145,10 +155,9 @@ context("Fork test Emissions Controller on mainnet", () => { }) }) describe("Set vote weights", () => { - let firstEpoch: BN before(async () => { + await setup() emissionsController = await deployEmissionsController(ops, hre) - firstEpoch = await (await currentWeekEpoch()).add(1) }) it("voter 1", async () => { expect(await emissionsController.callStatic.getVotes(voter1.address), "voter 1 total voting power").to.eq(voter1VotingPower) @@ -205,9 +214,13 @@ context("Fork test Emissions Controller on mainnet", () => { }) describe("calculate rewards", () => { before(async () => { + await setup() + await nexus.proposeModule(keeperKey, resolveAddress("OperationsSigner")) + // increase time to 2 December 2021, Thursday 08:00 UTC - await increaseTimeTo(1638439200) + // await increaseTimeTo(1638439200) emissionsController = await deployEmissionsController(ops, hre) + const visorFinanceDial = await deployBasicForwarder(ops, emissionsController.address, "VisorRouter", hre) await emissionsController.connect(governor).addDial(visorFinanceDial.address, 0, true) @@ -220,12 +233,8 @@ context("Fork test Emissions Controller on mainnet", () => { const fraxBridgeForwarder = await deployBridgeForwarder(ops, hre, fraxBridgeRecipientAddress, emissionsController.address) await emissionsController.connect(governor).addDial(fraxBridgeForwarder.address, 0, true) // Polygon Balancer Pool - const balancerBridgeForwarder = await deployBridgeForwarder( - ops, - hre, - "0x2f2Db75C5276481E2B018Ac03e968af7763Ed118", - emissionsController.address, - ) + const balBridgeRecipientAddress = resolveAddress("BAL", Chain.polygon, "bridgeRecipient") + const balancerBridgeForwarder = await deployBridgeForwarder(ops, hre, balBridgeRecipientAddress, emissionsController.address) await emissionsController.connect(governor).addDial(balancerBridgeForwarder.address, 0, true) // Treasury DAO dial @@ -312,13 +321,17 @@ context("Fork test Emissions Controller on mainnet", () => { weight: 10, // 5% }, ]) + + await increaseTime(ONE_WEEK.add(ONE_HOUR)) + await nexus.acceptProposedModule(keeperKey) }) it("immediately", async () => { const tx = emissionsController.calculateRewards() await expect(tx).to.revertedWith("Must wait for new period") }) it("after 2 weeks", async () => { - await increaseTime(ONE_WEEK.mul(2)) + await increaseTime(ONE_WEEK) + const currentEpochIndex = await currentWeekEpoch() const totalRewardsExpected = await emissionsController.topLineEmission(currentEpochIndex) expect(totalRewardsExpected, "First distribution rewards").to.gt(simpleToExactAmount(165000)).lt(simpleToExactAmount(166000)) @@ -353,7 +366,11 @@ context("Fork test Emissions Controller on mainnet", () => { const bridgeAmount = simpleToExactAmount(10000) before(async () => { + await setup() emissionsController = await deployEmissionsController(ops, hre) + + await nexus.proposeModule(keeperKey, resolveAddress("OperationsSigner")) + const visorFinanceDial = await deployBasicForwarder(ops, emissionsController.address, "VisorRouter", hre) await emissionsController.connect(governor).addDial(visorFinanceDial.address, 0, true) const bridgeRecipient = Wallet.createRandom() @@ -384,6 +401,9 @@ context("Fork test Emissions Controller on mainnet", () => { await setRewardsDistribution(RAI.vault) await setRewardsDistribution(HBTC.vault) await setRewardsDistribution(TBTCv2.vault) + + await increaseTime(ONE_WEEK.add(ONE_HOUR)) + await nexus.acceptProposedModule(keeperKey) }) it("distribute rewards to staking contracts", async () => { const tx = await emissionsController.distributeRewards([0, 1]) @@ -446,9 +466,15 @@ context("Fork test Emissions Controller on mainnet", () => { // 0.04147372e8 WBTC / 1,853e18 MTA = 44685e10 * 1e18 = 4.46e14 * 1e18 = 4.46e32 = 446e30 before(async () => { + await setup() + await nexus.proposeModule(keeperKey, resolveAddress("OperationsSigner")) + emissionsController = await deployEmissionsController(ops, hre) mtaToken = IERC20__factory.connect(MTA.address, ops) revenueBuyBack = await deployRevenueBuyBack(ops, hre, emissionsController.address) + + await increaseTime(ONE_WEEK.add(ONE_HOUR)) + await nexus.acceptProposedModule(keeperKey) }) it("check Uniswap USDC to MTA price", async () => { const uniswapQuoterAddress = resolveAddress("UniswapQuoterV3") diff --git a/test-utils/machines/standardAccounts.ts b/test-utils/machines/standardAccounts.ts index 5f392091..abc8871e 100644 --- a/test-utils/machines/standardAccounts.ts +++ b/test-utils/machines/standardAccounts.ts @@ -48,6 +48,8 @@ export class StandardAccounts { public mockRewardsDistributor: Account + public keeper: Account + public async initAccounts(signers: Signer[]): Promise { this.all = await Promise.all( signers.map(async (s) => ({ @@ -75,6 +77,7 @@ export class StandardAccounts { this.mockRecollateraliser, this.mockMasset, this.mockRewardsDistributor, + this.keeper, ] = this.all return this } diff --git a/test/buy-and-make/revenue-buy-back.spec.ts b/test/buy-and-make/revenue-buy-back.spec.ts index 2a875ac2..ee5a9b68 100644 --- a/test/buy-and-make/revenue-buy-back.spec.ts +++ b/test/buy-and-make/revenue-buy-back.spec.ts @@ -70,6 +70,7 @@ describe("RevenueBuyBack", () => { sa.mockSavingsManager.address, sa.mockInterestValidator.address, ) + await nexus.setKeeper(sa.keeper.address) // Mocked Uniswap V3 uniswap = await new MockUniswapV3__factory(sa.default.signer).deploy() @@ -111,7 +112,7 @@ describe("RevenueBuyBack", () => { emissionController.address, ) // reverse the order to make sure dial id != staking contract id for testing purposes - await revenueBuyBack.initialize(sa.fundManager.address, [1, 0]) + await revenueBuyBack.initialize([1, 0]) // Add config to buy rewards from mAssets await revenueBuyBack @@ -152,7 +153,6 @@ describe("RevenueBuyBack", () => { expect(await revenueBuyBack.EMISSIONS_CONTROLLER(), "Emissions Controller").eq(emissionController.address) }) it("should have storage variables set", async () => { - expect(await revenueBuyBack.keeper(), "Keeper").eq(sa.fundManager.address) expect(await revenueBuyBack.stakingDialIds(0), "Staking Contract 1 dial id").eq(1) expect(await revenueBuyBack.stakingDialIds(1), "Staking Contract 2 dial id").eq(0) expect((await emissionController.dials(0)).recipient, "first dial is first staking contract").to.eq(staking1.address) @@ -195,16 +195,6 @@ describe("RevenueBuyBack", () => { ) await expect(tx).to.revertedWith("Emissions controller is zero") }) - it("Keeper", async () => { - const revBuyBack = await new RevenueBuyBack__factory(sa.default.signer).deploy( - nexus.address, - rewardsToken.address, - uniswap.address, - emissionController.address, - ) - const tx = revBuyBack.initialize(ZERO_ADDRESS, []) - await expect(tx).to.revertedWith("Keeper is zero") - }) }) }) describe("notification of revenue", () => { @@ -269,7 +259,7 @@ describe("RevenueBuyBack", () => { expect(await mUSD.balanceOf(revenueBuyBack.address), "revenueBuyBack's mUSD Bal before").to.eq(musdRevenue) expect(await bAsset1.balanceOf(mUSD.address), "mAsset's bAsset Bal before").to.eq(musdRevenue) - const tx = revenueBuyBack.connect(sa.fundManager.signer).buyBackRewards([mUSD.address]) + const tx = revenueBuyBack.connect(sa.keeper.signer).buyBackRewards([mUSD.address]) const bAsset1Amount = musdRevenue.mul(98).div(100) // Exchange rate = 0.80 MTA/USD = 8 / 18 @@ -283,7 +273,7 @@ describe("RevenueBuyBack", () => { expect(await mBTC.balanceOf(revenueBuyBack.address), "revenueBuyBack's mBTC Bal before").to.eq(mbtcRevenue) expect(await bAsset2.balanceOf(mBTC.address), "mAsset's bAsset Bal before").to.eq(mbtcRevenue.div(1e12)) - const tx = revenueBuyBack.connect(sa.fundManager.signer).buyBackRewards([mBTC.address]) + const tx = revenueBuyBack.connect(sa.keeper.signer).buyBackRewards([mBTC.address]) const bAsset2Amount = mbtcRevenue.mul(98).div(100).div(1e12) // Exchange rate = 50,000 MTA/BTC @@ -294,7 +284,7 @@ describe("RevenueBuyBack", () => { expect(await mBTC.balanceOf(revenueBuyBack.address), "revenueBuyBack's mBTC Bal after").to.eq(0) }) it("should sell mUSD and mBTC for MTA", async () => { - const tx = revenueBuyBack.connect(sa.fundManager.signer).buyBackRewards([mUSD.address, mBTC.address]) + const tx = revenueBuyBack.connect(sa.keeper.signer).buyBackRewards([mUSD.address, mBTC.address]) // const bAsset1Amount = musdRevenue.mul(98).div(100) @@ -314,11 +304,11 @@ describe("RevenueBuyBack", () => { }) describe("should fail when", () => { it("No mAssets", async () => { - const tx = revenueBuyBack.connect(sa.fundManager.signer).buyBackRewards([]) + const tx = revenueBuyBack.connect(sa.keeper.signer).buyBackRewards([]) await expect(tx).to.revertedWith("Invalid args") }) it("Not a mAsset", async () => { - const tx = revenueBuyBack.connect(sa.fundManager.signer).buyBackRewards([rewardsToken.address]) + const tx = revenueBuyBack.connect(sa.keeper.signer).buyBackRewards([rewardsToken.address]) await expect(tx).to.revertedWith("Invalid mAsset") }) it("Not keeper or governor", async () => { @@ -338,7 +328,7 @@ describe("RevenueBuyBack", () => { expect(await rewardsToken.balanceOf(revenueBuyBack.address), "revenue buy back rewards before").to.eq(totalRewards) const rewardsECbefore = await rewardsToken.balanceOf(emissionController.address) - const tx = revenueBuyBack.connect(sa.fundManager.signer).donateRewards() + const tx = revenueBuyBack.connect(sa.keeper.signer).donateRewards() await expect(tx).to.emit(revenueBuyBack, "DonatedRewards").withArgs(totalRewards) await expect(tx).to.emit(emissionController, "DonatedRewards").withArgs(1, totalRewards.div(4)) @@ -354,13 +344,13 @@ describe("RevenueBuyBack", () => { await staking1.setTotalSupply(0) await staking2.setTotalSupply(0) - const tx = revenueBuyBack.connect(sa.fundManager.signer).donateRewards() + const tx = revenueBuyBack.connect(sa.keeper.signer).donateRewards() await expect(tx).to.revertedWith("No voting power") }) it("no rewards to donate", async () => { expect(await rewardsToken.balanceOf(revenueBuyBack.address), "revenue buy back rewards before").to.eq(0) - const tx = revenueBuyBack.connect(sa.fundManager.signer).donateRewards() + const tx = revenueBuyBack.connect(sa.keeper.signer).donateRewards() await expect(tx).to.revertedWith("No rewards to donate") }) }) diff --git a/test/buy-and-make/revenue-forwarder.spec.ts b/test/buy-and-make/revenue-forwarder.spec.ts index 2c00a78a..d8297db0 100644 --- a/test/buy-and-make/revenue-forwarder.spec.ts +++ b/test/buy-and-make/revenue-forwarder.spec.ts @@ -14,7 +14,6 @@ import { } from "types/generated" import { ZERO_ADDRESS } from "@utils/constants" import { Wallet } from "@ethersproject/wallet" -import { Account } from "types/common" describe("RevenueForwarder", () => { let sa: StandardAccounts @@ -22,7 +21,6 @@ describe("RevenueForwarder", () => { let nexus: MockNexus let revenueForwarder: RevenueForwarder let mAsset: MockMasset - let keeper: Account let forwarderAddress: string /* @@ -44,16 +42,11 @@ describe("RevenueForwarder", () => { sa.mockSavingsManager.address, sa.mockInterestValidator.address, ) - keeper = sa.fundManager + await nexus.setKeeper(sa.keeper.address) forwarderAddress = Wallet.createRandom().address // Deploy aRevenueForwarder - revenueForwarder = await new RevenueForwarder__factory(sa.default.signer).deploy( - nexus.address, - mAsset.address, - keeper.address, - forwarderAddress, - ) + revenueForwarder = await new RevenueForwarder__factory(sa.default.signer).deploy(nexus.address, mAsset.address, forwarderAddress) } before(async () => { @@ -68,44 +61,19 @@ describe("RevenueForwarder", () => { it("should have immutable variables set", async () => { expect(await revenueForwarder.nexus(), "Nexus").eq(nexus.address) expect(await revenueForwarder.mAsset(), "mAsset").eq(mAsset.address) - expect(await revenueForwarder.keeper(), "Keeper").eq(keeper.address) expect(await revenueForwarder.forwarder(), "Forwarder").eq(forwarderAddress) }) describe("it should fail if zero", () => { it("nexus", async () => { - const tx = new RevenueForwarder__factory(sa.default.signer).deploy( - ZERO_ADDRESS, - mAsset.address, - keeper.address, - forwarderAddress, - ) + const tx = new RevenueForwarder__factory(sa.default.signer).deploy(ZERO_ADDRESS, mAsset.address, forwarderAddress) await expect(tx).to.revertedWith("Nexus address is zero") }) it("mAsset", async () => { - const tx = new RevenueForwarder__factory(sa.default.signer).deploy( - nexus.address, - ZERO_ADDRESS, - keeper.address, - forwarderAddress, - ) + const tx = new RevenueForwarder__factory(sa.default.signer).deploy(nexus.address, ZERO_ADDRESS, forwarderAddress) await expect(tx).to.revertedWith("mAsset is zero") }) - it("Keeper", async () => { - const tx = new RevenueForwarder__factory(sa.default.signer).deploy( - nexus.address, - mAsset.address, - ZERO_ADDRESS, - forwarderAddress, - ) - await expect(tx).to.revertedWith("Keeper is zero") - }) it("Forwarder", async () => { - const tx = new RevenueForwarder__factory(sa.default.signer).deploy( - nexus.address, - mAsset.address, - keeper.address, - ZERO_ADDRESS, - ) + const tx = new RevenueForwarder__factory(sa.default.signer).deploy(nexus.address, mAsset.address, ZERO_ADDRESS) await expect(tx).to.revertedWith("Forwarder is zero") }) }) @@ -162,7 +130,7 @@ describe("RevenueForwarder", () => { expect(await mAsset.balanceOf(revenueForwarder.address), "revenue forwarder's mAsset bal before").to.eq(notificationAmount) expect(await mAsset.balanceOf(forwarderAddress), "forwarder's mAsset bal before").to.eq(0) - const tx = await revenueForwarder.connect(keeper.signer).forward() + const tx = await revenueForwarder.connect(sa.keeper.signer).forward() await expect(tx).to.emit(revenueForwarder, "Withdrawn").withArgs(notificationAmount) expect(await mAsset.balanceOf(revenueForwarder.address), "revenue forwarder's mAsset bal after").to.eq(0) @@ -180,11 +148,11 @@ describe("RevenueForwarder", () => { }) it("should forward with no rewards balance", async () => { // Forward whatever balance it currently has - await revenueForwarder.connect(keeper.signer).forward() + await revenueForwarder.connect(sa.keeper.signer).forward() expect(await mAsset.balanceOf(revenueForwarder.address), "revenue forwarder's mAsset bal before").to.eq(0) - const tx = await revenueForwarder.connect(keeper.signer).forward() + const tx = await revenueForwarder.connect(sa.keeper.signer).forward() await expect(tx).to.not.emit(revenueForwarder, "Withdrawn") expect(await mAsset.balanceOf(revenueForwarder.address), "revenue forwarder's mAsset bal after").to.eq(0) @@ -206,7 +174,7 @@ describe("RevenueForwarder", () => { expect(await revenueForwarder.forwarder(), "forwarder after").to.eq(newForwarderAddress) }) it("keeper should fail to set new forwarder", async () => { - const tx = revenueForwarder.connect(keeper.signer).setConfig(newForwarderAddress) + const tx = revenueForwarder.connect(sa.keeper.signer).setConfig(newForwarderAddress) await expect(tx).to.revertedWith("Only governor can execute") }) diff --git a/test/emissions/emission-controller.spec.ts b/test/emissions/emission-controller.spec.ts index ed508211..8fb34b2e 100644 --- a/test/emissions/emission-controller.spec.ts +++ b/test/emissions/emission-controller.spec.ts @@ -19,6 +19,7 @@ import { } from "types/generated" import { currentWeekEpoch, increaseTime, getTimestamp, increaseTimeTo, startWeek, weekEpoch } from "@utils/time" import { Account } from "types/common" +import { keccak256, toUtf8Bytes } from "ethers/lib/utils" const defaultConfig = { A: -166000000000000, @@ -36,12 +37,12 @@ interface VoteHistoryExpectation { lastEpoch: number } interface DialData { - disabled: boolean - notify: boolean - cap: number - balance: BN - recipient: string - voteHistory: { votes: BN; epoch: number }[] + disabled: boolean + notify: boolean + cap: number + balance: BN + recipient: string + voteHistory: { votes: BN; epoch: number }[] } const sum = (a:BN, b:BN) => a.add(b) @@ -138,10 +139,7 @@ const expectTopLineEmissionForEpoch = (emissionsController: EmissionsController, const expectedEmissionAmount = await nextRewardAmount(emissionsController, deltaEpoch) expect(emissionForEpoch).eq(expectedEmissionAmount) } -const snapDial = async ( - emissionsController: EmissionsController, - dialId: number, -): Promise => { +const snapDial = async (emissionsController: EmissionsController, dialId: number): Promise => { const dialData = await emissionsController.dials(dialId) const voteHistory = await emissionsController.getDialVoteHistory(dialId) return { @@ -216,6 +214,8 @@ describe("EmissionsController", async () => { const accounts = await ethers.getSigners() sa = await new StandardAccounts().initAccounts(accounts) + console.log(`Keeper ${keccak256(toUtf8Bytes("Keeper"))}`) + voter1 = sa.dummy1 voter2 = sa.dummy2 voter3 = sa.dummy3 @@ -721,9 +721,9 @@ describe("EmissionsController", async () => { await increaseTime(ONE_WEEK) // Expect initial vote with no weight - const dialsVoteHistory = [ { dialId: 0, votesNo: 1, lastVote: 0, lastEpoch: startEpoch} ] + const dialsVoteHistory = [{ dialId: 0, votesNo: 1, lastVote: 0, lastEpoch: startEpoch }] await expectDialVotesHistoryForDials(emissionsController, dialsVoteHistory) - + const tx = await emissionsController.calculateRewards() await expect(tx).to.emit(emissionsController, "PeriodRewards").withArgs([0, 0, 0]) @@ -1503,11 +1503,11 @@ describe("EmissionsController", async () => { it("added that was not in previous distributions", async () => { // --- Given --- // that dial 1,2 and 3 are enabled - // and voter 1 gives all its votes to dial 1, and voter 2 gives all its votes to dial 2, and dial 3 does not have any vote weight + // and voter 1 gives all its votes to dial 1, and voter 2 gives all its votes to dial 2, and dial 3 does not have any vote weight - const dialBefore:Array = []; - const dialAfter:Array = []; - const adjustedDials:BN[][] = [[]] + const dialBefore: Array = [] + const dialAfter: Array = [] + const adjustedDials: BN[][] = [[]] dialBefore[0] = await snapDial(emissionsController, 0) dialBefore[1] = await snapDial(emissionsController, 1) @@ -1525,7 +1525,6 @@ describe("EmissionsController", async () => { let nextEpochEmission = await nextRewardAmount(emissionsController) let tx = await emissionsController.calculateRewards() - dialAfter[0] = await snapDial(emissionsController, 0) dialAfter[1] = await snapDial(emissionsController, 1) @@ -1546,23 +1545,26 @@ describe("EmissionsController", async () => { // -- When -- // it adds a new dial and calculates the distribution - const newDial = await new MockRewardsDistributionRecipient__factory(sa.default.signer).deploy(rewardToken.address, DEAD_ADDRESS) + const newDial = await new MockRewardsDistributionRecipient__factory(sa.default.signer).deploy( + rewardToken.address, + DEAD_ADDRESS, + ) tx = await emissionsController.connect(sa.governor.signer).addDial(newDial.address, 0, true) - await expect(tx).to.emit(emissionsController, "AddedDial").withArgs(3, newDial.address) + await expect(tx).to.emit(emissionsController, "AddedDial").withArgs(3, newDial.address) - // Assign voters 3 weight to new dial - await emissionsController.connect(voter3.signer).setVoterDialWeights([{ dialId: 3, weight: 200 }]) + // Assign voters 3 weight to new dial + await emissionsController.connect(voter3.signer).setVoterDialWeights([{ dialId: 3, weight: 200 }]) dialBefore[3] = await snapDial(emissionsController, 3) // calculates distribution await increaseTime(ONE_WEEK) - nextEpochEmission = await nextRewardAmount(emissionsController) - tx = await emissionsController.calculateRewards() + nextEpochEmission = await nextRewardAmount(emissionsController) + tx = await emissionsController.calculateRewards() - dialAfter[0] = await snapDial(emissionsController, 0) - dialAfter[1] = await snapDial(emissionsController, 1) - dialAfter[3] = await snapDial(emissionsController, 3) + dialAfter[0] = await snapDial(emissionsController, 0) + dialAfter[1] = await snapDial(emissionsController, 1) + dialAfter[3] = await snapDial(emissionsController, 3) // -- Then -- // the new dial should receive emissions after calculating rewards @@ -1572,10 +1574,12 @@ describe("EmissionsController", async () => { adjustedDials[1][1] = nextEpochEmission.div(2) // Voter 3 has 300 of the 1200 votes (2/5) adjustedDials[3][0] = BN.from(0) - adjustedDials[3][1] = nextEpochEmission.div(4) + adjustedDials[3][1] = nextEpochEmission.div(4) // Then disabled dials should not receive any distribution - - await expect(tx).to.emit(emissionsController, "PeriodRewards").withArgs([adjustedDials[0][1], adjustedDials[1][1], 0, adjustedDials[3][1]]) + + await expect(tx) + .to.emit(emissionsController, "PeriodRewards") + .withArgs([adjustedDials[0][1], adjustedDials[1][1], 0, adjustedDials[3][1]]) expect((await emissionsController.dials(0)).balance, "dial 1 balance after").to.eq(adjustedDials[0].reduce(sum)) expect((await emissionsController.dials(1)).balance, "dial 2 balance after").to.eq(adjustedDials[1].reduce(sum)) @@ -1583,7 +1587,9 @@ describe("EmissionsController", async () => { expect((await emissionsController.dials(3)).balance, "dial new balance after").to.eq(adjustedDials[3].reduce(sum)) // New votes should be cast for new dial expect(dialBefore[3].voteHistory.length + 1, "dial new vote history should increase").to.eq(dialAfter[3].voteHistory.length) - expect(dialBefore[3].voteHistory.slice(-1)[0].votes, "dial 1 vote weight should not change").to.eq(dialAfter[3].voteHistory.slice(-1)[0].votes) + expect(dialBefore[3].voteHistory.slice(-1)[0].votes, "dial 1 vote weight should not change").to.eq( + dialAfter[3].voteHistory.slice(-1)[0].votes, + ) }) }) // after a new staking contract was added that was not in previous distributions [DONE] @@ -2553,4 +2559,4 @@ describe("EmissionsController", async () => { }) }) }) -}) \ No newline at end of file +})