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

Integrated GP Boost #13

Open
wants to merge 2 commits into
base: main
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
121 changes: 121 additions & 0 deletions Report/optimism-one-shot-output.csv

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions config/ethereum.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"simulation": {
"num_users": "10",
"time_step": "1800",
"time_step": "7200",
"tvl_usd": "10000000",
"verbosity": "1",
"duration_draws": "24",
"duration_draws": "4",
"gp_boost_first_draw": "0",
"gp_boost_per_draw": "0",
"gp_boost_per_draw_last_draw": "0",

"eth_price_usd_per_draw" : [
"3400"
Expand Down Expand Up @@ -38,9 +41,9 @@
},

"gas": {
"start_draw_cost_in_usd": "217600",
"award_draw_cost_in_usd": "95200",
"claim_cost_in_usd": "76000",
"liquidation_cost_in_usd": "150000"
"start_draw_cost_in_usd": "20000",
"award_draw_cost_in_usd": "10000",
"claim_cost_in_usd": "150",
"liquidation_cost_in_usd": "1000"
}
}
11 changes: 7 additions & 4 deletions config/optimism.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@
"simulation": {
"num_users": "2",
"time_step": "1800",
"tvl_usd": "5000000",
"tvl_usd": "4000000",
"verbosity": "1",
"duration_draws": "15",
"duration_draws": "4",
"gp_boost_first_draw": "0",
"gp_boost_per_draw": "0",
"gp_boost_per_draw_last_draw": "30",

"eth_price_usd_per_draw" : [
"3300"
],

"apr_for_each_draw" : [
"30000000000000000"
"100000000000000000"
]
},

"prize_pool": {
"draw_period_seconds": "86400",
"draw_timeout": "30",
"grand_prize_period_draws": "365",
"grand_prize_period_draws": "90",
"number_of_tiers": "4",
"tier_shares": "100",
"canary_shares": "4",
Expand Down
78 changes: 46 additions & 32 deletions src/agent/ClaimerAgent.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Vm } from "forge-std/Vm.sol";
import { Claimer } from "pt-v5-claimer/Claimer.sol";
import { PrizePool } from "pt-v5-prize-pool/PrizePool.sol";
import { PrizeVault } from "pt-v5-vault/PrizeVault.sol";
import { IClaimable } from "pt-v5-claimable-interface/interfaces/IClaimable.sol";

