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

Gas based keeper rewards #1890

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a888961
wip: checkpoint
leomassazza Oct 30, 2023
0154141
checkpoint
leomassazza Oct 30, 2023
01c7a04
wip: add test checkpoint
leomassazza Oct 30, 2023
b2bfe54
fix event
leomassazza Oct 31, 2023
a1153ea
optimize contracts
leomassazza Oct 31, 2023
af9baec
checkpoint: number of chunks for liquidation
leomassazza Oct 31, 2023
88ddfbc
checkpoint: flag and liquidate code
leomassazza Oct 31, 2023
bba9018
checkpoint: fix bootstrap
leomassazza Nov 1, 2023
337da5b
checkpoint: split tests
leomassazza Nov 1, 2023
fd0e7dd
checkpoint: caps
leomassazza Nov 1, 2023
22a090d
checkpoint: bug fixed
leomassazza Nov 2, 2023
5298f61
checkpoint: snxUSD doesn't need a feed update
leomassazza Nov 2, 2023
95a5b98
multi-collateral flag/liquidate fixed and tested
leomassazza Nov 2, 2023
54c80ce
lint
leomassazza Nov 2, 2023
73250ff
Merge branch 'main' into gas-based-keeper-rewards
leomassazza Nov 2, 2023
44fa3b7
test large account (liquidated in chunks)
leomassazza Nov 2, 2023
2af05cd
checkpoint: add missing parameters
leomassazza Nov 2, 2023
c0e4f37
checkpoint
leomassazza Nov 2, 2023
a48bc66
checkpoint: rename
leomassazza Nov 2, 2023
0ddd32e
fix tests
leomassazza Nov 2, 2023
f6bee85
use dynamic min and max cap for keeper rewards
leomassazza Nov 3, 2023
8b6f956
fix cap after flagging + test
leomassazza Nov 3, 2023
db8f7cf
Merge branch 'main' into gas-based-keeper-rewards
leomassazza Nov 3, 2023
da181ed
wip: keeper is paid
leomassazza Nov 5, 2023
c0108e7
Test: Keeper gets paid
leomassazza Nov 5, 2023
465797f
move config to global perps market module
leomassazza Nov 6, 2023
1d89020
PR review wip: rename feedId to keeperCostNodeId
leomassazza Nov 6, 2023
e49bed5
PR review wip: rename costOfFlaggingAndLiquidation
leomassazza Nov 6, 2023
221b429
PR review wip: less duplicate code on KeeperCosts
leomassazza Nov 6, 2023
d30e9c5
PR review wip: move function to MathUtil
leomassazza Nov 6, 2023
485b0f2
PR wip: get margin on flagForLiquidation
leomassazza Nov 6, 2023
899b354
fix on the flight
leomassazza Nov 6, 2023
8f3d869
PR fixes
leomassazza Nov 6, 2023
9287791
remove unused kind
leomassazza Nov 6, 2023
699a927
fix requiredMarginForOrder
leomassazza Nov 7, 2023
b3e956d
rename chunks related to windows
leomassazza Nov 7, 2023
9701118
split costs and rewards on keeperReward function
leomassazza Nov 7, 2023
27e92bb
add test
leomassazza Nov 8, 2023
13d54b3
test if KeeperCost node is not set
leomassazza Nov 8, 2023
9324c3a
typos and fixes
leomassazza Nov 8, 2023
60ca44d
PR fixes
leomassazza Nov 9, 2023
a0f3713
Merge branch 'main' into gas-based-keeper-rewards
leomassazza Nov 12, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ interface IGlobalPerpsMarketModule {
event SynthDeductionPrioritySet(uint128[] newSynthDeductionPriority);

/**
* @notice Gets fired when liquidation reward guard is set or updated.
* @param minLiquidationRewardUsd Minimum liquidation reward expressed as USD value.
* @param maxLiquidationRewardUsd Maximum liquidation reward expressed as USD value.
*/
event LiquidationRewardGuardsSet(
uint256 indexed minLiquidationRewardUsd,
uint256 indexed maxLiquidationRewardUsd
* @notice Gets fired when keeper reward guard is set or updated.
* @param minKeeperRewardUsd Minimum keeper reward expressed as USD value.
* @param minKeeperProfitRatioD18 Minimum keeper profit ratio used together with minKeeperRewardUsd to calculate the minimum.
* @param maxKeeperRewardUsd Maximum keeper reward expressed as USD value.
* @param maxKeeperScalingRatioD18 Scaling used to calculate the Maximum keeper reward together with maxKeeperRewardUsd.
*/
event KeeperRewardGuardsSet(
uint256 minKeeperRewardUsd,
uint256 minKeeperProfitRatioD18,
uint256 maxKeeperRewardUsd,
uint256 maxKeeperScalingRatioD18
);

/**
Expand All @@ -48,6 +52,12 @@ interface IGlobalPerpsMarketModule {
*/
event PerAccountCapsSet(uint128 maxPositionsPerAccount, uint128 maxCollateralsPerAccount);

/**
* @notice Gets fired when feed id for keeper cost node id is updated.
* @param keeperCostNodeId oracle node id
*/
event KeeperCostNodeIdUpdated(bytes32 keeperCostNodeId);

/**
* @notice Thrown when the fee collector does not implement the IFeeCollector interface
*/
Expand Down Expand Up @@ -87,24 +97,35 @@ interface IGlobalPerpsMarketModule {
function getSynthDeductionPriority() external view returns (uint128[] memory);

/**
* @notice Sets the liquidation reward guard (min and max).
* @param minLiquidationRewardUsd Minimum liquidation reward expressed as USD value.
* @param maxLiquidationRewardUsd Maximum liquidation reward expressed as USD value.
* @notice Sets the keeper reward guard (min and max).
* @param minKeeperRewardUsd Minimum keeper reward expressed as USD value.
* @param minKeeperProfitRatioD18 Minimum keeper profit ratio used together with minKeeperRewardUsd to calculate the minimum.
* @param maxKeeperRewardUsd Maximum keeper reward expressed as USD value.
* @param maxKeeperScalingRatioD18 Scaling used to calculate the Maximum keeper reward together with maxKeeperRewardUsd.
*/
function setLiquidationRewardGuards(
uint256 minLiquidationRewardUsd,
uint256 maxLiquidationRewardUsd
function setKeeperRewardGuards(
uint256 minKeeperRewardUsd,
uint256 minKeeperProfitRatioD18,
uint256 maxKeeperRewardUsd,
uint256 maxKeeperScalingRatioD18
) external;

/**
* @notice Gets the liquidation reward guard (min and max).
* @return minLiquidationRewardUsd Minimum liquidation reward expressed as USD value.
* @return maxLiquidationRewardUsd Maximum liquidation reward expressed as USD value.
* @notice Gets the keeper reward guard (min and max).
* @return minKeeperRewardUsd Minimum keeper reward expressed as USD value.
* @return minKeeperProfitRatioD18 Minimum keeper profit ratio used together with minKeeperRewardUsd to calculate the minimum.
* @return maxKeeperRewardUsd Maximum keeper reward expressed as USD value.
* @return maxKeeperScalingRatioD18 Scaling used to calculate the Maximum keeper reward together with maxKeeperRewardUsd.
*/
function getLiquidationRewardGuards()
function getKeeperRewardGuards()
external
view
returns (uint256 minLiquidationRewardUsd, uint256 maxLiquidationRewardUsd);
returns (
uint256 minKeeperRewardUsd,
uint256 minKeeperProfitRatioD18,
uint256 maxKeeperRewardUsd,
uint maxKeeperScalingRatioD18
);

/**
* @notice Gets the total collateral value of all deposited collateral from all traders.
Expand Down Expand Up @@ -158,6 +179,18 @@ interface IGlobalPerpsMarketModule {
*/
function getReferrerShare(address referrer) external view returns (uint256 shareRatioD18);

/**
* @notice Set node id for keeper cost
* @param keeperCostNodeId the node id
*/
function updateKeeperCostNodeId(bytes32 keeperCostNodeId) external;

/**
* @notice Get the node id for keeper cost
* @return keeperCostNodeId the node id
*/
function getKeeperCostNodeId() external view returns (bytes32 keeperCostNodeId);

/**
* @notice get all existing market ids
* @return marketIds an array of existing market ids
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ interface ILiquidationModule {
* @param reward total reward sent to liquidator.
* @param fullLiquidation flag indicating if it was a partial or full liquidation.
*/
event AccountLiquidated(uint128 indexed accountId, uint256 reward, bool fullLiquidation);
event AccountLiquidationAttempt(
uint128 indexed accountId,
uint256 reward,
bool fullLiquidation
);

/**
* @notice Liquidates an account.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ interface IPerpsAccountModule {
* @param accountId Id of the account.
* @return requiredInitialMargin initial margin req (used when withdrawing collateral).
* @return requiredMaintenanceMargin maintenance margin req (used to determine liquidation threshold).
* @return totalAccumulatedLiquidationRewards sum of all liquidation rewards of if all account open positions were to be liquidated fully.
* @return maxLiquidationReward max liquidation reward the keeper would receive if account was fully liquidated. Note here that the accumulated rewards are checked against the global max/min configured liquidation rewards.
*/
function getRequiredMargins(
Expand All @@ -103,7 +102,6 @@ interface IPerpsAccountModule {
returns (
uint256 requiredInitialMargin,
uint256 requiredMaintenanceMargin,
uint256 totalAccumulatedLiquidationRewards,
uint256 maxLiquidationReward
);
}
68 changes: 68 additions & 0 deletions markets/perps-market/contracts/mocks/MockGasPriceNode.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@synthetixio/oracle-manager/contracts/interfaces/external/IExternalNode.sol";

contract MockGasPriceNode is IExternalNode {
NodeOutput.Data private output;

uint256 public constant KIND_SETTLEMENT = 0;
uint256 public constant KIND_FLAG = 1;
uint256 public constant KIND_LIQUIDATE = 2;

uint public settlementCost;
uint public flagCost;
uint public liquidateCost;

constructor() {}

function setCosts(uint _settlementCost, uint _flagCost, uint _liquidateCost) external {
settlementCost = _settlementCost;
flagCost = _flagCost;
liquidateCost = _liquidateCost;
}

// solhint-disable numcast/safe-cast
function process(
NodeOutput.Data[] memory,
bytes memory,
bytes32[] memory runtimeKeys,
bytes32[] memory runtimeValues
) external view override returns (NodeOutput.Data memory) {
NodeOutput.Data memory theOutput = output;
uint256 executionKind;
uint256 numberOfUpdatedFeeds;
for (uint256 i = 0; i < runtimeKeys.length; i++) {
if (runtimeKeys[i] == "executionKind") {
executionKind = uint256(runtimeValues[i]);
continue;
}
if (runtimeKeys[i] == "numberOfUpdatedFeeds") {
numberOfUpdatedFeeds = uint256(runtimeValues[i]);
continue;
}
}

if (executionKind == KIND_SETTLEMENT) {
theOutput.price = int(settlementCost);
} else if (executionKind == KIND_FLAG) {
theOutput.price = int(flagCost * numberOfUpdatedFeeds);
} else if (executionKind == KIND_LIQUIDATE) {
theOutput.price = int(liquidateCost);
} else {
revert("Invalid execution kind");
}

return theOutput;
}

function isValid(
NodeDefinition.Data memory nodeDefinition
) external pure override returns (bool) {
return nodeDefinition.nodeType == NodeDefinition.NodeType.EXTERNAL;
}

function supportsInterface(bytes4) public view virtual override(IERC165) returns (bool) {
return true;
}
}
12 changes: 4 additions & 8 deletions markets/perps-market/contracts/modules/AsyncOrderModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -123,23 +123,19 @@ contract AsyncOrderModule is IAsyncOrderModule {
);

Position.Data storage oldPosition = PerpsMarket.accountPosition(marketId, accountId);
(
,
uint256 currentMaintenanceMargin,
uint256 currentTotalLiquidationRewards,

) = PerpsAccount.load(accountId).getAccountRequiredMargins();
PerpsAccount.Data storage account = PerpsAccount.load(accountId);
(, uint256 currentMaintenanceMargin, ) = account.getAccountRequiredMargins();
(uint256 orderFees, uint256 fillPrice) = _computeOrderFees(marketId, sizeDelta);

return
AsyncOrder.getRequiredMarginWithNewPosition(
account,
marketConfig,
marketId,
oldPosition.size,
oldPosition.size + sizeDelta,
fillPrice,
currentMaintenanceMargin,
currentTotalLiquidationRewards
currentMaintenanceMargin
) + orderFees;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {PerpsMarketFactory} from "../storage/PerpsMarketFactory.sol";
import {GlobalPerpsMarketConfiguration} from "../storage/GlobalPerpsMarketConfiguration.sol";
import {IMarketEvents} from "../interfaces/IMarketEvents.sol";
import {IAccountEvents} from "../interfaces/IAccountEvents.sol";
import {KeeperCosts} from "../storage/KeeperCosts.sol";

/**
* @title Module for settling async orders using pyth as price feed.
Expand All @@ -32,6 +33,7 @@ contract AsyncOrderSettlementPythModule is
using GlobalPerpsMarket for GlobalPerpsMarket.Data;
using GlobalPerpsMarketConfiguration for GlobalPerpsMarketConfiguration.Data;
using Position for Position.Data;
using KeeperCosts for KeeperCosts.Data;

/**
* @inheritdoc IAsyncOrderSettlementPythModule
Expand Down Expand Up @@ -64,8 +66,7 @@ contract AsyncOrderSettlementPythModule is
(runtime.newPosition, runtime.totalFees, runtime.fillPrice, oldPosition) = asyncOrder
.validateRequest(settlementStrategy, price);

runtime.amountToDeduct += runtime.totalFees;

runtime.amountToDeduct = runtime.totalFees;
runtime.newPositionSize = runtime.newPosition.size;
runtime.sizeDelta = asyncOrder.request.sizeDelta;

Expand Down Expand Up @@ -114,7 +115,9 @@ contract AsyncOrderSettlementPythModule is
}
}
}
runtime.settlementReward = settlementStrategy.settlementReward;
runtime.settlementReward =
settlementStrategy.settlementReward +
KeeperCosts.load().getSettlementKeeperCosts(runtime.accountId);

if (runtime.settlementReward > 0) {
// pay keeper
Expand Down
62 changes: 49 additions & 13 deletions markets/perps-market/contracts/modules/GlobalPerpsMarketModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {IGlobalPerpsMarketModule} from "../interfaces/IGlobalPerpsMarketModule.s
import {OwnableStorage} from "@synthetixio/core-contracts/contracts/ownership/OwnableStorage.sol";
import {AddressError} from "@synthetixio/core-contracts/contracts/errors/AddressError.sol";
import {ParameterError} from "@synthetixio/core-contracts/contracts/errors/ParameterError.sol";
import {KeeperCosts} from "../storage/KeeperCosts.sol";

/**
* @title Module for global Perps Market settings.
Expand All @@ -20,6 +21,7 @@ contract GlobalPerpsMarketModule is IGlobalPerpsMarketModule {
using SetUtil for SetUtil.UintSet;
using GlobalPerpsMarketConfiguration for GlobalPerpsMarketConfiguration.Data;
using GlobalPerpsMarket for GlobalPerpsMarket.Data;
using KeeperCosts for KeeperCosts.Data;

/**
* @inheritdoc IGlobalPerpsMarketModule
Expand Down Expand Up @@ -68,34 +70,50 @@ contract GlobalPerpsMarketModule is IGlobalPerpsMarketModule {
/**
* @inheritdoc IGlobalPerpsMarketModule
*/
function setLiquidationRewardGuards(
uint256 minLiquidationRewardUsd,
uint256 maxLiquidationRewardUsd
function setKeeperRewardGuards(
uint256 minKeeperRewardUsd,
uint256 minKeeperProfitRatioD18,
uint256 maxKeeperRewardUsd,
uint256 maxKeeperScalingRatioD18
) external override {
OwnableStorage.onlyOwner();
if (minLiquidationRewardUsd > maxLiquidationRewardUsd) {
revert ParameterError.InvalidParameter("min/maxLiquidationRewardUSD", "min > max");
if (minKeeperRewardUsd > maxKeeperRewardUsd) {
revert ParameterError.InvalidParameter("min/maxKeeperRewardUSD", "min > max");
}

GlobalPerpsMarketConfiguration.Data storage store = GlobalPerpsMarketConfiguration.load();
store.minLiquidationRewardUsd = minLiquidationRewardUsd;
store.maxLiquidationRewardUsd = maxLiquidationRewardUsd;

emit LiquidationRewardGuardsSet(minLiquidationRewardUsd, maxLiquidationRewardUsd);
store.minKeeperRewardUsd = minKeeperRewardUsd;
store.minKeeperProfitRatioD18 = minKeeperProfitRatioD18;
store.maxKeeperRewardUsd = maxKeeperRewardUsd;
store.maxKeeperScalingRatioD18 = maxKeeperScalingRatioD18;

emit KeeperRewardGuardsSet(
minKeeperRewardUsd,
minKeeperProfitRatioD18,
maxKeeperRewardUsd,
maxKeeperScalingRatioD18
);
}

/**
* @inheritdoc IGlobalPerpsMarketModule
*/
function getLiquidationRewardGuards()
function getKeeperRewardGuards()
external
view
override
returns (uint256 minLiquidationRewardUsd, uint256 maxLiquidationRewardUsd)
returns (
uint256 minKeeperRewardUsd,
uint256 minKeeperProfitRatioD18,
uint256 maxKeeperRewardUsd,
uint256 maxKeeperScalingRatioD18
)
{
GlobalPerpsMarketConfiguration.Data storage store = GlobalPerpsMarketConfiguration.load();
minLiquidationRewardUsd = store.minLiquidationRewardUsd;
maxLiquidationRewardUsd = store.maxLiquidationRewardUsd;
minKeeperRewardUsd = store.minKeeperRewardUsd;
minKeeperProfitRatioD18 = store.minKeeperProfitRatioD18;
maxKeeperRewardUsd = store.maxKeeperRewardUsd;
maxKeeperScalingRatioD18 = store.maxKeeperScalingRatioD18;
}

/**
Expand Down Expand Up @@ -134,6 +152,24 @@ contract GlobalPerpsMarketModule is IGlobalPerpsMarketModule {
return address(GlobalPerpsMarketConfiguration.load().feeCollector);
}

/**
* @inheritdoc IGlobalPerpsMarketModule
*/
function updateKeeperCostNodeId(bytes32 keeperCostNodeId) external override {
OwnableStorage.onlyOwner();

KeeperCosts.load().update(keeperCostNodeId);

emit KeeperCostNodeIdUpdated(keeperCostNodeId);
}

/**
* @inheritdoc IGlobalPerpsMarketModule
*/
function getKeeperCostNodeId() external view override returns (bytes32 keeperCostNodeId) {
return KeeperCosts.load().keeperCostNodeId;
}

/**
* @inheritdoc IGlobalPerpsMarketModule
*/
Expand Down
Loading
Loading