Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

veListaRewardsCourier #35

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions contracts/VeListaRewardsCourier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./interfaces/IVeListaDistributor.sol";

/**
* @title VeListaRewardsCourier
* @dev VeListaRewardsCourier act as a courier of delivery of rewards to the veListaDistributor
* veLista rewards will be deposit to the VeListaRewardsCourier weekly(usually at the beginning of the week),
* and a off-chain component with the BOT role will be responsible for triggers the VeListaRewardsCourier
* to deliver the rewards to the veListaDistributor on-time.
*/
contract VeListaRewardsCourier is Initializable, AccessControlUpgradeable {
using SafeERC20 for IERC20;

// @dev records rewarding week and token info.
// @dev week and tokens will be the parameter when calling `depositNewReward`
uint16 public week;
IVeListaDistributor.TokenAmount[] public tokens;
IVeListaDistributor public veListaDistributor;
bool public rewardsDeliveredForWeek;

// --- events
event RewardsRecharged(uint16, IVeListaDistributor.TokenAmount[]); // rewards has been pre-deposited to VeListaRewardsCourier
event RewardsDelivered(uint16, IVeListaDistributor.TokenAmount[]); // rewards has been delivered to veListaDistributor
event RewardsRevoked(IVeListaDistributor.TokenAmount[]); // reward has been revoked by the admin

// --- roles
bytes32 public constant BOT = keccak256("BOT");

/**
* @dev initialize the contract
* @param _admin address of the ADMIN role
* @param _bot address of the BOT role
* @param _veListaDistributor address of the veListaDistributor contract
*/
function initialize(address _admin, address _bot, address _veListaDistributor) external initializer {
__AccessControl_init();
require(_admin != address(0), "admin is a zero address");
require(_bot != address(0), "bot is a zero address");
require(_veListaDistributor != address(0), "veListaDistributor is a zero address");
_setupRole(DEFAULT_ADMIN_ROLE, _admin);
_setupRole(BOT, _bot);
veListaDistributor = IVeListaDistributor(_veListaDistributor);
rewardsDeliveredForWeek = true;
}

/**
* @dev recharge rewards to VeListaRewardsCourier
* @param _week rewards week
* @param _tokens rewards token info
*/
function rechargeRewards(uint16 _week, IVeListaDistributor.TokenAmount[] memory _tokens) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(rewardsDeliveredForWeek, "Pending rewards delivery for the week");
require(_tokens.length > 0, "No rewards to recharge");
// mark rewards as not delivered
rewardsDeliveredForWeek = false;
week = _week;
tokens = _tokens;
// moves token from sender to this contract
for (uint8 i = 0; i < _tokens.length; ++i) {
// validate if token is registered and non-zero amount
require(veListaDistributor.rewardTokenIndexes(_tokens[i].token) > 0, "Token is not registered");
require(_tokens[i].amount > 0, "Invalid token amount");
IERC20(_tokens[i].token).safeTransferFrom(msg.sender, address(this), _tokens[i].amount);
}
emit RewardsRecharged(_week, _tokens);
}

/**
* @dev deliver rewards to veListaDistributor
*/
function deliverRewards() external onlyRole(BOT) {
require(!rewardsDeliveredForWeek, "Rewards already delivered for the week");
rewardsDeliveredForWeek = true;
// approve veListaDistributor to move the token
for (uint256 i = 0; i < tokens.length; i++) {
IERC20(tokens[i].token).approve(address(veListaDistributor), tokens[i].amount);
}
// send to veListaDistributor
veListaDistributor.depositNewReward(week, tokens);
emit RewardsDelivered(week, tokens);
}

/**
* @dev revoke rewards in-case any need before rewards delivered to veListaDistributor
* extract rewards and send it back to the original sender
*/
function revokeRewards() external onlyRole(DEFAULT_ADMIN_ROLE) {
require(!rewardsDeliveredForWeek, "Rewards already delivered for the week");
rewardsDeliveredForWeek = true;
for (uint256 i = 0; i < tokens.length; i++) {
IERC20(tokens[i].token).safeTransfer(msg.sender, tokens[i].amount);
}
emit RewardsRevoked(tokens);
}

}
148 changes: 148 additions & 0 deletions contracts/VeListaRewardsCourierV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./interfaces/IVeListaDistributor.sol";
import "./interfaces/IVeLista.sol";

