forked from mstable/mStable-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add WIP GaugeBriber (mstable#256)
* feat: Add GaugeBriber
- Loading branch information
1 parent
a2c98d5
commit 66f3646
Showing
4 changed files
with
277 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
pragma solidity 0.8.6; | ||
|
||
import { IRevenueRecipient } from "../interfaces/IRevenueRecipient.sol"; | ||
import { ImmutableModule } from "../shared/ImmutableModule.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
/** | ||
* @title GaugeBriber | ||
* @author mStable | ||
* @notice Collect system revenue in mUSD, converts to MTA, funds bribe on Votium | ||
* @dev VERSION: 1.0 | ||
* DATE: 2021-10-19 | ||
*/ | ||
contract GaugeBriber is IRevenueRecipient, ImmutableModule { | ||
using SafeERC20 for IERC20; | ||
|
||
event RevenueReceived(address indexed mAsset, uint256 amountIn); | ||
event Withdrawn(uint256 amountOut, uint256 amountToChild); | ||
|
||
IERC20 public immutable musd; | ||
|
||
address public immutable keeper; | ||
address public briber; | ||
|
||
IRevenueRecipient public childRecipient; | ||
uint256 public feeSplit; | ||
|
||
uint256[2] public available; | ||
|
||
constructor( | ||
address _nexus, | ||
address _musd, | ||
address _keeper, | ||
address _briber, | ||
address _childRecipient | ||
) ImmutableModule(_nexus) { | ||
musd = IERC20(_musd); | ||
keeper = _keeper; | ||
briber = _briber; | ||
childRecipient = IRevenueRecipient(_childRecipient); | ||
} | ||
|
||
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 | ||
* @param _amount Units of mAsset collected | ||
*/ | ||
function notifyRedistributionAmount(address _mAsset, uint256 _amount) external override { | ||
require(_mAsset == address(musd), "This Recipient is only for mUSD"); | ||
// Transfer from sender to here | ||
IERC20(_mAsset).safeTransferFrom(msg.sender, address(this), _amount); | ||
|
||
available[0] += ((_amount * (1e18 - feeSplit)) / 1e18); | ||
available[1] += ((_amount * feeSplit) / 1e18); | ||
|
||
emit RevenueReceived(_mAsset, _amount); | ||
} | ||
|
||
/** | ||
* @dev Withdraws to bribing capacity | ||
*/ | ||
function forward() external keeperOrGovernor { | ||
uint256 amt = available[0]; | ||
available[0] = 0; | ||
musd.safeTransfer(briber, amt); | ||
|
||
uint256 amtChild = available[1]; | ||
if (amtChild > 0) { | ||
available[1] = 0; | ||
childRecipient.notifyRedistributionAmount(address(musd), amtChild); | ||
} | ||
emit Withdrawn(amt, amtChild); | ||
} | ||
|
||
/** | ||
* @dev Sets fee split details for child revenue recipient | ||
* @param _briber new briber | ||
* @param _newRecipient Address of child RevenueRecipient | ||
* @param _feeSplit Percentage of total received that goes to child | ||
*/ | ||
function setConfig( | ||
address _briber, | ||
address _newRecipient, | ||
uint256 _feeSplit | ||
) external onlyGovernor { | ||
require(_feeSplit <= 5e17, "Must be less than 50%"); | ||
require(_briber != address(0), "Invalid briber"); | ||
briber = _briber; | ||
childRecipient = IRevenueRecipient(_newRecipient); | ||
feeSplit = _feeSplit; | ||
} | ||
|
||
/** | ||
* @dev Abstract override | ||
*/ | ||
function depositToPool( | ||
address[] calldata, /* _mAssets */ | ||
uint256[] calldata /* _percentages */ | ||
) external override {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import "ts-node/register" | ||
import "tsconfig-paths/register" | ||
import { task, types } from "hardhat/config" | ||
import { GaugeBriber__factory } from "types/generated" | ||
import { deployContract } from "./utils/deploy-utils" | ||
import { getSigner } from "./utils/signerFactory" | ||
import { verifyEtherscan } from "./utils/etherscan" | ||
import { getChain, resolveAddress } from "./utils/networkAddressFactory" | ||
|
||
task("deploy-GaugeBriber") | ||
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) | ||
.setAction(async (taskArgs, hre) => { | ||
const signer = await getSigner(hre, taskArgs.speed) | ||
const chain = getChain(hre) | ||
|
||
const nexus = resolveAddress("Nexus", chain) | ||
const musd = resolveAddress("mUSD", chain, "address") | ||
const keeper = "0xb81473f20818225302b8fffb905b53d58a793d84" | ||
const briber = "0xd0f0F590585384AF7AB420bE1CFB3A3F8a82D775" | ||
const childRecipient = resolveAddress("RevenueRecipient", chain) | ||
|
||
const gaugeBriber = await deployContract(new GaugeBriber__factory(signer), "GaugeBriber", [ | ||
nexus, | ||
musd, | ||
keeper, | ||
briber, | ||
childRecipient, | ||
]) | ||
|
||
await verifyEtherscan(hre, { | ||
address: gaugeBriber.address, | ||
contract: "contracts/buy-and-make/GaugeBriber.sol:GaugeBriber", | ||
}) | ||
}) | ||
|
||
module.exports = {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import { DEAD_ADDRESS, ZERO_ADDRESS, ONE_WEEK } from "@utils/constants" | ||
import { impersonate, impersonateAccount } from "@utils/fork" | ||
import { simpleToExactAmount } from "@utils/math" | ||
import { increaseTime } from "@utils/time" | ||
import { assertBNClose } from "@utils/assertions" | ||
import { expect } from "chai" | ||
import { Signer } from "ethers" | ||
import * as hre from "hardhat" | ||
import { | ||
SavingsManager, | ||
SavingsManager__factory, | ||
GaugeBriber, | ||
GaugeBriber__factory, | ||
ERC20__factory, | ||
Collector, | ||
Collector__factory, | ||
} from "types/generated" | ||
import { Account } from "types" | ||
import { Chain } from "tasks/utils/tokens" | ||
import { resolveAddress } from "../../tasks/utils/networkAddressFactory" | ||
import { deployContract } from "../../tasks/utils/deploy-utils" | ||
|
||
const musdWhaleAddress = "0x136d841d4bece3fc0e4debb94356d8b6b4b93209" | ||
const governorAddress = resolveAddress("Governor") | ||
const deployerAddress = resolveAddress("OperationsSigner") | ||
const ethWhaleAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" | ||
|
||
// 1. Deploy && change Recipient | ||
// - check config | ||
// 2. Beta testing | ||
// - collector.distributeInterest | ||
// - forward | ||
// - setConfig | ||
// - collector.distributeInterest | ||
// - check split | ||
context("Recipient deployment and upgrade", () => { | ||
let deployer: Account | ||
let governor: Account | ||
let ethWhale: Signer | ||
let musdWhale: Signer | ||
let savingsManager: SavingsManager | ||
let musdAddr: string | ||
let gaugeBriber: GaugeBriber | ||
let collector: Collector | ||
|
||
const { network } = hre | ||
|
||
before("reset block number", async () => { | ||
await network.provider.request({ | ||
method: "hardhat_reset", | ||
params: [ | ||
{ | ||
forking: { | ||
jsonRpcUrl: process.env.NODE_URL, | ||
blockNumber: 13467671, | ||
}, | ||
}, | ||
], | ||
}) | ||
deployer = await impersonateAccount(deployerAddress) | ||
governor = await impersonateAccount(governorAddress) | ||
ethWhale = await impersonate(ethWhaleAddress) | ||
musdWhale = await impersonate(musdWhaleAddress) | ||
|
||
// send some Ether to the impersonated multisig contract as it doesn't have Ether | ||
await ethWhale.sendTransaction({ | ||
to: governorAddress, | ||
value: simpleToExactAmount(1), | ||
}) | ||
}) | ||
context("1. Deploying", () => { | ||
it("deploys new contract", async () => { | ||
const nexus = resolveAddress("Nexus", Chain.mainnet) | ||
musdAddr = resolveAddress("mUSD", Chain.mainnet, "address") | ||
const keeper = "0xb81473f20818225302b8fffb905b53d58a793d84" | ||
const briber = "0xd0f0F590585384AF7AB420bE1CFB3A3F8a82D775" | ||
const childRecipient = resolveAddress("RevenueRecipient", Chain.mainnet) | ||
|
||
gaugeBriber = await deployContract(new GaugeBriber__factory(deployer.signer), "GaugeBriber", [ | ||
nexus, | ||
musdAddr, | ||
keeper, | ||
briber, | ||
childRecipient, | ||
]) | ||
}) | ||
it("execs upgrade", async () => { | ||
const savingsManagerAddress = resolveAddress("SavingsManager", Chain.mainnet) | ||
savingsManager = SavingsManager__factory.connect(savingsManagerAddress, governor.signer) | ||
await savingsManager.setRevenueRecipient(musdAddr, gaugeBriber.address) | ||
|
||
const collectorAddress = resolveAddress("Collector", Chain.mainnet) | ||
collector = Collector__factory.connect(collectorAddress, governor.signer) | ||
}) | ||
}) | ||
// 2. Beta testing | ||
// - collector.distributeInterest | ||
// - forward | ||
// - setConfig | ||
// - collector.distributeInterest | ||
// - check split | ||
context("2. Beta tests", () => { | ||
let bal | ||
it("collects & distributes to revenueRecipient", async () => { | ||
await collector.distributeInterest([musdAddr], true) | ||
bal = await ERC20__factory.connect(musdAddr, deployer.signer).balanceOf(gaugeBriber.address) | ||
expect(bal).gt(0) | ||
expect(await gaugeBriber.available(0)).eq(bal) | ||
}) | ||
it("forwards to briber", async () => { | ||
await gaugeBriber.forward() | ||
const briberBal = await ERC20__factory.connect(musdAddr, deployer.signer).balanceOf( | ||
"0xd0f0F590585384AF7AB420bE1CFB3A3F8a82D775", | ||
) | ||
expect(briberBal).eq(bal) | ||
}) | ||
it("sets config", async () => { | ||
await gaugeBriber.connect(governor.signer).setConfig(DEAD_ADDRESS, ZERO_ADDRESS, simpleToExactAmount(1, 17)) | ||
expect(await gaugeBriber.briber()).eq(DEAD_ADDRESS) | ||
expect(await gaugeBriber.childRecipient()).eq(ZERO_ADDRESS) | ||
expect(await gaugeBriber.feeSplit()).eq(simpleToExactAmount(1, 17)) | ||
}) | ||
it("collects & distributes to revenueRecipient", async () => { | ||
await increaseTime(ONE_WEEK) | ||
await collector.distributeInterest([musdAddr], true) | ||
bal = await ERC20__factory.connect(musdAddr, deployer.signer).balanceOf(gaugeBriber.address) | ||
expect(bal).gt(0) | ||
const available0 = await gaugeBriber.available(0) | ||
const available1 = await gaugeBriber.available(1) | ||
assertBNClose(bal, available0.add(available1), 1) | ||
}) | ||
}) | ||
}) |