Skip to content

Commit

Permalink
feat: cross chain reward distribution base deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
gathonwar committed Sep 27, 2023
1 parent 947ee6b commit 216b573
Show file tree
Hide file tree
Showing 167 changed files with 45,959 additions and 51,100 deletions.
133 changes: 133 additions & 0 deletions contracts/BaseRewardManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';

import './interfaces/AerodromeInterfaces.sol';
import './interfaces/SonneMerkleDistributorInterfaces.sol';
import './libraries/SafeToken.sol';

struct OpPoolState {
bytes32 sMerkleRoot;
bytes32 uMerkleRoot;
uint256 sSupply;
uint256 uSupply;
uint256 blockNumber;
uint256 withdrawUnlockTime;
}

contract BaseRewardManager is AccessControlUpgradeable {
using SafeToken for address;

bytes32 public constant MANAGER_ROLE = keccak256('MANAGER_ROLE');

ISonneMerkleDistributor public constant sSonneDistributor =
ISonneMerkleDistributor(0x071B00ef6AD31Fb716955a1d70Ab26D47290120a);
ISonneMerkleDistributor public constant uUsdcDistributor =
ISonneMerkleDistributor(0x6cbDABf17c4634fA4F9A0C4A5e73Fd869F9a9be8);
ISonneMerkleDistributor public constant sAeroDistributor =
ISonneMerkleDistributor(0xA1918c958963DC7E710F5736A7fbCa7C32Bf501d);
ISonneMerkleDistributor public constant uAeroDistributor =
ISonneMerkleDistributor(0xcA31a8173AB19c3fa006D0Adc2883882F189797b);

address public constant sonne = 0x22a2488fE295047Ba13BD8cCCdBC8361DBD8cf7c;
address public constant usdc = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;
address public constant usdbc = 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA;
address public constant aero = 0x940181a94A35A4569E4529A3CDfB74e38FD98631;

IRouter public constant router = IRouter(0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43);

function initialize() public initializer {
__AccessControl_init();

_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
_grantRole(MANAGER_ROLE, _msgSender());
}

function addRewards(
uint256 usdcAmount,
uint256 aeroAmount,
OpPoolState calldata state
) public onlyRole(MANAGER_ROLE) {
uint256 totalSupply = state.sSupply + state.uSupply;

// add velo
if (aeroAmount > 0) {
pullTokenInternal(aero, aeroAmount);
uint256 amount = aero.balanceOf(address(this));

uint256 uAmount = (amount * state.uSupply) / totalSupply;
uint256 sAmount = amount - uAmount;

// add for uSonne
aero.safeApprove(address(uAeroDistributor), uAmount);
uAeroDistributor.addReward(
uAmount,
state.uMerkleRoot,
state.blockNumber,
state.withdrawUnlockTime,
state.sSupply
);

// add for sSonne
aero.safeApprove(address(sAeroDistributor), sAmount);
sAeroDistributor.addReward(
sAmount,
state.sMerkleRoot,
state.blockNumber,
state.withdrawUnlockTime,
state.sSupply
);
}

// add usdc
if (usdcAmount > 0) {
pullTokenInternal(usdc, usdcAmount);
uint256 amount = usdc.balanceOf(address(this));

uint256 uUSDCAmount = (amount * state.uSupply) / totalSupply;
uint256 sUSDCAmount = amount - uUSDCAmount;

// add usdc for uSonne
usdc.safeApprove(address(uUsdcDistributor), uUSDCAmount);
uUsdcDistributor.addReward(
uUSDCAmount,
state.uMerkleRoot,
state.blockNumber,
state.withdrawUnlockTime,
state.uSupply
);

// swap usdc to sonne
swapUSDCtoSonneInternal(sUSDCAmount);

// add sonne for sSonne
uint256 sonneAmount = sonne.balanceOf(address(this));
sonne.safeApprove(address(sSonneDistributor), sonneAmount);
sSonneDistributor.addReward(
sonneAmount,
state.sMerkleRoot,
state.blockNumber,
state.withdrawUnlockTime,
state.sSupply
);
}
}

function pullTokenInternal(address token, uint256 amount) internal {
if (amount == type(uint256).max) {
amount = token.balanceOf(msg.sender);
}

token.safeTransferFrom(msg.sender, address(this), amount);
}

function swapUSDCtoSonneInternal(uint256 usdcAmount) internal {
IRouter.Route[] memory path = new IRouter.Route[](2);
path[0] = IRouter.Route({from: usdc, to: usdbc, stable: true, factory: address(0)});
path[1] = IRouter.Route({from: usdbc, to: sonne, stable: false, factory: address(0)});

usdc.safeApprove(address(router), usdcAmount);
router.swapExactTokensForTokens(usdcAmount, 0, path, address(this), block.timestamp);
}
}
129 changes: 129 additions & 0 deletions contracts/ReserveManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';

