Skip to content

Commit

Permalink
test: adds tests to VotiumBribeForwarder (#303)
Browse files Browse the repository at this point in the history
* test: adds tests to VotiumBribeForwarder

* test: adds tests to DisperseForwarder

* chore:fix eslint issues on emissions controller files
  • Loading branch information
doncesarts authored Dec 7, 2021
1 parent 5c794bd commit 5d57eae
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 25 deletions.
19 changes: 9 additions & 10 deletions contracts/emissions/EmissionsController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ struct VoterPreferences {
// Each item is a dialId and weight.
// The array is stored as a uint256
uint256 dialWeights;

// Total voting power cast by this voter across the staking contracts.
uint128 votesCast;
// Last time balance was looked up across all staking contracts
Expand Down Expand Up @@ -289,8 +288,8 @@ contract EmissionsController is IGovernanceHook, Initializable, ImmutableModule
returns (Preference[16] memory preferences)
{
for (uint256 i = 0; i < 16; i++) {
preferences[i].weight = uint8(voterPreferences[voter].dialWeights >> i * 16);
preferences[i].dialId = uint8(voterPreferences[voter].dialWeights >> i * 16 + 8);
preferences[i].weight = uint8(voterPreferences[voter].dialWeights >> (i * 16));
preferences[i].dialId = uint8(voterPreferences[voter].dialWeights >> (i * 16 + 8));
}
}

Expand Down Expand Up @@ -583,19 +582,19 @@ contract EmissionsController is IGovernanceHook, Initializable, ImmutableModule
newTotalWeight += _preferences[i].weight;

// Add staker's dial weight
newDialWeights |= uint256(_preferences[i].weight) << i * 16;
newDialWeights |= uint256(_preferences[i].weight) << (i * 16);
// Add staker's dial id
newDialWeights |= uint256(_preferences[i].dialId) << (i * 16) + 8;
newDialWeights |= uint256(_preferences[i].dialId) << ((i * 16) + 8);
}

// 2.1 - In the likely scenario less than 16 preferences are given, add a breaker with max uint
// to signal that this is the end of array.
if (_preferences.length < 16) {
// Set dialId to 255
newDialWeights |= uint256(255) << (_preferences.length * 16) + 8;
newDialWeights |= uint256(255) << ((_preferences.length * 16) + 8);
}
require(newTotalWeight <= SCALE, "Imbalanced weights");
// Update storage with the array of 16 Preferences stored as an uint256
// Update storage with the array of 16 Preferences stored as an uint256
voterPreferences[msg.sender].dialWeights = newDialWeights;

// Need to set before calling _moveVotingPower for the second time
Expand Down Expand Up @@ -691,10 +690,10 @@ contract EmissionsController is IGovernanceHook, Initializable, ImmutableModule

// 1.0 - Loop through voter preferences until dialId == 255 or until end
for (uint256 i = 0; i < 16; i++) {
uint256 dialId = uint8(preferences.dialWeights >> (i * 16) + 8);
uint256 dialId = uint8(preferences.dialWeights >> ((i * 16) + 8));
if (dialId == 255) break;
uint256 weight = uint8(preferences.dialWeights >> i * 16);

uint256 weight = uint8(preferences.dialWeights >> (i * 16));

// 1.1 - Scale the vote by dial weight
// e.g. 5e17 * 1e18 / 1e18 * 100e18 / 1e18 = 50e18
Expand Down
14 changes: 8 additions & 6 deletions contracts/emissions/VotiumBribeForwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ImmutableModule } from "../shared/ImmutableModule.sol";
contract VotiumBribeForwarder is ImmutableModule {
using SafeERC20 for IERC20;

/// @notice Rewards token that is to be deposit.
/// @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
Expand All @@ -29,18 +29,20 @@ contract VotiumBribeForwarder is ImmutableModule {
* @param _rewardsToken Bridged rewards token on the Polygon chain.
* @param _votiumBribe Token votium bribe contract.
*/
constructor(address _nexus, address _rewardsToken, address _votiumBribe)
ImmutableModule(_nexus) {
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 amount the amount of reward tokens to deposit including decimal places.
* @param proposal votium brive proposal
*/
function depositBribe(uint256 amount, bytes32 proposal) external onlyKeeperOrGovernor {
Expand All @@ -59,6 +61,6 @@ contract VotiumBribeForwarder is ImmutableModule {
* @param _choiceIndex the bribe choice index
*/
function updateChoiceIndex(uint256 _choiceIndex) public onlyKeeperOrGovernor {
choiceIndex = _choiceIndex;
choiceIndex = _choiceIndex;
}
}
18 changes: 18 additions & 0 deletions contracts/z_mocks/emissions/MockDisperse.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IDisperse } from "../../interfaces/IDisperse.sol";

contract MockDisperse is IDisperse {
function disperseTokenSimple(
IERC20 token,
address[] calldata recipients,
uint256[] calldata values
) external override {
for (uint256 i = 0; i < recipients.length; i++) {
// solhint-disable-next-line reason-string
require(token.transferFrom(msg.sender, recipients[i], values[i]));
}
}
}
29 changes: 29 additions & 0 deletions contracts/z_mocks/emissions/MockVotiumBribe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

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

contract MockVotiumBribe is IVotiumBribe {
using SafeERC20 for IERC20;

address public feeAddress = 0xe39b8617D571CEe5e75e1EC6B2bb40DdC8CF6Fa3; // Votium multisig
event Bribed(address _token, uint256 _amount, bytes32 indexed _proposal, uint256 _choiceIndex);

function depositBribe(
address _token,
uint256 _amount,
bytes32 _proposal,
uint256 _choiceIndex
) external override {
uint256 fee = 0;
uint256 bribeTotal = _amount - fee;
// Sends the fee to votium multisig
IERC20(_token).safeTransferFrom(msg.sender, feeAddress, fee);
// if distributor contract is not set, store in this contract until ready
IERC20(_token).safeTransferFrom(msg.sender, address(this), bribeTotal);

emit Bribed(_token, bribeTotal, _proposal, _choiceIndex);
}
}
2 changes: 1 addition & 1 deletion tasks/feeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
outputFees,
getCollectedInterest,
} from "./utils/snap-utils"
import { Chain, PFRAX, PmUSD, RAI, Token, tokens } from "./utils/tokens"
import { Chain, PFRAX, PmUSD, Token, tokens } from "./utils/tokens"
import { btcFormatter, QuantityFormatter, usdFormatter } from "./utils/quantity-formatters"
import { getSwapRates } from "./utils/rates-utils"
import { getSigner } from "./utils/signerFactory"
Expand Down
2 changes: 1 addition & 1 deletion test/buy-and-make/revenue-buy-back.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ describe("RevenueBuyBack", () => {
const senderBalBefore = await mUSD.balanceOf(sa.default.address)
const revenueBuyBackBalBefore = await mUSD.balanceOf(revenueBuyBack.address)
const notificationAmount = simpleToExactAmount(100, 18)
expect(senderBalBefore.gte(notificationAmount), "sender rewards bal before").to.be.true
expect(senderBalBefore.gte(notificationAmount), "sender rewards bal before").to.eq(true)

// approve
await mUSD.approve(revenueBuyBack.address, notificationAmount)
Expand Down
2 changes: 1 addition & 1 deletion test/buy-and-make/revenue-forwarder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe("RevenueForwarder", () => {
const senderBalBefore = await mAsset.balanceOf(sa.default.address)
const revenueBuyBackBalBefore = await mAsset.balanceOf(revenueForwarder.address)
const notificationAmount = simpleToExactAmount(100, 18)
expect(senderBalBefore.gte(notificationAmount), "sender rewards bal before").to.be.true
expect(senderBalBefore.gte(notificationAmount), "sender rewards bal before").to.eq(true)

// approve
await mAsset.approve(revenueForwarder.address, notificationAmount)
Expand Down
114 changes: 114 additions & 0 deletions test/emissions/disperse-forwarder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { MAX_UINT256, ZERO_ADDRESS } from "@utils/constants"
import { MassetMachine, StandardAccounts } from "@utils/machines"
import { simpleToExactAmount } from "@utils/math"
import { expect } from "chai"
import { ethers } from "hardhat"
import { Account } from "types/common"
import {
MockERC20,
MockNexus,
MockNexus__factory,
MockDisperse,
MockDisperse__factory,
DisperseForwarder,
DisperseForwarder__factory,
} from "types/generated"

describe("DisperseForwarder", () => {
let sa: StandardAccounts
let mAssetMachine: MassetMachine
let nexus: MockNexus
let rewardsToken: MockERC20
let owner: Account
let emissionsController: Account
let forwarder: DisperseForwarder
let disperse: MockDisperse

/*
Test Data
mAssets: mUSD and mBTC with 18 decimals
*/
const setup = async (): Promise<void> => {
// Deploy mock Nexus
nexus = await new MockNexus__factory(sa.default.signer).deploy(
sa.governor.address,
sa.mockSavingsManager.address,
sa.mockInterestValidator.address,
)
// Deploy mock Disperse
disperse = await new MockDisperse__factory(sa.default.signer).deploy()

rewardsToken = await mAssetMachine.loadBassetProxy("Rewards Token", "RWD", 18)
owner = sa.default
emissionsController = sa.dummy2

// Deploy DisperseForwarder
forwarder = await new DisperseForwarder__factory(owner.signer).deploy(nexus.address, rewardsToken.address, disperse.address)

await rewardsToken.transfer(emissionsController.address, simpleToExactAmount(10000))
await rewardsToken.connect(emissionsController.signer).approve(forwarder.address, MAX_UINT256)
}

before(async () => {
const accounts = await ethers.getSigners()
mAssetMachine = await new MassetMachine().initAccounts(accounts)
sa = mAssetMachine.sa
await setup()
})

describe("creating new instance", () => {
it("should have immutable variables set", async () => {
expect(await forwarder.nexus(), "Nexus").eq(nexus.address)
expect(await forwarder.REWARDS_TOKEN(), "rewards token").eq(rewardsToken.address)
expect(await forwarder.DISPERSE(), "disperse contract").eq(disperse.address)
})
describe("it should fail if zero", () => {
it("nexus", async () => {
const tx = new DisperseForwarder__factory(sa.default.signer).deploy(ZERO_ADDRESS, rewardsToken.address, disperse.address)
await expect(tx).to.revertedWith("Nexus address is zero")
})
it("rewards token", async () => {
const tx = new DisperseForwarder__factory(sa.default.signer).deploy(nexus.address, ZERO_ADDRESS, disperse.address)
await expect(tx).to.revertedWith("Invalid Rewards token")
})
it("disperse contract ", async () => {
const tx = new DisperseForwarder__factory(sa.default.signer).deploy(nexus.address, rewardsToken.address, ZERO_ADDRESS)
await expect(tx).to.revertedWith("Invalid Disperse contract")
})
})
})
describe("disperse token", () => {
it("should transfer rewards to forwarder", async () => {
const recipients = [disperse.address]
const values = [simpleToExactAmount(100, 18)]

const endRecipientBalBefore = await rewardsToken.balanceOf(disperse.address)
const amount = simpleToExactAmount(100, 18)
// Simulate the emissions controller calling the forwarder
await rewardsToken.transfer(forwarder.address, amount)

await forwarder.connect(sa.governor.signer).disperseToken(recipients, values)

// check output balances: mAsset sender/recipient
expect(await rewardsToken.balanceOf(disperse.address), "end recipient balance after").eq(endRecipientBalBefore.add(amount))
})
describe("should fail if", () => {
it("recipients and values do not match", async () => {
const recipients = [disperse.address]
const tx = forwarder.connect(sa.governor.signer).disperseToken(recipients, [])
await expect(tx).to.be.revertedWith("array mismatch")
})
it("balance is insufficient", async () => {
const balance = await rewardsToken.balanceOf(forwarder.address)
const tx = forwarder.connect(sa.governor.signer).disperseToken([disperse.address, disperse.address], [balance, 1])
await expect(tx).to.be.revertedWith("Insufficient rewards")
})
it("non governor or keeper", async () => {
const recipients = [disperse.address]
const values = [simpleToExactAmount(100, 18)]
const tx = forwarder.disperseToken(recipients, values)
await expect(tx).to.be.revertedWith("Only keeper or governor")
})
})
})
})
Loading

0 comments on commit 5d57eae

Please sign in to comment.