SIP-356: Transaction Costs based Rewards
Author | |
---|---|
Status | Implemented |
Type | Governance |
Network | Optimism & Base |
Implementor | Leo Massazza (@leomassazza) |
Release | TBD |
Created | 2023-11-07 |
Simple Summary
Use dynamic keeper rewards based on gas price to incentivize keepers to settle/liquidate positions, even with high gas prices and not without overpaying for keeping the system.
Abstract
We propose to add an node to oracle manager that will give the cost of a settlement or liquidation transaction in USD based on current gas and ETH price. Then we'll use this new node to calculate the rewards for keepers on the different scenarios that requires a keeper activity. We include caps for lower and higher rewards to guarantee some minimum and maximum rewards are given.
The minimum and maximum rewards are configurable via governance as well as the parameters to get the different kind of transaction costs.
Motivation
SIP-2013 states the problem in perpsV2, same situation happens in V3 perps markets, where keepers are not incentivized to settle/liquidate positions when gas prices are high if rewards are too low to cover for the transaction cost. This is a problem because it can lead to a situation where the system is undercollateralized and the liquidation queue is not being processed. But if we set the rewards too high, we'll end up overpaying for keepers to keep the system, which is not ideal either.
The solution proposed, based on SIP-2013 mechanism, is to include the transaction cost in the rewards calculation, so keepers are incentivized to keep the system even with high gas prices, but not overpaying for it.
Specification
Technical Specification
In order to get the keeper rewards we need to:
1- Get the cost of transaction in eth ( costOfSingleExecutionGrossEth
): We add a new external node that will use Optimism Gas Price Oracle. The cost of execution can be get using the formula:
costOfSingleExecutionGrossEth = (gasPrice * gasUnitsL2) + baseFee * (gasUnitsL1 + overhead) * scalar)
where gasPrice
, baseFee
, overhead
and scalar
are all queried from gasPriceOracle;
and the parameters gasUnitsL1
and gasUnitsL2
are configurable via SCCP and depend on the kind of transaction (settlement, liquidation or flag and liquidate).
References and Optimism contracts for the calculation can be found at:
- https://community.optimism.io/docs/useful-tools/oracles/#gas-oracle
- https://community.optimism.io/docs/developers/build/transaction-fees/
- https://optimistic.etherscan.io/address/0x420000000000000000000000000000000000000F#readProxyContract
- https://basescan.org/address/0x420000000000000000000000000000000000000F#readProxyContract
2- Get the cost of transaction in USD ( costOfSingleExecutionGrossUsd
): With the help of Oracle Manager composability, we get the ETH/USD
ratio from a node and calculate get the cost of execution in USD by doing:
costOfSingleExecutionGrossUsd = costOfExecutionGrossEth * ETH/USD
Notice that costOfSingleExecutionGrossUsd
represents the cost of a single liquidation window call or a single feed update for flagging. We'll come to this later.
3- We calculate the total cost of the transaction costOfExecutionGrossUsd
.
For liquidations after an account was flagged or settlement of an order,
costOfExecutionGrossUsd = costOfSingleExecutionGrossUsd
.
For liquidating a non-flagged account we need to compute the cost of flagging that requires all the feeds to be updated. Since updating a feed requires a large amount of data to be passed in the callData, and due to the nature of Gas costs on Optimism based networks, the cost of flagging can be reduced as
costOfExecutionGrossUsd = N * costOfSingleExecutionGrossUsd
,
where N
is the number of feeds that need to be updated (all collaterals types and positions an account has without taking into account snxUSD). We'll use this formula to calculate the cost of flagging and liquidating a non-flagged account.
4- We then need to guarantee a minimum of rewards to keepers (minimumKeeperRewardCap
), proportional to the cost of execution, or a minimum set. The minimum rewards are configurable by governance. We calculate the minimum rewards as:
minimumKeeperRewardCap = max(costOfExecutionGrossUsd + minKeeperRewardUsd , costOfExecutionGrossUsd * (1 + minKeeperProfitRatioD18)
where minKeeperRewardUsd
and minKeeperProfitRatioD18
are configurable by governance.
5- We also need to guarantee a maximum of rewards to keeper(maximumKeeperRewardCap
), in order to limit the effect it has on users in case we have a temporary surge in base fee gas prices, which result in liquidations of small positions, as we’ve experienced in PerpsV2. The cap will be the minimum of an absolute cap and a variable cap. The variable cap will be based on the size availableMarginInUSD
meaning a larger margin allows for more tolerance of gas price surges. We calculate the maximum as:
maximumKeeperRewardCap = min(availableMarginInUSD * maxKeeperScalingRatioD18 , maxKeeperRewardUsd)
where maxKeeperScalingRatioD18
and maxKeeperRewardUsd
are both configurable by governance and that the cap defaults to simply being maxKeeperRewardUsd
for situations where the account does not have sufficient margin to pay the keeping fees (i.e. availableMarginUSD being zero).
6- Finally, we calculate the rewards for keepers, depending on the kind of transaction as the costs + profit
(based on the kind of transaction) and bounded by the minimum and maximum rewards.
External Node
We introduce a new external node TxGasPriceOracle
that needs to be registered in oracle manager. This node will use the Optimism Gas Price Oracle to get the cost of execution in eth.
The node will be registered with the following parameters:
- l1SettleGasUnits,
- l2SettleGasUnits,
- l1FlagGasUnits,
- l2FlagGasUnits,
- l1LiquidateGasUnits,
- l2LiquidateGasUnits
Settlements
For settlements we use the l1SettleGasUnits
and l2SettleGasUnits
to calculate the transaction gas costs costOfExecutionGrossUsd
. The rewards for keepers are calculated as:
settlementKeeperReward = min(max(minimumKeeperRewardCap, costOfExecutionGrossUsd + settlementStrategy.settlementReward), maximumKeeperRewardCap)
Liquidations
When a keeper calls liquidate, the account is first flagged, then attempted to fully liquidate, if it succeeds to liquidate it fully in that window, it will remove the flag on the account and it will get liquidated.
If the account cannot be fully liquidated in that window, the flag will remain and the account will be liquidated in the next window(s).
Non-flagged liquidations
When a keeper calls liquidate(account)
on a non-flagged account, it needs to update all the feeds related to the account, including the collateral types (except for snxUSD that do not require a feed update) and positions. We calculate the number of feeds as N = #non_snxUSD_collateralTypes + #positions
.
In this case we use the l1FlagGasUnits
and l2FlagGasUnits
and N number of feeds
to calculate the transaction gas costs costOfExecutionGrossUsd
. The rewards for keepers are calculated as:
flagReward = sumOfPositions(notional * liquidationRewardRatioD18)
flagAndLiquidateKeeperReward = min(max(minimumKeeperRewardCap, costOfExecutionGrossUsd + flagReward), maximumKeeperRewardCap)
Flagged liquidations
When a keeper calls liquidate(account)
on a non-flagged account, liquidateFlagged(maxNumberOfAccounts)
, or liquidateFlaggedAccounts([]accountIds[])
it doesn't need to update any feed and will just liquidate the flagged accounts that are still pending to be liquidated due to window limit.
The rewards for the keeper will be the sum of the liquidation rewards for each account that is liquidated in the window.
In this case, for each liquidated account, we use the l1LiquidateGasUnits
and l2LiquidateGasUnits
to calculate the transaction gas costs costOfExecutionGrossUsd
. The rewards for keepers are calculated as:
liquidateKeeperReward = min(max(minimumKeeperRewardCap, costOfExecutionGrossUsd), maximumKeeperRewardCap)
Minimum Required Margin
In order to find if a position can be liquidated, we need to calculate the minimum required margin required to pay the keeper rewards to completely liquidate the position.
To fully liquidate an account we need to flag and fully liquidate it. The flagging should include the cost of updating all the feeds, while fully liquidating an account should include the number of windows of liquidation required to process it fully. Using the parameters and formulas for the scenarions above, we can calculate the minimum required margin to liquidate an account as:
numberOfWindows = ceil(accountSize / maxSizePerWindow)
minimumRequiredMargin = flagAndLiquidateKeeperReward + (numberOfWindows-1) * liquidateKeeperReward
Notice that numberOfWindows-1 is used since the first iteration was included in the flagAndLiquidate step.
Test Cases
TBD but all the different caps and parameters can be tested with the different scenarios.
Configurable Values (Via SCCP)
Please list all values configurable via SCCP under this implementation.
Caps related
minKeeperRewardUsd
,minKeeperProfitRatioD18
,maxKeeperRewardUsd
,maxKeeperScalingRatioD18
are set via setKeeperRewardGuards()
Transaction costs related
- l1SettleGasUnits,
- l2SettleGasUnits,
- l1FlagGasUnits,
- l2FlagGasUnits,
- l1LiquidateGasUnits,
- l2LiquidateGasUnits
are set when registering the external node TxGasPriceOracle
in oracle manager.
Other parameters
- Settlement strategy:
settlementStrategy.settlementReward
- Market:
liquidationRewardRatioD18
This sip was implemented as part of the v3 perp rollout.
Copyright
Copyright and related rights waived via CC0.