Skip to content
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
1,864 changes: 1,864 additions & 0 deletions .openzeppelin/unknown-250.json

Large diffs are not rendered by default.

76 changes: 66 additions & 10 deletions contracts/MagicatsHandlerUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: AGPLv3
// SPDX-License-Identifier: BSL1.1

pragma solidity ^0.8.9;

Expand Down Expand Up @@ -75,6 +75,20 @@ contract MagicatsHandlerUpgradeable is
// address of the strategy the magicatsHandler hooks into
address public strategy;

/**
* @dev Variables for off-chain bot
* {lastAllocationTimestamp} - The block.timestamp of the most recent call to updateStakedMagicats
* allowing the bot not to make unecessary re-allocations
*/
uint256 public lastAllocationTimestamp;

// catID -> savedPendingRewards
mapping(uint256 => uint256) public savedRewards;

bool shouldHarvest;

uint256 timeBetweenProcesses;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}

Expand Down Expand Up @@ -124,6 +138,7 @@ contract MagicatsHandlerUpgradeable is
_safeMint(msg.sender, magicatsIds[i]);
}
_updateStakedMagicats(IStrategy(strategy).currentPoolId(), magicatsIds, new uint256[](0));
shouldHarvest = true;
}

/***
Expand Down Expand Up @@ -157,6 +172,7 @@ contract MagicatsHandlerUpgradeable is

IERC721Upgradeable(MAGICATS).transferFrom(currentOwner, msg.sender, magicatsIds[i]);
}
shouldHarvest = true;
}

/***
Expand Down Expand Up @@ -187,10 +203,15 @@ contract MagicatsHandlerUpgradeable is
function updateStakedMagicats(
uint256 poolID,
uint256[] memory IDsToStake,
uint256[] memory IDsToUnstake
uint256[] memory IDsToUnstake,
bool allocationCompleted
) external {
_atLeastRole(KEEPER);
_updateStakedMagicats(poolID, IDsToStake, IDsToUnstake);

if (allocationCompleted) {
lastAllocationTimestamp = block.timestamp;
}
}

/***
Expand All @@ -206,13 +227,23 @@ contract MagicatsHandlerUpgradeable is
* writes to harvest log and allows for reward claims
*/
function processRewards() external {
Harvest memory latestHarvest;
uint256 beforeAmount = IERC20Upgradeable(vault).balanceOf(address(this));
_redepositGains();
latestHarvest.amount = IERC20Upgradeable(vault).balanceOf(address(this)) - beforeAmount;
latestHarvest.totalManaPoints = totalMp;
latestHarvest.timestamp = block.timestamp;
harvests.push(latestHarvest);

if(_passedTimeToProcess() || shouldHarvest)
{
Harvest memory latestHarvest;
uint256 beforeAmount = IERC20Upgradeable(vault).balanceOf(address(this));
_redepositGains();
latestHarvest.amount = IERC20Upgradeable(vault).balanceOf(address(this)) - beforeAmount;
latestHarvest.totalManaPoints = totalMp;
latestHarvest.timestamp = block.timestamp;
harvests.push(latestHarvest);
shouldHarvest = false;
}
}

function _passedTimeToProcess() internal view returns (bool) {
uint256 harvestsLength = harvests.length;
return block.timestamp - harvests[harvestsLength - 1].timestamp >= timeBetweenProcesses;
}

/***
Expand All @@ -230,7 +261,7 @@ contract MagicatsHandlerUpgradeable is
unclaimedReward += magicatShare;
}

return unclaimedReward;
return unclaimedReward + savedRewards[id];
}

/***
Expand All @@ -250,6 +281,7 @@ contract MagicatsHandlerUpgradeable is
*/
function _claimRewards(uint256 _id) internal {
uint256 owed = getMagicatReward(_id);
delete savedRewards[_id];
idToMagicat[_id].lastHarvestClaimed = harvests.length;
IERC20(vault).transfer(msg.sender, owed);
}
Expand Down Expand Up @@ -417,4 +449,28 @@ contract MagicatsHandlerUpgradeable is
require(upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp);
clearUpgradeCooldown();
}

function partialClaimRewards(uint256 magicatID, uint256 _harvests) public {
uint256 totalHarvests = harvests.length;
Magicat storage cat = idToMagicat[magicatID];
uint256 magicatShare;
uint256 unclaimedReward;
uint256 lastHarvestClaimed = cat.lastHarvestClaimed;
uint256 lastToClaim = lastHarvestClaimed + _harvests;
if(lastToClaim > totalHarvests){
lastToClaim = totalHarvests;
}
for(uint256 i = lastHarvestClaimed; i < lastToClaim; i = _uncheckedInc(i)){
magicatShare = (harvests[i].amount * cat.manapoints) / harvests[i].totalManaPoints;
unclaimedReward += magicatShare;
}
savedRewards[magicatID] += unclaimedReward;
cat.lastHarvestClaimed = lastToClaim;
}

