Skip to content

Commit

Permalink
rewards redirector
Browse files Browse the repository at this point in the history
  • Loading branch information
belbix committed Aug 26, 2023
1 parent 33c384e commit 7590462
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 0 deletions.
99 changes: 99 additions & 0 deletions contracts/tools/RewardsRedirector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "../openzeppelin/SafeERC20.sol";
import "../interfaces/IGauge.sol";
import "../openzeppelin/EnumerableSet.sol";

contract RewardsRedirector {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;

string public constant VERSION = "1.0.0";

address public owner;
address public pendingOwner;
address public gauge;
EnumerableSet.AddressSet internal operators;
EnumerableSet.AddressSet internal redirected;
mapping(address => address[]) public redirectedVaults;

constructor(address _owner, address _gauge) {
owner = _owner;
gauge = _gauge;
operators.add(_owner);
}

modifier onlyOwner() {
require(msg.sender == owner, "!owner");
_;
}

modifier onlyOperator() {
require(operators.contains(msg.sender), "!operator");
_;
}

/////////////////// VIEWS ////////////////////

function getOperators() external view returns (address[] memory) {
return operators.values();
}

function getRedirected() external view returns (address[] memory) {
return redirected.values();
}

function getRedirectedVaults(address adr) external view returns (address[] memory) {
return redirectedVaults[adr];
}

/////////////////// GOV ////////////////////

function offerOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "zero");
pendingOwner = newOwner;
}

function acceptOwnership() external {
require(msg.sender == pendingOwner, "!owner");
owner = pendingOwner;
}

function changeOperator(address adr, bool status) external onlyOwner {
if (status) {
operators.add(adr);
} else {
operators.remove(adr);
}
}

function changeRedirected(address adr, address[] calldata vaults, bool status) external onlyOwner {
if (status) {
redirected.add(adr);
redirectedVaults[adr] = vaults;
} else {
redirected.remove(adr);
delete redirectedVaults[adr];
}
}

/////////////////// MAIN LOGIC ////////////////////

function claimRewards() external onlyOperator {
address _gauge = gauge;
address[] memory _redirected = redirected.values();
for (uint j; j < _redirected.length; ++j) {
address[] memory _vaults = redirectedVaults[_redirected[j]];
for (uint i; i < _vaults.length; ++i) {
IGauge(_gauge).getAllRewards(_vaults[i], _redirected[j]);
}
}
}

