Cleaning Offers

Overview

On Mangrove, some offers may fail during execution. When this happens, the taker is compensated with a bounty in native tokens, paid from the failing offers's provision (see Offer Provisions for details).

Failing offers are not a risk to users, they are simply inefficient:

  • They waste gas when encountered.

  • They distort price and depth information in the book.

To address this, Mangrove provides a mechanism for removing failing offers and claiming their bounties, without executing a full market order. This process is called Offer Cleaning. You can read more about the role of cleaning here.

Cleaning Offers

Mangrove’s cleanByImpersonation function allows anyone to simulate taking offers that are expected to fail, receive the bounty, and remove them from the book, while reverting all token transfers.

Since cleaning involves simulated transfers, the cleaner must specify a taker account whose balance and allowances Mangrove can use for impersonation.

The taker:

  • Must have approved Mangrove to transfer the inbound token.

  • Is never affected, as all transfers are reverted after execution.

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.

The Function

function cleanByImpersonation(
  OLKey memory olKey, 
  MgvLib.CleanTarget[] calldata targets, 
  address taker
) external returns (uint successes, uint bounty);

Inputs

  • olKey — identifies the offer list

    • outbound_tkn: token that the taker would receive (buy)

    • inbound_tkn: token that the taker would pay (spend)

    • tickSpacing: spacing between valid ticks

  • targets — array of CleanTarget structs, one per offer to clean:

    struct CleanTarget {
      uint offerId;
      Tick tick;
      uint gasreq;
      uint takerWants;
    }

    Each target defines:

    • offerId — ID of the offer to clean

    • tick — expected offer tick; cleaning will be skipped if it mismatches

    • gasreq — expected gas requirement; cleaning will be skipped if it’s too low

    • takerWants — amount of outbound tokens requested

  • taker — address of the account to impersonate during cleaning Must have sufficient inbound_tkn and allowance for Mangrove.

Protection against malicious updates: Since offers can be updated, including both tick and gasreq prevents cleaning an offer that has changed unexpectedly.

To take offers without such checks:

  • Set gasreq = type(uint).max

  • Set tick = maxTick

Outputs

  • successes — number of offers successfully cleaned

  • bounty — total bounty earned and transferred to msg.sender

Example

Let’s say the DAI–WETH order book contains these offers:

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

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:

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, 925.26);
targets[1] = MgvLib.CleanTarget(42, -79748, 300_000, 871.76);

// 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)
);

Events

// 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
 );

Revert Reasons

Code
Description

"mgv/dead"

Mangrove is terminated

"mgv/inactive"

Offer list is inactive

"mgv/clean/protected"

Protected offer cannot be cleaned

"mgv/clean/offerNotLive"

Offer is not live

"mgv/clean/takerWants/tooBig"

takerWants overflow

"mgv/clean/tick/outOfRange"

Tick is outside valid range

"mgv/clean/tickMismatch"

Tick mismatch between target and offer

"mgv/clean/gasreqTooLow"

Gas requirement too low

"mgv/sendPenaltyReverted"

Failed to send bounty to taker

"mgv/MgvFailToPayTaker"

Failed to transfer outbound token to taker

Last updated