import { SingleChainEnvironment } from "../environment/SingleChainEnvironment.sol";
import { Config } from "../utils/Config.sol";
Expand All @@ -20,7 +21,8 @@ contract ClaimerAgent is Utils {

Claimer public claimer;
PrizePool public prizePool;
PrizeVault public vault;
address public vault;
address[] users;

struct Prize {
uint8 tier;
Expand All @@ -47,13 +49,14 @@ contract ClaimerAgent is Utils {

uint logVerbosity;

constructor(SingleChainEnvironment _env, PrizeVault _vault) {
constructor(SingleChainEnvironment _env, address _vault, address[] memory _users) {
env = _env;
logVerbosity = _env.config().simulation().verbosity;

claimer = env.claimer();
prizePool = env.prizePool();
vault = _vault;
users = _users;

claimerCsvFile = string.concat(vm.projectRoot(), "/data/claimerOut-", vm.toString(address(_vault)), ".csv");
initOutputFileCsv(claimerCsvFile, claimerCsvColumns);
Expand Down Expand Up @@ -121,37 +124,44 @@ contract ClaimerAgent is Utils {
}
}

uint32 maxPrizesPerLiquidity = uint32(prizePool.getTierRemainingLiquidity(tier) / prizeSize);

if (targetClaimCount > maxPrizesPerLiquidity) {
drawTierInsufficientLiquidityPrizeCounts[drawId][tier] =
targetClaimCount -
maxPrizesPerLiquidity;
console2.log("-------- INSUFFICIENT LIQUIDITY: Draw id %s, tier %s", drawId, tier);
console2.log("-------- \tprize count %s, possible count %s, target count %s ", targetClaimCount, maxPrizesPerLiquidity, prizePool.getTierPrizeCount(tier));
console2.log("-------- \tremaining liquidity", prizePool.getTierRemainingLiquidity(tier));
console2.log("-------- \tprize size: ", prizeSize);
targetClaimCount = maxPrizesPerLiquidity;
{
uint32 maxPrizesPerLiquidity = uint32(prizePool.getTierRemainingLiquidity(tier) / prizeSize);

if (targetClaimCount > maxPrizesPerLiquidity) {
drawTierInsufficientLiquidityPrizeCounts[drawId][tier] =
targetClaimCount -
maxPrizesPerLiquidity;
console2.log("-------- INSUFFICIENT LIQUIDITY: Draw id %s, tier %s", drawId, tier);
console2.log("-------- \tprize count %s, possible count %s, target count %s ", targetClaimCount, maxPrizesPerLiquidity, prizePool.getTierPrizeCount(tier));
console2.log("-------- \tremaining liquidity", prizePool.getTierRemainingLiquidity(tier));
console2.log("-------- \tprize size: ", prizeSize);
targetClaimCount = maxPrizesPerLiquidity;
}
}

if (targetClaimCount > 0) {
// count winners
uint256 winnersLength = countWinners(nextPrizeIndex, targetClaimCount);

// build result arrays
(address[] memory winners, uint32[][] memory prizeIndices) = populateArrays(
nextPrizeIndex,
targetClaimCount,
winnersLength,
countPrizeIndicesPerWinner(nextPrizeIndex, targetClaimCount, winnersLength)
);
address[] memory winners;
uint32[][] memory prizeIndices;

{
// count winners
uint256 winnersLength = countWinners(nextPrizeIndex, targetClaimCount);

// build result arrays
(winners, prizeIndices) = populateArrays(
nextPrizeIndex,
targetClaimCount,
winnersLength,
countPrizeIndicesPerWinner(nextPrizeIndex, targetClaimCount, winnersLength)
);
}

if (isLogging(2)) {
console2.log("claiming %s prizes for drawId %s tier %s", tierPrizes, drawId, tier);
}

uint feesForBatch = claimer.claimPrizes(
vault,
IClaimable(vault),
tier,
winners,
prizeIndices,
Expand Down Expand Up @@ -289,18 +299,21 @@ contract ClaimerAgent is Utils {

function computePrizes() public {
uint24 drawId = prizePool.getLastAwardedDrawId();
require(drawId >= computedDrawId, "invalid draw");
require(drawId > computedDrawId, "invalid draw");
uint8 numTiers = prizePool.numberOfTiers();
drawNumberOfTiers[drawId] = numTiers;
for (uint8 t = 0; t < numTiers; t++) {
for (uint8 tier = 0; tier < numTiers; tier++) {
// make sure canary tier is last
for (uint i = 0; i < env.userCount(); i++) {
address user = env.users(i);
for (uint32 p = 0; p < prizePool.getTierPrizeCount(t); p++) {
if (prizePool.isWinner(address(vault), user, t, p)) {
drawPrizes[drawId].push(Prize(t, user, p));
for (uint i = 0; i < users.length; i++) {
address user = users[i];
for (uint32 p = 0; p < prizePool.getTierPrizeCount(tier); p++) {
if (prizePool.isWinner(vault, user, tier, p)) {
if (tier == 0 && user == address(env.gpBoostHook())) {
console2.log("GP BOOST WON GRAND PRIZE");
}
drawPrizes[drawId].push(Prize(tier, user, p));
totalPrizesComputed++;
drawTierComputedPrizeCounts[drawId][t]++;
drawTierComputedPrizeCounts[drawId][tier]++;
}
}
}
Expand Down Expand Up @@ -342,4 +355,5 @@ contract ClaimerAgent is Utils {
console2.log("\t\t\tReserve", prizePool.reserve() / 1e18);
}
}

}
20 changes: 15 additions & 5 deletions src/environment/SingleChainEnvironment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import { Claimer } from "pt-v5-claimer/Claimer.sol";
import { ILiquidationSource } from "pt-v5-liquidator-interfaces/ILiquidationSource.sol";
import { ILiquidationPair } from "pt-v5-liquidator-interfaces/ILiquidationPair.sol";

// import { LiquidationPairFactory } from "pt-v5-cgda-liquidator/LiquidationPairFactory.sol";
// import { LiquidationRouter } from "pt-v5-cgda-liquidator/LiquidationRouter.sol";
import { GpBoostHook } from "../external/GpBoostHook.sol";

import { TpdaLiquidationPairFactory } from "pt-v5-tpda-liquidator/TpdaLiquidationPairFactory.sol";
import { TpdaLiquidationRouter } from "pt-v5-tpda-liquidator/TpdaLiquidationRouter.sol";

Expand Down Expand Up @@ -60,6 +60,7 @@ contract SingleChainEnvironment is Utils, StdCheats {
TpdaLiquidationRouter public router;
StakingVault public poolVault;
PrizeVault public poolPrizeVault;
GpBoostHook public gpBoostHook;

address[] public users;

Expand Down Expand Up @@ -110,6 +111,8 @@ contract SingleChainEnvironment is Utils, StdCheats {
claimerConfig.maxFeePortionOfPrize
);

gpBoostHook = new GpBoostHook(prizePool, address(claimer));

poolVault = new StakingVault("POOL Staking Vault", "sPOOL", IERC20(address(poolToken)));
poolPrizeVault = new PrizeVault(
"POOL Staking Prize Vault",
Expand Down Expand Up @@ -163,13 +166,16 @@ contract SingleChainEnvironment is Utils, StdCheats {
)
);

prizeToken.mint(address(this), simulationConfig.gpBoost);
prizeToken.approve(address(gpBoostHook), simulationConfig.gpBoost);
gpBoostHook.contributePrizeTokens(simulationConfig.gpBoost);

initializeCgdaLiquidator(liquidatorConfig);
initializeLiquidator(liquidatorConfig);
// console2.log("config.simulation().totalValueLocked: ", config.simulation().totalValueLocked);
addUsers(config.simulation().numUsers, config.simulation().totalValueLocked / config.simulation().numUsers);
}

function initializeCgdaLiquidator(LiquidatorConfig memory _liquidatorConfig) public virtual {
function initializeLiquidator(LiquidatorConfig memory _liquidatorConfig) public virtual {
SD59x18 wethUsdValue = config.wethUsdValueOverTime().get(block.timestamp);
SD59x18 poolUsdValue = config.poolUsdValueOverTime().get(block.timestamp);

Expand All @@ -178,7 +184,7 @@ contract SingleChainEnvironment is Utils, StdCheats {
TpdaLiquidationRouter fixedRouter = new TpdaLiquidationRouter(pairFactory);
vm.label(address(fixedRouter), "TpdaLiquidationRouter");
// console2.log(
// "initializeCgdaLiquidator _liquidatorConfig.exchangeRatePrizeTokenToUnderlying",
// "initializeLiquidator _liquidatorConfig.exchangeRatePrizeTokenToUnderlying",
// _liquidatorConfig.exchangeRatePrizeTokenToUnderlying.unwrap()
// );

Expand Down Expand Up @@ -240,6 +246,10 @@ contract SingleChainEnvironment is Utils, StdCheats {
}
}

function allUsers() public view returns (address[] memory) {
return users;
}

function userCount() public view returns (uint256) {
return users.length;
}
Expand Down
63 changes: 63 additions & 0 deletions src/external/GpBoostHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { console2 } from "forge-std/console2.sol";

import { IPrizeHooks, PrizeHooks } from "pt-v5-vault/interfaces/IPrizeHooks.sol";
import { Claimable } from "pt-v5-vault/abstract/Claimable.sol";
import { PrizePool } from "pt-v5-prize-pool/PrizePool.sol";

/// @title PoolTogether V5 - Grand Prize Booster
/// @notice Uses both hook calls to redirect all prizes won (except the GP) back to the prize pool and contribute them on
/// behalf of this "vault", creating a continuous loop of contributions until this vault's chance slowly fades. The end
/// result of this hook is to contribute as much capital as possible to the GP without creating a game-able opportunity.
/// @dev If the GP is won by this "vault", the hook will revert any claims, thus forcing the GP value to remain in the
/// prize pool.
/// @dev !!! WARNING !!! This contract has not been audited.
/// @author G9 Software Inc.
contract GpBoostHook is IPrizeHooks, Claimable {

/// @notice Thrown if the GP is won by this contract
error LeaveTheGpInThePrizePool();

/// @notice Emitted when a prize is contributed to the prize pool
event ContributedPrize(PrizePool indexed prizePool, uint256 amount);

/// @notice Constructs a new GP Boost Hook
/// @param prizePool_ The prize pool that the prizes originate from
/// @param claimer_ The permitted claimer for prizes
constructor(PrizePool prizePool_, address claimer_) Claimable(prizePool_, claimer_) {
// Initialize a TWAB for this contract so it can win prizes
prizePool.twabController().mint(address(this), 1e18);

// Ensure this contract uses it's own hooks for wins
_hooks[address(this)] = PrizeHooks({
useBeforeClaimPrize: true,
useAfterClaimPrize: true,
implementation: IPrizeHooks(address(this))
});
}

/// @inheritdoc IPrizeHooks
/// @dev Returns the prize pool address as the prize recipient address.
/// @dev Reverts if the prize is the GP.
function beforeClaimPrize(address winner, uint8 tier, uint32, uint96, address) external view returns (address prizeRecipient, bytes memory data) {
prizeRecipient = address(prizePool);
}

/// @inheritdoc IPrizeHooks
/// @dev Contributes the prize amount back to the prize pool on behalf of the specified vault.
function afterClaimPrize(address, uint8, uint32, uint256 prizeAmount, address prizeRecipient, bytes memory) external {
if (prizeRecipient == address(prizePool)) {
prizePool.contributePrizeTokens(address(this), prizeAmount);
emit ContributedPrize(prizePool, prizeAmount);
}
}

/// @notice Contributes prize tokens to the prize pool on behalf of this contract.
/// @param amount The amount to contribute
function contributePrizeTokens(uint256 amount) external {
prizePool.prizeToken().transferFrom(msg.sender, address(prizePool), amount);
prizePool.contributePrizeTokens(address(this), amount);
}
}
6 changes: 6 additions & 0 deletions src/utils/Config.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ struct SimulationConfig {
uint256 timeStep;
uint256 totalValueLocked;
uint256 verbosity;
uint256 gpBoost;
uint256 gpBoostPerDraw;
uint256 gpBoostPerDrawLastDraw;
}

// Contracts configs
Expand Down Expand Up @@ -93,6 +96,9 @@ contract Config is CommonBase {
_simulation.totalValueLocked = uint(convert(convert(int(vm.parseJsonUint(config, "$.simulation.tvl_usd"))).mul(convert(int(10**USD_DECIMALS))).div(poolUsdValueOverTime.get(block.timestamp))));
_simulation.verbosity = vm.parseJsonUint(config, "$.simulation.verbosity");
_simulation.durationDraws = vm.parseJsonUint(config, "$.simulation.duration_draws");
_simulation.gpBoost = vm.parseJsonUint(config, "$.simulation.gp_boost_first_draw");
_simulation.gpBoostPerDraw = vm.parseJsonUint(config, "$.simulation.gp_boost_per_draw");
_simulation.gpBoostPerDrawLastDraw = vm.parseJsonUint(config, "$.simulation.gp_boost_per_draw_last_draw");

_prizePool.drawPeriodSeconds = vm.parseJsonUint(config, "$.prize_pool.draw_period_seconds").toUint32();
_prizePool.firstDrawOpensAt = uint48(block.timestamp + _prizePool.drawPeriodSeconds);
Expand Down
Loading
Loading