function setTimeBetweenProcesses(uint256 newTime) external {
_atLeastRole(STRATEGIST);
require(newTime <= 1 days, "time between harvests is too long");
timeBetweenProcesses = newTime;
}
}
79 changes: 50 additions & 29 deletions contracts/ReaperAutoCompoundXBoo.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: AGPLv3
// SPDX-License-Identifier: BSL1.1

pragma solidity 0.8.9;

Expand Down Expand Up @@ -87,6 +87,19 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
//and the address of the contract that can handle it according the pre-set API
mapping(uint256 => RewardHandler) public idToSpecialHandler;

/**
* @dev Variables for off-chain bot
* {lastAllocationTimestamp} - The block.timestamp of the most recent call to setXBooAllocations
* allowing the bot not to make unecessary re-allocations
*/
uint256 public lastAllocationTimestamp;

/**
* @dev Variable for contract upgrade
* {v2UpgradeCompleted} - If the one time upgrade function has run or not
*/
bool public v2UpgradeCompleted;

/**
* @dev Initializes the strategy. Sets parameters, saves routes, and gives allowances.
* @notice see documentation for each variable above its respective declaration.
Expand Down Expand Up @@ -150,11 +163,10 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
if (booBalance < _amount) {
uint256 xBooToWithdraw = XBOO.BOOForxBOO(_amount - booBalance);
uint256[] memory depositedPoolIDs = depositedPools.values();
uint256 depositPoolsLength = depositedPoolIDs.length;

uint256 withdrawnAmount = 0;
uint256 withdrawnAmount;
uint256 amountToWithdraw;
for (uint256 i = 0; i < depositPoolsLength; i = _uncheckedInc(i)) {
for (uint256 i = 0; i < depositedPoolIDs.length; i = _uncheckedInc(i)) {
uint256 currentDepositedPoolId = depositedPoolIDs[i];
amountToWithdraw = _getMin(poolXBOOBalance[currentDepositedPoolId], _amount - withdrawnAmount);
_aceLabWithdraw(currentDepositedPoolId, amountToWithdraw);
Expand Down Expand Up @@ -183,8 +195,9 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
*/
function _aceLabWithdraw(uint256 _poolId, uint256 _XBOOAmount) internal {
totalPoolBalance -= _XBOOAmount;
poolXBOOBalance[_poolId] -= _XBOOAmount;
if (poolXBOOBalance[_poolId] == 0 && depositedPools.contains(_poolId)) {
uint256 xBOOBalance = poolXBOOBalance[_poolId] - _XBOOAmount;
poolXBOOBalance[_poolId] = xBOOBalance;
if (xBOOBalance == 0 && depositedPools.contains(_poolId)) {
depositedPools.remove(_poolId);
}
_writeCatDebt(_poolId);
Expand All @@ -205,21 +218,21 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
uint256[] calldata depositAmounts
) external {
_atLeastRole(KEEPER);
uint256 depositPoolsLength = depositPoolIds.length;
uint256 withdrawPoolsLength = withdrawPoolIds.length;

for (uint256 i = 0; i < withdrawPoolsLength; i = _uncheckedInc(i)) {
for (uint256 i = 0; i < withdrawPoolIds.length; i = _uncheckedInc(i)) {
_aceLabWithdraw(withdrawPoolIds[i], withdrawAmounts[i]);
}

for (uint256 i = 0; i < depositPoolsLength; i = _uncheckedInc(i)) {
for (uint256 i = 0; i < depositPoolIds.length; i = _uncheckedInc(i)) {
uint256 XBOOAvailable = IERC20Upgradeable(XBOO).balanceOf(address(this));
if (XBOOAvailable == 0) {
return;
}
uint256 depositAmount = _getMin(XBOOAvailable, depositAmounts[i]);
_aceLabDeposit(depositPoolIds[i], depositAmount);
}

lastAllocationTimestamp = block.timestamp;
}

/**
Expand All @@ -229,10 +242,10 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
* 3. It swaps the {WFTM} token for {BOO} which is deposited into {XBOO}
* 4. It distributes the XBOO using a yield optimization algorithm into various pools.
*/
function _harvestCore() internal override returns (uint256 callerFee) {
function _harvestCore() internal override returns (uint256 feeCharged) {
_claimAllRewards();
uint256 catBoostPercentage = _processRewards();
callerFee = _chargeFees();
feeCharged = _chargeFees();
_swapWFTMToBOO();
if (magicatsHandler != address(0) && catBoostPercentage != 0) {
_payMagicatDepositors(catBoostPercentage);
Expand All @@ -244,8 +257,7 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad

function _claimAllRewards() internal {
uint256[] memory depositedPoolIDs = depositedPools.values();
uint256 depositPoolsLength = depositedPoolIDs.length;
for (uint256 i = 0; i < depositPoolsLength; i = _uncheckedInc(i)) {
for (uint256 i = 0; i < depositedPoolIDs.length; i = _uncheckedInc(i)) {
_aceLabWithdraw(depositedPoolIDs[i], 0);
}
}
Expand All @@ -263,6 +275,7 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
uint256 catBoostPercent;
uint256 catBoostWFTM;
uint256 catBoostTotal;
uint256 catDebt;
uint256 totalHarvest;
address rewardToken;
uint256 activeIndex;
Expand All @@ -276,8 +289,9 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
//leave is pretty standard for xTokens if it does not have leave we will need an external handler
try IBooMirrorWorld(rewardToken).leave(tokenBal) {} catch {}

if (accCatDebt[activeIndex] != 0) {
catBoostPercent = (accCatDebt[activeIndex] * PERCENT_DIVISOR) / tokenBal;
catDebt = accCatDebt[activeIndex];
if (catDebt != 0) {
catBoostPercent = (catDebt * PERCENT_DIVISOR) / tokenBal;
} else {
catBoostPercent = 0;
}
Expand Down Expand Up @@ -328,22 +342,20 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
* callFeeToUser is set as a percentage of the fee,
* as is treasuryFeeToVault
*/
function _chargeFees() internal returns (uint256 callFeeToUser) {
function _chargeFees() internal returns (uint256 feeCharged) {
uint256 WFTMFee = (IERC20Upgradeable(WFTM).balanceOf(address(this)) * totalFee) / PERCENT_DIVISOR;
if (WFTMFee != 0) {
uint256 usdcBalanceBefore = IERC20Upgradeable(USDC).balanceOf(address(this));
IUniswapRouterETH(UNIROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
WFTMFee,
0,
WFTMToUSDCPath,
address(this),
block.timestamp
);
uint256 USDCBal = IERC20Upgradeable(USDC).balanceOf(address(this));
callFeeToUser = (USDCBal * callFee) / PERCENT_DIVISOR;
uint256 treasuryFeeToVault = (USDCBal * treasuryFee) / PERCENT_DIVISOR;
feeCharged = IERC20Upgradeable(USDC).balanceOf(address(this)) - usdcBalanceBefore;

IERC20Upgradeable(USDC).safeTransfer(msg.sender, callFeeToUser);
IERC20Upgradeable(USDC).safeTransfer(treasury, treasuryFeeToVault);
IERC20Upgradeable(USDC).safeTransfer(treasury, feeCharged);
}
}

Expand Down Expand Up @@ -422,17 +434,14 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
*/
function _reclaimWant() internal override {
uint256[] memory depositedPoolIDs = depositedPools.values();
uint256 depositPoolsLength = depositedPoolIDs.length;
uint256 currentDepositedPoolId;
for (uint256 index = 0; index < depositPoolsLength; index = _uncheckedInc(index)) {
for (uint256 index = 0; index < depositedPoolIDs.length; index = _uncheckedInc(index)) {
currentDepositedPoolId = depositedPoolIDs[index];
IAceLab(aceLab).emergencyWithdraw(currentDepositedPoolId);
totalPoolBalance -= poolXBOOBalance[currentDepositedPoolId];
poolXBOOBalance[currentDepositedPoolId] = 0;
depositedPools.remove(currentDepositedPoolId);
}

IMagicatsHandler(magicatsHandler).massUnstakeMagicats();
totalPoolBalance = 0;

uint256 XBOOBalance = XBOO.balanceOf(address(this));
XBOO.leave(XBOOBalance);
Expand Down Expand Up @@ -513,11 +522,11 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
uint256[] memory IDsToUnstake
) public {
_atLeastRole(MAGICATS_HANDLER);
if (IDsToUnstake.length > 0) {
if (IDsToUnstake.length != 0) {
IAceLab(aceLab).withdraw(poolID, 0, IDsToUnstake);
}

if (IDsToStake.length > 0) {
if (IDsToStake.length != 0) {
IAceLab(aceLab).deposit(poolID, 0, IDsToStake);
}
}
Expand Down Expand Up @@ -639,4 +648,16 @@ contract ReaperAutoCompoundXBoov2 is ReaperBaseStrategyv3, IERC721ReceiverUpgrad
min = _b;
}
}

// One-time function to modify state post-V2 upgrade
function completeV2Upgrade(address[] memory _keepers) external {
_atLeastRole(STRATEGIST);
require(!v2UpgradeCompleted, "V2 upgrade has already been completed");
v2UpgradeCompleted = true;
address keeper;
for (uint256 index = 0; index < _keepers.length; index = _uncheckedInc(index)) {
keeper = _keepers[index];
_grantRole(KEEPER, keeper);
}
}
}
3 changes: 2 additions & 1 deletion contracts/abstract/ReaperBaseStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ abstract contract ReaperBaseStrategyv3 is
totalFee = 450;
callFee = 1000;
treasuryFee = 9000;
strategistFee = 2500;
strategistFee = 0;
Comment thread
tess3rac7 marked this conversation as resolved.
securityFee = 0;

vault = _vault;
Expand Down Expand Up @@ -168,6 +168,7 @@ abstract contract ReaperBaseStrategyv3 is
* override _harvestCore() and implement their specific logic in it.
*/
function harvest() external override whenNotPaused returns (uint256 callerFee) {
_atLeastRole(KEEPER);
callerFee = _harvestCore();

if (block.timestamp >= harvestLog[harvestLog.length - 1].timestamp + harvestLogCadence) {
Expand Down
Loading