Skip to content

Commit

Permalink
feat: start of pounder
Browse files Browse the repository at this point in the history
  • Loading branch information
0xtekgrinder committed Nov 13, 2023
1 parent 0614b1f commit 15f77b4
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "contracts/lib/Warlord"]
path = contracts/lib/Warlord
url = https://github.com/0xtekgrinder/Warlord
[submodule "contracts/lib/solady"]
path = contracts/lib/solady
url = https://github.com/Vectorized/solady
10 changes: 10 additions & 0 deletions contracts/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"solidity.packageDefaultDependenciesContractsDirectory": "src",
"solidity.packageDefaultDependenciesDirectory": "lib",
"solidity.compileUsingRemoteVersion": "v0.8.20",
"[solidity]": {
"editor.defaultFormatter": "JuanBlanco.solidity"
},
"solidity.formatter": "forge",
"search.exclude": { "lib": true },
}
1 change: 1 addition & 0 deletions contracts/lib/solady
Submodule solady added at 16c0dd
4 changes: 3 additions & 1 deletion contracts/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts
solmate/=lib/solmate/src/
warlord/=lib/Warlord/src/
solgen/=lib/solidity-generators/src/
solgen/=lib/solidity-generators/src/
solady/=lib/solady/src/
src=src/
106 changes: 106 additions & 0 deletions contracts/src/abstracts/AIncentiveClaimer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//SPDX-License-Identifier GPL-3.0-or-later
pragma solidity 0.8.20;

import "interfaces/IIncentivizedLocker.sol";
import {
IQuestDistributor,
IDelegationDistributor,
IVotiumDistributor,
IHiddenHandDistributor
} from "warlord/interfaces/external/incentives/IIncentivesDistributors.sol";
import { Errors } from "utils/Errors.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { ERC20 } from "solady/tokens/ERC20.sol";
import { ReentrancyGuard } from "solmate/utils/ReentrancyGuard.sol";

/**
* @title Incentivized Locker contract
* @author Paladin
* @notice Locker contract capable of claiming vote rewards from different sources
*/
abstract contract AIncentiveClaimer is IIncentivizedLocker, ReentrancyGuard {
/**
* @notice Claims voting rewards from Quest
* @param distributor Address of the contract distributing the rewards
* @param questID ID of the Quest to claim rewards from
* @param period Timestamp of the Quest period to claim
* @param index Index in the Merkle Tree
* @param account Address claiming the rewards
* @param amount Amount to claim
* @param merkleProof Merkle Proofs for the claim
*/
function claimQuestRewards(
address distributor,
uint256 questID,
uint256 period,
uint256 index,
address account,
uint256 amount,
bytes32[] calldata merkleProof
) external nonReentrant {
IQuestDistributor _distributor = IQuestDistributor(distributor);
ERC20 _token = ERC20(_distributor.questRewardToken(questID));

_distributor.claim(questID, period, index, account, amount, merkleProof);
}

/**
* @notice Claims voting rewards from the Paladin Delegation address
* @param distributor Address of the contract distributing the rewards
* @param token Address of the reward token to claim
* @param index Index in the Merkle Tree
* @param account Address claiming the rewards
* @param amount Amount to claim
* @param merkleProof Merkle Proofs for the claim
*/
function claimDelegationRewards(
address distributor,
address token,
uint256 index,
address account,
uint256 amount,
bytes32[] calldata merkleProof
) external nonReentrant {
IDelegationDistributor(distributor).claim(token, index, account, amount, merkleProof);
}

// TODO mayybe delete this function for liquis
/**
* @notice Claims voting rewards from Votium
* @param distributor Address of the contract distributing the rewards
* @param token Address of the reward token to claim
* @param index Index in the Merkle Tree
* @param account Address claiming the rewards
* @param amount Amount to claim
* @param merkleProof Merkle Proofs for the claim
*/
function claimVotiumRewards(
address distributor,
address token,
uint256 index,
address account,
uint256 amount,
bytes32[] calldata merkleProof
) external nonReentrant {
IVotiumDistributor(distributor).claim(token, index, account, amount, merkleProof);
}

/**
* @notice Claims voting rewards from HiddenHand
* @param distributor Address of the contract distributing the rewards
* @param claimParams Parameters for claims
*/
function claimHiddenHandRewards(address distributor, IHiddenHandDistributor.Claim[] calldata claimParams)
external
nonReentrant
{
require(claimParams.length == 1);

IHiddenHandDistributor _distributor = IHiddenHandDistributor(distributor);
address token = _distributor.rewards(claimParams[0].identifier).token;

uint256 initialBalance = ERC20(token).balanceOf(address(this));

_distributor.claim(claimParams);
}
}
241 changes: 241 additions & 0 deletions contracts/src/abstracts/APounder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.20;