function withdraw(address token) external onlyOperator {
IERC20(token).safeTransfer(msg.sender, IERC20(token).balanceOf(address(this)));
}

}
1 change: 1 addition & 0 deletions scripts/addresses/polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class PolygonAddresses {
public static SPLITTER_REBALANCE_RESOLVER = "0x9618D3e9c8a133d081dE37b99c6ECf108C1e82F2".toLowerCase();
public static PERF_FEE_TREASURY = "0x9Cc199D4353b5FB3e6C8EEBC99f5139e0d8eA06b".toLowerCase();
public static TETU_BRIDGED_PROCESSING = "0x1950a09fc28Dd3C36CaC89485357844Af0739C07".toLowerCase();
public static REWARDS_REDIRECTOR = "0xA9947d0815d6EA3077805E2112FB19572DD4dc9E".toLowerCase();

// PROTOCOL ADRS
public static DEPOSIT_HELPER_V2 = "0xab2422A4d8Ac985AE98F5Da3713988b420f24165".toLowerCase();
Expand Down
20 changes: 20 additions & 0 deletions scripts/deploy/DeployRewardsRedirector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {ethers} from "hardhat";
import {DeployerUtils} from "../utils/DeployerUtils";
import {appendFileSync} from "fs";
import {Addresses} from "../addresses/addresses";
import {ForwarderDistributeResolver__factory, HardWorkResolver__factory} from "../../typechain";
import {RunHelper} from "../utils/RunHelper";


async function main() {
const signer = (await ethers.getSigners())[0];
const core = Addresses.getCore();
await DeployerUtils.deployContract(signer, 'RewardsRedirector', '0x0644141dd9c2c34802d28d334217bd2034206bf7', core.gauge);
}

main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
157 changes: 157 additions & 0 deletions test/tools/RewardsRedirectorTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers";
import {MockStakingToken, MockToken, MultiGauge, RewardsRedirector, VeTetu,} from "../../typechain";
import {TimeUtils} from "../TimeUtils";
import {ethers} from "hardhat";
import {DeployerUtils} from "../../scripts/utils/DeployerUtils";
import {BigNumber} from "ethers";
import {Misc} from "../../scripts/utils/Misc";
import {parseUnits} from "ethers/lib/utils";

const {expect} = chai;
chai.use(chaiAsPromised);

const FULL_AMOUNT = parseUnits('100');

describe("RewardsRedirectorTest", function () {
let snapshotBefore: string;
let snapshot: string;
let owner: SignerWithAddress;
let rewarder: SignerWithAddress;
let user: SignerWithAddress;
let claimer: SignerWithAddress;

let redirector: RewardsRedirector;
let stakingToken: MockStakingToken;
let stakingToken2: MockStakingToken;
let tetu: MockToken;
let rewardToken: MockToken;
let rewardToken2: MockToken;
let rewardTokenDefault: MockToken;
let gauge: MultiGauge;
let ve: VeTetu;


before(async function () {
this.timeout(1200000);
snapshotBefore = await TimeUtils.snapshot();
[owner, rewarder, user, claimer] = await ethers.getSigners();

tetu = await DeployerUtils.deployMockToken(owner, 'TETU', 18);
const controller = await DeployerUtils.deployMockController(owner);
ve = await DeployerUtils.deployVeTetu(owner, tetu.address, controller.address);
const voter = await DeployerUtils.deployMockVoter(owner, ve.address);
await controller.setVoter(voter.address);

rewardToken = await DeployerUtils.deployMockToken(owner, 'REWARD', 18);
rewardToken = await DeployerUtils.deployMockToken(owner, 'REWARD', 18);
await rewardToken.mint(rewarder.address, BigNumber.from(Misc.MAX_UINT).sub(parseUnits('1000000')));
rewardToken2 = await DeployerUtils.deployMockToken(owner, 'REWARD2', 18);
await rewardToken2.mint(rewarder.address, parseUnits('100'));
rewardTokenDefault = await DeployerUtils.deployMockToken(owner, 'REWARD_DEFAULT', 18);
await rewardTokenDefault.mint(rewarder.address, parseUnits('100'));

gauge = await DeployerUtils.deployMultiGauge(
owner,
controller.address,
ve.address,
rewardTokenDefault.address,
);

stakingToken = await DeployerUtils.deployMockStakingToken(owner, gauge.address, 'VAULT', 18);
stakingToken2 = await DeployerUtils.deployMockStakingToken(owner, gauge.address, 'VAULT2', 18);

await gauge.addStakingToken(stakingToken.address);
await gauge.registerRewardToken(stakingToken.address, rewardToken.address);

await stakingToken.mint(owner.address, FULL_AMOUNT);
await stakingToken.mint(user.address, FULL_AMOUNT);

await tetu.approve(ve.address, Misc.MAX_UINT);
await tetu.connect(user).approve(ve.address, Misc.MAX_UINT);
await tetu.connect(rewarder).approve(ve.address, Misc.MAX_UINT);
await rewardToken.approve(gauge.address, Misc.MAX_UINT);
await rewardToken2.approve(gauge.address, Misc.MAX_UINT);
await rewardTokenDefault.approve(gauge.address, Misc.MAX_UINT);
await rewardToken.connect(rewarder).approve(gauge.address, Misc.MAX_UINT);
await rewardToken2.connect(rewarder).approve(gauge.address, Misc.MAX_UINT);
await rewardTokenDefault.connect(rewarder).approve(gauge.address, Misc.MAX_UINT);

redirector = await DeployerUtils.deployContract(owner, "RewardsRedirector", owner.address, gauge.address) as RewardsRedirector;
})

after(async function () {
await TimeUtils.rollback(snapshotBefore);
});

beforeEach(async function () {
snapshot = await TimeUtils.snapshot();
});

afterEach(async function () {
await TimeUtils.rollback(snapshot);
});

it("set new gov", async () => {
await expect(redirector.connect(user).offerOwnership(user.address)).revertedWith('!owner');
await redirector.offerOwnership(user.address)
await expect(redirector.acceptOwnership()).revertedWith('!owner');
await redirector.connect(user).acceptOwnership()
expect(await redirector.owner()).eq(user.address)
await expect(redirector.offerOwnership(user.address)).revertedWith('!owner');
})

it("change operator test", async () => {
expect((await redirector.getOperators())[0]).eq(owner.address);

await redirector.changeOperator(user.address, true);
await redirector.changeOperator(rewarder.address, true);

expect((await redirector.getOperators())[1]).eq(user.address);
expect((await redirector.getOperators())[2]).eq(rewarder.address);

await redirector.changeOperator(user.address, false);
expect((await redirector.getOperators())[1]).eq(rewarder.address);
})

it("change redirect test", async () => {
expect((await redirector.getRedirected()).length).eq(0);

await redirector.changeRedirected(user.address, [owner.address, rewarder.address], true);

expect((await redirector.getRedirected())[0]).eq(user.address);
expect((await redirector.getRedirectedVaults(user.address))[0]).eq(owner.address);
expect((await redirector.getRedirectedVaults(user.address))[1]).eq(rewarder.address);

await redirector.changeRedirected(user.address, [], false);
expect((await redirector.getRedirected()).length).eq(0);
expect((await redirector.getRedirectedVaults(user.address)).length).eq(0);
})

it("claim test", async () => {
// add reward
await gauge.notifyRewardAmount(stakingToken.address, rewardToken.address, parseUnits('1'));
await gauge.registerRewardToken(stakingToken.address, rewardToken2.address);
await gauge.notifyRewardAmount(stakingToken.address, rewardToken2.address, parseUnits('1'));

// redirect
await gauge.setRewardsRedirect(user.address, redirector.address);
await redirector.changeRedirected(user.address, [stakingToken.address], true);

await TimeUtils.advanceBlocksOnTs(60 * 60 * 24 * 4)

expect(await rewardToken.balanceOf(user.address)).eq(0);
expect(await rewardToken.balanceOf(redirector.address)).eq(0);

await redirector.claimRewards()

expect(await rewardToken.balanceOf(user.address)).eq(0);
expect(await rewardToken.balanceOf(redirector.address)).not.eq(0);

await redirector.changeOperator(claimer.address, true);
expect(await rewardToken.balanceOf(claimer.address)).eq(0);
await redirector.connect(claimer).withdraw(rewardToken.address);
expect(await rewardToken.balanceOf(claimer.address)).not.eq(0);
})
})

0 comments on commit 7590462

Please sign in to comment.