import './interfaces/LendingInterfaces.sol';
import './interfaces/VelodromeInterfaces.sol';
import './libraries/SafeToken.sol';

import './RewardManager.sol';

contract ReserveManager is AccessControlUpgradeable {
using SafeToken for address;

bytes32 public constant DISTRIBUTOR_ROLE = keccak256('DISTRIBUTOR_ROLE');

/* Tokens */
address public constant usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607;
address public constant velo = 0x9560e827aF36c94D2Ac33a39bCE1Fe78631088Db;

/* OpenOcean */
address public constant OORouter = 0x6352a56caadC4F1E25CD6c75970Fa768A3304e64;

/* Velodrome */
address public constant voter = 0x09236cfF45047DBee6B921e00704bed6D6B8Cf7e;

/* Distribution */
address public constant rewardManager = 0xC5ba10B609E8500c04884e1bcfc935B2c22654cd;
address public constant sonneTimelock = 0x5b22BD2fC485afe2DEAf1Ac9e2fAd316dDE163B0;

function initialize() public initializer {
__AccessControl_init();

_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/* Guarded Distribution Functions */
function distributeReserves(
IMarket usdcMarket,
uint56 usdcAmount,
IMarket[] calldata markets,
uint256[] calldata amounts,
bytes[] calldata swapQuoteData
) external onlyRole(DISTRIBUTOR_ROLE) {
require(
markets.length == amounts.length && markets.length == swapQuoteData.length,
'ReserveManager: INVALID_INPUT'
);

reduceReserveInternal(usdcMarket, usdcAmount);

for (uint256 i = 0; i < markets.length; i++) {
reduceReserveInternal(markets[i], amounts[i]);
address underlying = markets[i].underlying();
swapToBaseInternal(underlying, amounts[i], swapQuoteData[i]);
}

uint256 distAmount = usdc.myBalance();

usdc.safeApprove(rewardManager, distAmount);
RewardManager(rewardManager).addRewards(distAmount, 0, 0);
}

function distributeVelo(address pair) external onlyRole(DISTRIBUTOR_ROLE) {
claimVeloInternal(pair);

uint256 distAmount = velo.myBalance();

velo.safeApprove(rewardManager, distAmount);
RewardManager(rewardManager).addRewards(0, distAmount, 0);
}

/* Guarded Velodrome Management Function */
function _stakeLP(address pair, uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (amount == type(uint256).max) {
amount = pair.balanceOf(msg.sender);
}

pair.safeTransferFrom(msg.sender, address(this), amount);

stakeLPInternal(pair);
}

function _unstakeLP(address pair, address to) public onlyRole(DEFAULT_ADMIN_ROLE) {
unstakeLPInternal(pair);

uint256 amount = pair.myBalance();
pair.safeTransfer(to, amount);
}

/* Internal Market Management Functions */
function reduceReserveInternal(IMarket market, uint256 amount) internal {
market.accrueInterest();

require(market.getCash() >= amount, 'ReserveManager: NOT_ENOUGH_CASH');
require(market.totalReserves() >= amount, 'ReserveManager: NOT_ENOUGH_RESERVE');

ISonneTimelock(sonneTimelock)._reduceReserves(address(market), amount, address(this));
}

function swapToBaseInternal(address underlying, uint256 amount, bytes memory swapQuoteDatum) internal {
underlying.safeApprove(OORouter, amount);

(bool success, bytes memory result) = OORouter.call{value: 0}(swapQuoteDatum);
require(success, 'ReserveManager: OO_API_SWAP_FAILED');
}

/* Internal Velodrome Management Functions */
function stakeLPInternal(address pair) internal {
address gauge = IVoter(voter).gauges(pair);

uint256 amountPair = pair.myBalance();
pair.safeApprove(gauge, amountPair);
IGauge(gauge).deposit(amountPair, 0);
}

function unstakeLPInternal(address pair) internal {
address gauge = IVoter(voter).gauges(pair);
IGauge(gauge).withdrawAll();
}

function claimVeloInternal(address pair) internal {
address[] memory tokens = new address[](1);
tokens[0] = velo;

address gauge = IVoter(voter).gauges(pair);
IGauge(gauge).getReward(address(this), tokens);
}
}
104 changes: 104 additions & 0 deletions contracts/RewardManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';

import './interfaces/VelodromeInterfaces.sol';
import './libraries/SafeToken.sol';

import './StakedDistributor.sol';

contract RewardManager is OwnableUpgradeable {
using SafeToken for address;

StakedDistributor public constant sSonne = StakedDistributor(0xDC05d85069Dc4ABa65954008ff99f2D73FF12618);
StakedDistributor public constant uSonne = StakedDistributor(0x41279e29586EB20f9a4f65e031Af09fced171166);

address public constant sonne = 0x1DB2466d9F5e10D7090E7152B68d62703a2245F0;
address public constant usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607;
address public constant velo = 0x9560e827aF36c94D2Ac33a39bCE1Fe78631088Db;
address public constant op = 0x4200000000000000000000000000000000000042;

IRouter public constant router = IRouter(0x9c12939390052919aF3155f41Bf4160Fd3666A6f);

function initialize() public initializer {
__Ownable_init();
}

function addRewards(uint256 usdcAmount, uint256 veloAmount, uint256 opAmount) public {
// calculate supplies
uint256 sSupply = sSonne.totalSupply();
uint256 uSupply = uSonne.totalSupply();
uint256 totalSupply = sSupply + uSupply;

// add velo
if (veloAmount > 0) {
pullTokenInternal(velo, veloAmount);
addTokenRewardInternal(velo, uSupply, totalSupply);
}

// add op
if (opAmount > 0) {
pullTokenInternal(op, opAmount);
addTokenRewardInternal(op, uSupply, totalSupply);
}

// add usdc
if (usdcAmount > 0) {
pullTokenInternal(usdc, usdcAmount);
addUSDCRewardInternal(uSupply, totalSupply);
}
}

function pullTokenInternal(address token, uint256 amount) internal {
if (amount == type(uint256).max) {
amount = token.balanceOf(msg.sender);
}

token.safeTransferFrom(msg.sender, address(this), amount);
}

function addTokenRewardInternal(address token, uint256 uSupply, uint256 totalSupply) internal {
uint256 amount = token.balanceOf(address(this));
if (amount == 0) return;

uint256 uAmount = (amount * uSupply) / totalSupply;
uint256 sAmount = amount - uAmount;

// add to uSonne
token.safeApprove(address(uSonne), uAmount);
uSonne.addReward(token, uAmount);

// add to sSonne
token.safeApprove(address(sSonne), sAmount);
sSonne.addReward(token, sAmount);
}

function addUSDCRewardInternal(uint256 uSupply, uint256 totalSupply) internal {
uint256 amount = usdc.balanceOf(address(this));
if (amount == 0) return;

uint256 uUSDCAmount = (amount * uSupply) / totalSupply;
uint256 sUSDCAmount = amount - uUSDCAmount;

// add to uSonne
usdc.safeApprove(address(uSonne), uUSDCAmount);
uSonne.addReward(usdc, uUSDCAmount);

// swap usdc to sonne
swapUSDCtoSonneInternal(sUSDCAmount);

// add to sMare
uint256 sonneAmount = sonne.balanceOf(address(this));
sonne.safeApprove(address(sSonne), sonneAmount);
sSonne.addReward(sonne, sonneAmount);
}

function swapUSDCtoSonneInternal(uint256 usdcAmount) internal {
IRouter.route[] memory path = new IRouter.route[](1);
path[0] = IRouter.route({from: usdc, to: sonne, stable: false});

usdc.safeApprove(address(router), usdcAmount);
router.swapExactTokensForTokens(usdcAmount, 0, path, address(this), block.timestamp);
}
}
Loading

0 comments on commit 216b573

Please sign in to comment.