SIP-79: Deferred Transaction Gas Tank
Author | |
---|---|
Status | Rejected |
Type | Governance |
Implementor | TBD |
Release | TBD |
Discussions-To | <Create a new thread on https://research.synthetix.io and drop the link here> |
Created | 2020-08-19 |
Simple Summary
A persistent gas tank allows users to submit transactions to be executed at a later time by keepers, paying those keepers out of the user's balance.
Abstract
A new GasTank
contract will be deployed that does the following:
- Holds a balance of ether for users to pay for deferred transactions
- Allows users to deposit and withdraw their balance
- Listens to current gas prices from an oracle
- Permits approved contracts to spend ether out of the user's balance for an execution at the current gas price, including a configurable keeper fee
- Allows users to set a maximum gas price they are willing to spend for a transaction
- Enables users to delegate privileged operations to other accounts
Motivation
There are a number of proposed operations in the Synthetix ecosystem which require transactions to be executed after a delay. Such operations include:
- Limit orders and other future triggered orders
- Futures contract order confirmations
- Fee reclamation settlements
Users cannot be expected to monitor prices for limit orders, or tediously execute the transaction sequences required by frontrunning protection. This gas tank mechanism allows keepers to execute such deferred transactions and be reimbursed for the gas cost of executing them. It is intended that this will significantly reduce UX friction for users, as they will not have to execute these advanced operations themselves. Having a balance in the gas tank will not be required for standard exchange operations that do not need it.
Specification
Overview
Any operation that needs to be deferred and executed by a keeper must measure its own gas consumption, reporting this quantity to the gas tank contract at the end of its execution. The gas tank will then consult the latest fast gas price from Chainlink, ensure that this does not exceed the user's configured maximum gas price, and reimburse the keeper from the user's balance, along with a fee to incentivise the execution.
Rationale
The gas tank addresses the question of incentivising keepers to perform actions for users that it would otherwise be too inconvenient for them to perform for themelves. Several different incentive schemes were considered.
SNX incentives
A gas tank could be avoided altogether by minting SNX to incentivise keepers, but this runs into several issues:
- the responsiveness of keepers is influenced by volatility in the SNX price
- the incentive level has to be set carefully to prevent reward farming
- There are potentially macro-economic consequences, as SNX supply expansion depends upon system demand
Synth incentives
Users could deposit synths in their gas tank instead, but this would add additional gas cost to transactions, and necessitate keepers to exchange their earnings. If keeper incentives are paid in ether, then they need never top up their own balances in order to continue operating.
Technical Specification
Least Privilege
Only contracts that absolutely need it should have the ability to invoke this functionality, and therefore the gas tank
contract should verify that those attempting to spend user's ether are the correct contracts known to the AddressResolver
.
Therefore, this contract will need to inherit MixinResolver
.
Upgradeability
The gas tank contract should not operate if the system is suspended, and should itself be pausable for upgrades.
For the same reason, it should have a separated state contract that holds user balances and ether; the two contracts
should retrieve each other's address through the AddressResolver
.
Execution Fee
Each execution by a keeper will be incentivised by flat SCCP-configurable keeper fee, stored as a global system setting.
This should be retrievable from the by a new SystemSettings.keeperFee()
function.
This will return the USD value of ether to be awarded to keepers, and should generally be kept as low as possible while
still being incentivising for keepers to operate.
Delegation
In order to support delegation of gas tank management, the DelegateApprovals
contract will need to be updated with a new canManageGasTankFor
function.
Function API
isApprovedContract
Signature: function isApprovedContract(bytes32 contractName) returns (bool isApproved)
Returns true if and only if the provided contract is approved to spend gas for deferred transactions.
approveContract
Signature: function approveContract(bytes32 contractName, bool approve) external
Allows a contract to be approved or disapproved to spend gas for deferred transactions.
This function should revert if either:
contractName
is not an identifier recognised by theAddressResolver
.msg.sender
is not the contract owner.
balanceOf
Signature: function balanceOf(address account) external view returns (uint balance)
Returns the remaining deposited ether in a given account.
depositEtherOnBehalf
Signature: function depositEtherOnBehalf(address account, uint value) external payable
Increases an account's ether balance. This function should revert if either:
tx.value != value
account != msg.sender && !DelegateApprovals.canManageGasTankFor(account, msg.sender)
depositEther
Signature: function depositEther(uint value) external payable
Equivalent to depositEtherOnBehalf(msg.sender, value)
.
withdrawEtherOnBehalf
Signature: function withdrawEtherOnBehalf(address account, address payable recipient, uint value) external
Reduces an account's ether balance, remitting the withdrawn ether to the recipient
address.
This function should revert if:
balanceOf(msg.sender) < value
account != msg.sender && !DelegateApprovals.canManageGasTankFor(account, msg.sender)
withdrawEther
Signature: function withdrawEther(address payable recipient, uint value) external
Equivalent to withdrawEtherOnBehalf(msg.sender, recipient, value)
.
maxGasPriceOf
Signature: function maxGasPriceOf(address account) external view returns (uint maxGasPriceWei)
Returns the account's configured maximum gas price in wei.
setMaxGasPriceOnBehalf
Signature: function setMaxGasPriceOnBehalf(address account, uint maxGasPriceWei) external
Allows a user to set the maximum gas price that they are willing to pay for any deferred transaction.
This should revert if:
account != msg.sender && !DelegateApprovals.canManageGasTankFor(account, msg.sender)
setMaxGasPrice
Signature: function setMaxGasPrice(uint maxGasPriceWei) external
Equivalent to setMaxGasPriceOnBehalf(msg.sender, maxGasPriceWei)
currentGasPrice
Signature: function currentGasPrice() external view returns (uint currentGasPriceWei)
Fetches the current fast gas price from Chainlink's Fast Gas / Gwei aggregation, returning it as a quantity of wei.
currentEtherPrice
Signature: function currentEtherPrice() external view returns (uint currentEtherPrice)
Fetches the current ether price from Chainlink's ETH / USD aggregation.
executionCost
Signature: function executionCost(uint gas) external returns (uint etherCost)
Returns the cost in ether to spend a given quantity of gas at the current gas price, plus the keeper fee,
plus the execution cost of an invocation of the spendGas
function.
That is, this returns (gas + cost(spendGas)) * currentGasPrice() + SystemSettings.keeperFee() / currentEtherPrice()
.
payGas
Signature: function payGas(address spender, address payable recipient, uint gas) external returns (uint etherSpent)
Allows privileged system smart contracts to reimburse an executing address for executions of a spender's deferred transactions at the current fast gas price.
Provided there is sufficient balance in the spender's account, and the transaction was executed at a gas price in the
correct range, executionCost(gas)
of ether will be transferred to the recipient
address, and deducted from the spender's
balance.
This function should revert if any of the following is satisfied:
msg.sender
is not an approved contractbalanceOf(spender) < executionCost(gas)
tx.gasprice < currentGasPrice()
maxGasPriceOf(spender) < tx.gasprice
- The function call is reentrant
Event API
ContractApproved
Signature: event ContractApproved(bytes32 contractName, bool approved)
Records that a contract was approved or disapproved to spend gas.
EtherDeposited
Signature: event EtherDeposited(address payable indexed spender, uint value)
Records that a user deposited ether in their gas tank.
EtherWithdrawn
Signature: event EtherWithdrawn(address indexed spender, address payable indexed recipient, uint value)
Records that a user withdrew ether from their gas tank.
EtherSpent
Signature: event EtherSpent(address indexed spender, address payable indexed recipient, uint value, uint gasPrice)
Records that an executor was transferred a value of ether by a spender to reimburse the the execution of a deferred transaction at a certain gas price.
MaxGasPriceSet
Signature: event MaxGasPriceSet(address indexed account, uint maxGasPriceWei)
Records that an account set its max acceptable gas price in wei.
Test Cases
See implementation.
Configurable Values (Via SCCP)
| Value | Type | Description |
| keeperFee
| uint
| The usd value of ether to pay keepers when they execute a gas-reimbursed deferred transaction |
Copyright
Copyright and related rights waived via CC0.