/**
* @title VeListaRewardsCourierV2
* @dev VeListaRewardsCourier act as a courier of delivery of rewards to the veListaDistributor
* veLista rewards will be deposit to the VeListaRewardsCourier weekly(usually at the beginning of the week),
* and a off-chain component with the BOT role will be responsible for triggers the VeListaRewardsCourier
* to deliver the rewards to the veListaDistributor on-time.
*/
contract VeListaRewardsCourierV2 is Initializable, AccessControlUpgradeable {

using SafeERC20 for IERC20;
// 1 week and 1 day in seconds
uint256 public constant WEEK = 1 weeks;
uint256 public constant DAY = 1 days;

// @dev rewards token: LISTA
IERC20 public token;
// @dev record if rewards has been delivered for the week
mapping(uint16 => bool) public rewardsDeliveredForWeek;
// @dev records the amount of rewards for each week
mapping(uint16 => uint256) public weeklyRewardAmounts;

// veLista ecosystem contracts
IVeLista public veLista;
IVeListaDistributor public veListaDistributor;

// --- events
event RewardsRecharged(uint16 week, uint256 amount); // rewards has been pre-deposited to VeListaRewardsCourier
event RewardsDelivered(uint16 week, uint256 amount); // rewards has been delivered to veListaDistributor
event RewardsAdjusted(uint16 week, int256 amount); // reward has been adjust by the admin after reconciliation

// --- roles
bytes32 public constant BOT = keccak256("BOT"); // triggering the delivery
bytes32 public constant DISTRIBUTOR = keccak256("DISTRIBUTOR"); // recharging rewards to VeListaRewardsCourier

/**
* @dev initialize the contract
* @param _rewardsToken address of the rewards token
* @param _admin address of the ADMIN role
* @param _bot address of the BOT role
* @param _distributor address of the DISTRIBUTOR role
* @param _veLista address of the veLista contract
* @param _veListaDistributor address of the veListaDistributor contract
*/
function initialize(
address _rewardsToken,
address _admin,
address _bot,
address _distributor,
address _veLista,
address _veListaDistributor
) external initializer {
__AccessControl_init();
require(_rewardsToken != address(0), "rewardsToken is a zero address");
require(_admin != address(0), "admin is a zero address");
require(_bot != address(0), "bot is a zero address");
require(_veLista != address(0), "veLista is a zero address");
require(_veListaDistributor != address(0), "veListaDistributor is a zero address");
token = IERC20(_rewardsToken);
_setupRole(DEFAULT_ADMIN_ROLE, _admin);
_setupRole(BOT, _bot);
_setupRole(DISTRIBUTOR, _distributor);
veLista = IVeLista(_veLista);
veListaDistributor = IVeListaDistributor(_veListaDistributor);
}

/**
* @dev recharge rewards to VeListaRewardsCourier
* only address with DISTRIBUTOR role can call this function
* @param amount amount of rewards to be recharged
*/
function rechargeRewards(uint256 amount) external onlyRole(DISTRIBUTOR) {

require(amount > 0, "Amount must be greater than 0");
// @dev Note that a week starts from Wed as defined the VeLista contract
// but rewards will be counted from Mon to Sun
// So, rewards for week N+1 will be distributed as follows:
// Part 1: week N-1's Mon 00:00 to Tue 23:59
// Part 2: week N's Wed 00:00 to Sun 23:59
// Finally, week N's rewards will be delivered and distributed at week N+1
uint16 rewardWeek = veLista.getCurrentWeek();
uint256 rewardWeekTimestamp = veLista.startTime() + uint256(rewardWeek) * WEEK;
// actual rewards belongs to rewardWeek + 1
if (block.timestamp >= rewardWeekTimestamp + 5 * DAY) {
rewardWeek += 1;
}
require(!rewardsDeliveredForWeek[rewardWeek], "Rewards already delivered for the week");
// add reward amount
weeklyRewardAmounts[rewardWeek] += amount;
// transfer token to contract
token.safeTransferFrom(msg.sender, address(this), amount);
// broadcast recharge event
emit RewardsRecharged(rewardWeek, amount);
}

/**
* @dev deliver rewards to veListaDistributor
*/
function deliverRewards() external onlyRole(BOT) {
// referring to rechargeRewards(), at this moment week is rewardWeek + 1
// should be called at 00:00
uint16 week = veLista.getCurrentWeek() - 1;
// check if its delivered
require(!rewardsDeliveredForWeek[week], "Rewards already delivered for the week");
rewardsDeliveredForWeek[week] = true;
// has non zero amount to deliver
if (weeklyRewardAmounts[week] > 0) {
// use token array as per veListaDistributor interface
IVeListaDistributor.TokenAmount[] memory tokens = new IVeListaDistributor.TokenAmount[](1);
tokens[0] = IVeListaDistributor.TokenAmount(address(token), weeklyRewardAmounts[week]);
// approve token
token.approve(address(veListaDistributor), weeklyRewardAmounts[week]);
// send to veListaDistributor
veListaDistributor.depositNewReward(week, tokens);
// broadcast delivery event
emit RewardsDelivered(week, weeklyRewardAmounts[week]);
}
}


/**
* @dev adjust the latest rewards
* @param amount amount to adjust
*/
function adjustRewards(uint16 week, int256 amount) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(amount != 0, "Amount must be non-zero");
require(!rewardsDeliveredForWeek[week], "Rewards already delivered for the week");
// adjust amount and transfer token
if (amount > 0) {
token.safeTransferFrom(msg.sender, address(this), uint256(amount));
weeklyRewardAmounts[week] += uint256(amount);
} else {
token.safeTransfer(msg.sender, uint256(-amount));
weeklyRewardAmounts[week] -= uint256(-amount);
}
emit RewardsAdjusted(week, amount);
}

}
12 changes: 11 additions & 1 deletion contracts/interfaces/IVeListaDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@ pragma solidity ^0.8.10;
import "./IVeLista.sol";

