Cleaning offers
Offers on Mangrove may fail during a market order. When they do, the taker is compensated for the gas wasted on making the offer fail in the form of a bounty in native tokens paid from the failing offer's provision which the offer owner deposited with Mangrove when posting the offer. Refer to Offer provisions for details on how provisions and bounties work and are calculated.
From the taker's perspective, failing offers are a nuisance: They consume gas when running limit orders and they pollute the price and depth information of the market. It's important to note, that failing offers are only a nuisance, not a risk: Users are compensated for gas spent on making the offer fail and the limit avg. price will never be exceeded.
Therefore Mangrove provides a facility for removing specific failing offers and receiving the bounty without running a market order. This is called Offer Cleaning.
You can read more about the role of cleaning here.
Mangrove's cleanByImpersonation
function allows anyone to clean a failing offer, by providing the execution parameters that will make it fail. The function will execute the specified offers, including token transfers, and (1) reward the cleaner and remove the offer from the book if the offer fails or (2) revert the offer execution if it succeeds and keep the offer in the book; in both cases, all token transfers are reverted.
Since this cleaning involves actual token transfers (though they will be reverted) Mangrove requires the caller to specify a taker account from which the inbound tokens will be transferred. In effect, this taker account will be impersonated during cleaning, hence *ByImpersonation
.
The taker must of course have approved Mangrove for transferring the inbound token on their behalf. And cleaners are free to specify any account, including accounts they do not control.
The takers that are impersonated during cleaning are unaffected: All token transfers made inside cleanByImpersonation
will be reverted.
cleanByImpersonation(olKey, targets, taker)
β
- Signature
- Events
- Revert strings
- Solidity
function cleanByImpersonation(
OLKey memory olKey,
MgvLib.CleanTarget[] calldata targets,
address taker
) external returns (uint successes, uint bounty);
struct CleanTarget {
uint offerId;
Tick tick;
uint gasreq;
uint takerWants;
}
// Since the contracts that are called during the order may be partly reentrant, more logs could be emitted by Mangrove.
// We list here only the main expected logs.
// For each successful offer taken during the market order:
event OfferSuccess(
bytes32 indexed olKeyHash, // hash of the OLKey (inbound token, outbound token and tickSpacing)
address indexed taker, // address of the market order call
uint indexed id,
uint takerWants, // original wants of the order
uint takerGives // original gives of the order
);
// For each offer cleaned during the market order:
event OfferFail(
bytes32 indexed olKeyHash, // hash of the OLKey (inbound token, outbound token and tickSpacing)
address indexed taker, // address of the market order call
uint indexed id,
uint takerWants, // original wants of the order
uint takerGives // original gives of the order
uint penalty,
// `mgvData` is either:
// * `"mgv/makerRevert"` if `makerExecute` call reverted
// * `"mgv/makerTransferFail"` if `outbound_tkn` transfer from the maker contract failed after `makerExecute`
// * `"mgv/makerReceiveFail"` if `inbound_tkn` transfer to maker contract failed (e.g. contract's address is not allowed to receive `inbound_tkn`)
bytes32 mgvData
);
// For each offer whose posthook reverted during second callback:
// 1. Loging offer failure
event OfferFailWithPosthookData(
bytes32 indexed olKeyHash,
address indexed taker,
uint indexed id,
uint takerWants,
uint takerGives,
uint penalty,
bytes32 mgvData,
// `posthookData` contains the first 32 bytes of the posthook revert reason
// e.g the complete reason if posthook reverted with a string small enough.
bytes32 posthookData
);
// 2. Debiting maker from Offer Bounty
event Debit(address indexed maker, uint amount);
// Logging at the end of Market Order:
event OrderComplete(
bytes32 indexed olKeyHash,
address indexed taker,
uint fee, // the fee paid by the taker
);
// Gatekeeping
"mgv/dead" // Trying to take offers on a terminated Mangrove
"mgv/inactive" // Trying to take offers on an inactive offer list
"mgv/clean/protected" //?
"mgv/clean/offerNotLive" //?
// Overflow
"mgv/clean/takerWants/tooBig" // takerWants for clean overflows
// Wrong inputs
'mgv/clean/tick/outOfRange' //?
"mgv/clean/tickMismatch" //?
"mgv/clean/gasreqTooLow" //?
// Panic reverts
"mgv/sendPenaltyReverted" // Mangrove could not send Offer Bounty to taker
"mgv/MgvFailToPayTaker" // Mangrove was unable to transfer outbound_tkn to taker (Taker blacklisted?)
import {IMangrove} from "@mgv/src/IMangrove.sol";
import "@mgv/src/core/MgvLib.sol";
// context of the call
// IMangrove mgv = IMangrove(payable(<address of Mangrove>));
// Mangrove contract
IMangrove mgv = IMangrove(payable(mangroveAddress));
// OLKey olkey = OLKey(<address of outbound token>, <address of inbound token>, <tick spacing>);
// struct containing outbound_tkn, inbound_tkn and tickSpacing
OLKey memory olkey = OLKey(address(base), address(quote), 1);
// if Mangrove is not approved yet for inbound token transfer.
IERC20(inbTkn).approve(MGV, type(uint).max);
MgvLib.CleanTarget[] memory targets = new MgvLib.CleanTarget[](2);
targets[0] = MgvLib.CleanTarget(77, -79815, 250_000, 0.3163);
targets[1] = MgvLib.CleanTarget(42, -79748, 300_000, 0.3000);
// cleaning the offer using olkey, the previous array of target offers and this contract's address as taker
(uint successes, uint bounty) = mgv.cleanByImpersonation(
olkey,
targets,
address(this)
);
Prerequisitesβ
The prerequisites and steps needed to use the Mangrove.cleanByImpersonation
function are:
taker
must have approved Mangrove for spendinginbound_tkn
on the offer list you want to clean.taker
must have sufficientinbound_tkn
funds for the execute arguments
Inputsβ
olKey
: identifies the offer list and consists of:outbound_tkn
: address of the outbound token (that the taker will buy).inbound_tkn
: address of the inbound token (that the taker will spend).tickSpacing
: specifies the space between allowed ticks (see Ticks, ratios, and prices for details)
targets
: an array of where eachCleanTarget
identifies an offer to clean and the execution parameters that will make it fail:offerId
: the ID of the offer to clean.tick
: the offer's expected tick. If this doesn't match when the tx is executed, cleaning of the offer will not be attempted to avoid wasting gas since the cleaning assumptions have changed.gasreq
: the offer's expected gasreq. If this is lower than the offer's actual gasreq when the tx is executed, cleaning of the offer will not be attempted to avoid wasting gas since the cleaning assumptions have changed.takerWants
: the amount to request from the offer.
taker
: the address of the taker to impersonate.
Offers can be updated, so if targets
was just an array of offerId
s, there would be no way to protect against a malicious offer update mined right before a clean
. The offer could suddenly have a worse price, or require a lot more gas.
If you only want to take offers without any checks on the offer contents, you can simply:
- Set
gasreq
totype(uint).max
, and - Set
tick
tomaxTick
.
Outputsβ
successes
is the number of successfully cleaned offers.bounty
is the total bounty received and transferred tomsg.sender
.
Exampleβ
Consider the offers on this DAI-WETH offer list and let's construct a cleanByImpersonation
call.
Tick | Ratio (WETH/DAI) | Offer ID | Gives (DAI) | Gas required |
---|---|---|---|---|
-79815 | 0.0003419 | 77 | 925.26 | 250,000 |
-79815 | 0.0003419 | 177 | 916.47 | 270,000 |
-79748 | 0.0003442 | 42 | 871.76 | 300,000 |
Let's say we've detected that offers 77 and 42 will fail. We can now construct the following targets
array:
targets[0] = [77, -79815, 250_000, 925.26]
targets[1] = [42, -79748, 300_000, 871.76]
.
We also know the address of a taker
who has approved Mangrove to transfer a sufficient amount of WETH on their behalf, such that it could pay for executing the most expensive of the offers. Remember, the transfers will be reverted after each cleaning, so the taker
need only have sufficient funds for the most expensive offer, not the total of the offers cleaned.
Putting it together, the call to cleanByImpersonation
would look like this (assuming mgv
is ):
IMangrove mgv = IMangrove(payable(<address of Mangrove>));
OLKey memory olkey = OLKey(<address of DAI>, <address of WETH>, 1);
MgvLib.CleanTarget[] memory targets = new MgvLib.CleanTarget[](2);
targets[0] = MgvLib.CleanTarget(77, -79815, 250_000, 0.3163);
targets[1] = MgvLib.CleanTarget(42, -79748, 300,000, 0.3000);
mgv.cleanByImpersonation(olKey, targets, taker);