import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { AIncentiveClaimer } from "./AIncentiveClaimer.sol";
import { AProtocolClaimer } from "./AProtocolClaimer.sol";
import { Ownable } from "solady/auth/Ownable.sol";
import { ERC20 } from "solady/tokens/ERC20.sol";

abstract contract APounder is ERC20, AIncentiveClaimer, AProtocolClaimer, Ownable {
address public asset;
address public swapper;
/**
* @notice Address of the voting power delegate
*/
address public delegate;

// The amount of CVX that needs to remain unlocked for redemptions
uint256 public outstandingRedemptions;

// The amount of new CVX deposits that is awaiting lock
uint256 public pendingLocks;

/**
* @notice Event emitted when the delegate is updated
*/
event SetDelegate(address newDelegatee);

constructor(address initialSwapper, address definitiveAsset) {
asset = definitiveAsset;
swapper = initialSwapper;
}

/**
* @dev Updates the Delegatee & delegates the voting power
* @param _delegatee Address of the delegatee
*/
function _setDelegate(address _delegatee) internal virtual;

/**
* @notice Updates the Delegatee & delegates the voting power
* @param _delegatee Address of the delegatee
*/
function setDelegate(address _delegatee) external onlyOwner {
delegate = _delegatee;
_setDelegate(_delegatee);

emit SetDelegate(_delegatee);
}

function _lock(uint256 amount) internal virtual;

/**
* @dev Locks the tokens in the vlToken contract
* @param amount Amount to lock
*/
function lock() external {
_processUnlock();

uint256 balance = asset.balanceOf(address(this));
bool balanceGreaterThanRedemptions = balance > outstandingRedemptions;

// Lock CVX if the balance is greater than outstanding redemptions or if there are pending locks
if (balanceGreaterThanRedemptions || pendingLocks != 0) {
uint256 balanceRedemptionsDifference = balanceGreaterThanRedemptions
? balance - outstandingRedemptions
: 0;

// Lock amount is the greater of the two: balanceRedemptionsDifference or pendingLocks
// balanceRedemptionsDifference is greater if there is unlocked CVX that isn't reserved for redemptions + deposits
// pendingLocks is greater if there are more new deposits than unlocked CVX that is reserved for redemptions
_lock(
balanceRedemptionsDifference > pendingLocks
? balanceRedemptionsDifference
: pendingLocks
);

pendingLocks = 0;
}
}

/**
* @dev Processes the unlock of tokens
*/
function _processUnlock() internal virtual;

function deposit(uint256 amount, address receiver) external {
if (amount == 0) revert ZeroAmount();
if (receiver == address(0)) revert ZeroAddress();

pendingLocks += amount;

// Transfer the tokens to the contract
SafeTransferLib.safeTransferFrom(asset, msg.sender, address(this), amount);

_mint(receiver, amount);
}

function _initiateRedemption(
ICvxLocker.LockedBalance memory lockData,
Futures f,
uint256 assets,
address receiver,
uint256 feeMin,
uint256 feeMax
) internal returns (uint256 feeAmount) {
if (assets == 0) revert ZeroAmount();
if (receiver == address(0)) revert ZeroAddress();

uint256 unlockTime = lockData.unlockTime;

// Used for calculating the fee and conditionally adding a round
uint256 waitTime = unlockTime - block.timestamp;

if (feeMax != 0) {
uint256 feePercent = feeMax -
(((feeMax - feeMin) * waitTime) / MAX_REDEMPTION_TIME);

feeAmount = (assets * feePercent) / FEE_DENOMINATOR;
}

uint256 postFeeAmount = assets - feeAmount;

// Increment redemptions for this unlockTime to prevent over-redeeming
redemptions[unlockTime] += postFeeAmount;

// Check if there is any sufficient allowance after factoring in redemptions by others
if (redemptions[unlockTime] > lockData.amount)
revert InsufficientRedemptionAllowance();

// Track assets that needs to remain unlocked for redemptions
outstandingRedemptions += postFeeAmount;

// Mint upxCVX with unlockTime as the id - validates `to`
upxCvx.mint(receiver, unlockTime, postFeeAmount, UNUSED_1155_DATA);

return feeAmount;
}

function initiateRedemptions(
uint256[] calldata lockIndexes,
Futures f,
uint256[] calldata assets,
address receiver
) external whenNotPaused nonReentrant {
uint256 lockLen = lockIndexes.length;

if (lockLen == 0) revert EmptyArray();
if (lockLen != assets.length) revert MismatchedArrayLengths();

emit InitiateRedemptions(lockIndexes, f, assets, receiver);

(, , , ICvxLocker.LockedBalance[] memory lockData) = cvxLocker
.lockedBalances(address(this));
uint256 totalAssets;
uint256 feeAmount;
uint256 feeMin = fees[Fees.RedemptionMin];
uint256 feeMax = fees[Fees.RedemptionMax];

for (uint256 i; i < lockLen; ++i) {
totalAssets += assets[i];
feeAmount += _initiateRedemption(
lockData[lockIndexes[i]],
f,
assets[i],
receiver,
feeMin,
feeMax
);
}

// Burn pxCVX - reverts if sender balance is insufficient
pxCvx.burn(msg.sender, totalAssets - feeAmount);

if (feeAmount != 0) {
// Allow PirexFees to distribute fees directly from sender
pxCvx.operatorApprove(msg.sender, address(pirexFees), feeAmount);

// Distribute fees
pirexFees.distributeFees(msg.sender, address(pxCvx), feeAmount);
}
}

function _redeem(
uint256[] calldata unlockTimes,
uint256[] calldata assets,
address receiver,
bool legacy
) internal {
uint256 unlockLen = unlockTimes.length;

if (unlockLen == 0) revert EmptyArray();
if (unlockLen != assets.length) revert MismatchedArrayLengths();
if (receiver == address(0)) revert ZeroAddress();

emit Redeem(unlockTimes, assets, receiver, legacy);

uint256 totalAssets;

for (uint256 i; i < unlockLen; ++i) {
uint256 asset = assets[i];

if (!legacy && unlockTimes[i] > block.timestamp)
revert BeforeUnlock();
if (asset == 0) revert ZeroAmount();

totalAssets += asset;
}

// Perform unlocking and locking procedure to ensure enough CVX is available
if (!legacy) {
_lock();
}

// Subtract redemption amount from outstanding CVX amount
outstandingRedemptions -= totalAssets;

// Reverts if sender has an insufficient upxCVX balance for any `unlockTime` id
upxCvx.burnBatch(msg.sender, unlockTimes, assets);

// Validates `to`
asset.safeTransfer(receiver, totalAssets);
}

function redeem(
uint256[] calldata unlockTimes,
uint256[] calldata assets,
address receiver
) external whenNotPaused nonReentrant {
if (upxCvxDeprecated) revert RedeemClosed();

_redeem(unlockTimes, assets, receiver, false);
}

/**
@notice Manually unlock CVX in the case of a mass unlock
*/
function unlock() external onlyOwner {
_processUnlock();
}
}
19 changes: 19 additions & 0 deletions contracts/src/abstracts/AProtocolClaimer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.20;

abstract contract AProtocolClaimer {
event ProtocolRewardsHarvest();

/**
* @dev Harvest rewards & send them to the Controller
*/
function _harvest() internal virtual;

/**
* @notice Harvest rewards
*/
function harvest() external {
emit ProtocolRewardsHarvest();
_harvest();
}
}
Loading

0 comments on commit 15f77b4

Please sign in to comment.