interface IVeListaDistributor {

struct TokenAmount {
address token;
uint256 amount;
}

function veLista() external view returns (IVeLista);

function getTokenClaimable(address _account, address _token, uint16 toWeek) external view returns (uint256, uint16);

function claimForCompound(address _account, address _lista, uint16 toWeek) external returns (uint256 _claimedAmount);
}

function rewardTokenIndexes(address _token) external view returns (uint8);

function depositNewReward(uint16 _week, TokenAmount[] memory _tokens) external;
}
39 changes: 39 additions & 0 deletions scripts/deploy_veListaRewardsCourier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { deployProxy } from "./tasks";
import hre from "hardhat";
async function main() {
const contractName = "VeListaRewardsCourier";
const signers = await hre.ethers.getSigners();
const deployer = signers[0].address;

let admin, bot, veListaDistributor;

if (hre.network.name === "bsc") {
admin = deployer;
bot = "";
veListaDistributor = "0x45aAc046Bc656991c52cf25E783c6942425ce40C";
} else if (hre.network.name === "bscTestnet") {
admin = deployer;
bot = deployer;
veListaDistributor = "0x040037d4c8cb2784d47a75Aa20e751CDB1E8971A";
}
const address = await deployProxy(
hre,
contractName,
admin,
bot,
veListaDistributor
);
console.log(`veListaRewardsCourier deployed to: ${address}`);
// verify contract
await hre.run("verify:verify", {
address,
});
console.log("----- DONE -----");
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
49 changes: 49 additions & 0 deletions scripts/deploy_veListaRewardsCourierV2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { deployProxy } from "./tasks";
import hre from "hardhat";
async function main() {
const contractName = "VeListaRewardsCourierV2";
const signers = await hre.ethers.getSigners();
const deployer = signers[0].address;

let listaToken, admin, bot, distributor, veLista, veListaDistributor;

if (hre.network.name === "bsc") {
listaToken = "0xFceB31A79F71AC9CBDCF853519c1b12D379EdC46";
admin = "";
bot = "";
distributor = "";
veLista = "0xd0C380D31DB43CD291E2bbE2Da2fD6dc877b87b3";
veListaDistributor = "0x45aAc046Bc656991c52cf25E783c6942425ce40C";
} else if (hre.network.name === "bscTestnet") {
listaToken = "0x90b94D605E069569Adf33C0e73E26a83637c94B1"
admin = deployer;
bot = deployer;
distributor = deployer;
veLista = "0x79B3286c318bdf7511A59dcf9a2E88882064eCbA";
veListaDistributor = "0x040037d4c8cb2784d47a75Aa20e751CDB1E8971A";
}

const address = await deployProxy(
hre,
contractName,
listaToken,
admin,
bot,
distributor,
veLista,
veListaDistributor,
);
console.log(`veListaRewardsCourierV2 deployed to: ${address}`);
// verify contract
await hre.run("verify:verify", {
address,
});
console.log("----- DONE -----");
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Loading