Sequential Dutch Auctioneer (SDA)

The Base SDA contract is an implementation of the Auctioneer Interface that uses a Sequential Dutch Auction pricing system to buy a target amount of quote tokens or sell a target amount of base tokens over the duration of a market.

The contract is abstract since it leaves final implementation of the createMarket function to a contract that inherits it.

Data Structures (Structs)

MarketParams

Parameters to create a new SDA market. Encoded as bytes and provided as input to createMarket.

struct MarketParams {
    ERC20 payoutToken;
    ERC20 quoteToken;
    address callbackAddr;
    bool capacityInQuote;
    uint256 capacity;
    uint256 formattedInitialPrice;
    uint256 formattedMinimumPrice;
    uint32 debtBuffer;
    uint48 vesting;
    uint48 conclusion;
    uint32 depositInterval;
    int8 scaleAdjustment;
}
FieldTypeDescription

payoutToken

ERC20 (address)

Payout Token (token paid out by market and provided by creator)

quoteToken

ERC20 (address)

Quote Token (token to be received by the market and provided by purchaser)

callbackAddr

address

Callback contract address, should conform to IBondCallback. If 0x00, tokens will be transferred from market owner. Using a callback requires whitelisting by the protocol

capacityInQuote

bool

Is Capacity in Quote Token?

capacity

uint256

Capacity (amount in quote token decimals or amount in payout token decimals). Set capacityInQuote flag appropriately

formattedInitialPrice

uint256

Initial price of the market as a ratio of quote tokens per payout token, see note below on formatting

formattedMinimumPrice

uint256

Minimum price of the market as a ratio of quote tokens per payout token, see note below on formatting

debtBuffer

uint32

Debt buffer. Percent with 3 decimals. Percentage over the initial debt to allow the market to accumulate at anyone time. Works as a circuit breaker for the market in case external conditions incentivize massive buying (e.g. stablecoin depeg). Minimum is the greater of 10% or initial max payout as a percentage of capacity

vesting

uint48

Is fixed term ? Vesting length (seconds) : Vesting expiry (timestamp). A 'vesting' param longer than 50 years is considered a timestamp for fixed expiry

conclusion

uint48

Timestamp that the market will conclude on. Must be at least 1 day (86400 seconds) after creation

depositInterval

uint32

Target deposit interval for purchases (in seconds). Determines the maxPayout of the market. Minimum is 1 hour (3600 seconds)

scaleAdjustment

int8

Market scaling adjustment factor, ranges from -24 to +24. See note below on how to calculate

Calculating Formatted Price and Scale Adjustment

In order to support a broad range of tokens, with different decimal configurations and prices, we calculate an optimal scaling factor for each market. Specifically, the scale adjustment allows the SDA to support tokens with 6 to 18 configured decimals and with prices that differ by up to 24 decimal places (if the tokens have the configured decimals, less if not). To implement this, the market creator must provide a scale adjustment factor and format the price correctly.

First, you need the price of each token in a common unit, e.g. dollars or ether.

Then, if Φp\Phi_p is the price of the payout token and Φq\Phi_q is the price of quote token in the common unit, it can expressed in base 10 (or scientific) notation as:

Φp=ϕp×10dϕp\Phi_p = \phi_p \times 10^{d_{\phi p}}
Φq=ϕq×10dϕq\Phi_q = \phi_q \times 10^{d_{\phi q}}

where:

  • ϕp\phi_p is the coefficient of the payout token price and ​dϕpd_{\phi p}​ is the number of price decimals for the payout token (aka the significand of the price).

  • ϕq\phi_q​ is the coefficient of the quote token price and dϕqd_{\phi q}​ is the number of price decimals for the quote token.

Example: If the price of WETH is $1,500, then we would deconstruct it as 1.5×1031.5 \times 10^3​. Therefore ϕ=1.5\phi = 1.5 and dϕ=3d_{\phi} = 3.

Now, using the price values and the configured number of decimals for each token, we can calculate the scale adjustment.

s=dpdqdϕpdϕq2s = d_p - d_q - \left\lfloor\frac{d_{\phi p} - d_{\phi q}}{2}\right\rfloor

where:

  • dpd_p is the configured payout token decimals (e.g. WETH has 18 configured decimals)

  • dqd_q is the configured quote token decimals.

  • dϕpd_{\phi p} and dϕqd_{\phi q} are as defined above.

Example:

  • Let WETH be the quote token and OHM be the payout token. WETH has 18 configured decimals and OHM has 9 configured decimals. Assume the market price of WETH is $1,500 -> 1.5×1031.5 \times 10^3and the market price of OHM is $10 -> 1×1011 \times 10^1.​

  • Then, the scale adjustment is s=918132=9+1=8s = 9 - 18 - \left\lfloor\frac{1-3}{2}\right\rfloor = -9 + 1 = -8

Once we have the scale adjustment, we can format the initial price using the variables defined above. We're starting the market at the current market price by using the token prices at the time of creation. This is optimal in most situations, but there may be circumstances where it makes sense to choose a different starting price (e.g. you want delayed activity or activity only if the price increases in the short-term).

Φ0=ϕpϕq×1036+s+dqdp+dϕpdϕq\Phi_0 = \frac{\phi_p}{\phi_q} \times 10^{36 + s + d_q - d_p +d_{\phi p} - d_{\phi q}}

Example: Continuing with the WETH and OHM example from above, we format the initial price as:

Φ0=11.5×1036+(8)+189+13=0.666..×1035=0.00666..×1037\Phi_0 = \frac{1}{1.5} \times 10^{36+(-8)+18-9+1-3} = 0.666.. \times 10^{35} = 0.00666.. \times 10^{37}

The difference between the scale and the full decimal difference can result in the price ratio shifting some decimal places. However, we can find the appropriate price factor by considering what we expect the ratio to be from the initial prices. 10 / 1500 = 0.00666.., so we can see the proper price factor is 10^37.

The minimum formatted price can then be extrapolated from the initial price. The above initial price example can be interpreted as 0.00666.. WETH per OHM. Therefore, if you want to receive a minimum of 0.005 WETH per OHM, you would set minimum price as: Φmin=0.005×1037\Phi_{min} = 0.005 \times 10^{37}.

Any alternative way to confirm your minimum price is correct is to redo the same calculation as used to determine initial price, with an updated minimum price for the payout token. Using the 0.005 WETH per OHM value, that would be an OHM price of $7.5 ($1500 x 0.005 = $7.5).

BondMarket

The BondMarket struct contains the core data about a bond market.

struct BondMarket {
    address owner; 
    ERC20 payoutToken;
    ERC20 quoteToken; 
    address callbackAddr; 
    bool capacityInQuote; 
    uint256 capacity;
    uint256 totalDebt;
    uint256 minPrice; 
    uint256 maxPayout; 
    uint256 sold; 
    uint256 purchased; 
    uint256 scale; 
}
FieldTypeDescription

owner

address

Market owner. Sends payout tokens, receives quote tokens (defaults to creator)

payoutToken

ERC20 (address)

Payout Token (token paid out by market and provided by creator)

quoteToken

ERC20 (address)

Quote Token (token to be received by the market and provided by purchaser)

callbackAddr

address

Address to call for any operations on bond purchase. Must inherit to IBondCallback

capacityInQuote

bool

Capacity limit is in payment token (true) or in payout (false, default)

capacity

uint256

Capacity remaining

totalDebt

uint256

Total payout token debt from market

minPrice

uint256

Minimum price (debt will stop decaying to maintain this)

maxPayout

uint256

Max payout tokens out in one order

sold

uint256

Payout tokens out

purchased

uint256

Quote tokens in

scale

uint256

Scaling factor for the market (see MarketParams struct)

BondTerms

struct BondTerms {
    uint256 controlVariable; 
    uint256 maxDebt; 
    uint48 vesting; 
    uint48 conclusion; 
}
FieldTypeDescription

controlVariable

uint256

Scaling variable for price

maxDebt

uint256

Max payout token debt accrued

vesting

uint48

Length of time from deposit to expiry if fixed-term, vesting timestamp if fixed-expiry

conclusion

uint48

Timestamp when market no longer offered

BondMetadata

struct BondMetadata {
    uint48 lastTune; 
    uint48 lastDecay; 
    uint32 length; 
    uint32 depositInterval; 
    uint32 tuneInterval; 
    uint32 tuneAdjustmentDelay; 
    uint32 debtDecayInterval; 
    uint256 tuneIntervalCapacity; 
    uint256 tuneBelowCapacity; 
    uint256 lastTuneDebt; 
}
FieldTypeDescription

lastTune

uint48

Last timestamp when control variable was tuned

lastDecay

uint48

Last timestamp when market was created and debt was decayed

length

uint32

Time from creation to conclusion

depositInterval

uint32

Target frequency of deposits

tuneInterval

uint32

Frequency of tuning

tuneAdjustmentDelay

uint32

Time to implement downward tuning adjustments

debtDecayInterval

uint32

Interval over which debt should decay completely

tuneIntervalCapacity

uint256

Capacity expected to be used during a tuning interval

tuneBelowCapacity

uint256

Capacity that the next tuning will occur at

lastTuneDebt

uint256

Target debt calculated at last tuning

Methods

closeMarket

function closeMarket(uint256 id_) external nonpayable

Disable existing bond marketMust be market owner

Parameters

NameTypeDescription

id_

uint256

ID of market to close

createMarket

function createMarket(bytes params_) external nonpayable returns (uint256)

Creates a new bond market

See specific auctioneer implementations for details on encoding the parameters.

Parameters

NameTypeDescription

params_

bytes

Configuration data needed for market creation, encoded in a bytes array

Returns

NameTypeDescription

id

uint256

ID of new bond market

currentCapacity

function currentCapacity(uint256 id_) external view returns (uint256)

Returns current capacity of a market

Parameters

NameTypeDescription

id_

uint256

undefined

Returns

NameTypeDescription

_0

uint256

undefined

currentControlVariable

function currentControlVariable(uint256 id_) external view returns (uint256)

Up to date control variable

Accounts for control variable adjustment

Parameters

NameTypeDescription

id_

uint256

ID of market

Returns

NameTypeDescription

_0

uint256

Control variable for market in payout token decimals

currentDebt

function currentDebt(uint256 id_) external view returns (uint256)

Calculate debt factoring in decay

Accounts for debt decay since last deposit

Parameters

NameTypeDescription

id_

uint256

ID of market

Returns

NameTypeDescription

_0

uint256

Current debt for market in payout token decimals

getAggregator

function getAggregator() external view returns (contract IBondAggregator)

Returns the Aggregator that services the Auctioneer

Returns

NameTypeDescription

_0

contract IBondAggregator

undefined

getMarketInfoForPurchase

function getMarketInfoForPurchase(uint256 id_) external view returns (address owner, address callbackAddr, contract ERC20 payoutToken, contract ERC20 quoteToken, uint48 vesting, uint256 maxPayout)

Provides information for the Teller to execute purchases on a Market

Parameters

NameTypeDescription

id_

uint256

Market ID

Returns

NameTypeDescription

owner

address

Address of the market owner (tokens transferred from this address if no callback)

callbackAddr

address

Address of the callback contract to get tokens for payouts

payoutToken

contract ERC20

Payout Token (token paid out) for the Market

quoteToken

contract ERC20

Quote Token (token received) for the Market

vesting

uint48

Timestamp or duration for vesting, implementation-dependent

maxPayout

uint256

Maximum amount of payout tokens you can purchase in one transaction

getTeller

function getTeller() external view returns (contract IBondTeller)

Returns the Teller that services the Auctioneer

Returns

NameTypeDescription

teller

contract IBondTeller

Address of the Teller contract servicing the Auctioneer

isInstantSwap

function isInstantSwap(uint256 id_) external view returns (bool)

Does market send payout immediately

Parameters

NameTypeDescription

id_

uint256

Market ID to search for

Returns

NameTypeDescription

_0

bool

undefined

isLive

function isLive(uint256 id_) external view returns (bool)

Is a given market accepting deposits

Parameters

NameTypeDescription

id_

uint256

ID of market

Returns

NameTypeDescription

_0

bool

undefined

marketPrice

function marketPrice(uint256 id_) external view returns (uint256)

Calculate current market price of payout token in quote tokens

Accounts for debt and control variable decay since last deposit (vs _marketPrice())

Parameters

NameTypeDescription

id_

uint256

ID of market

Returns

NameTypeDescription

_0

uint256

Price for market in configured decimals (see MarketParams)

marketScale

function marketScale(uint256 id_) external view returns (uint256)

Scale value to use when converting between quote token and payout token amounts with marketPrice()

Parameters

NameTypeDescription

id_

uint256

ID of market

Returns

NameTypeDescription

_0

uint256

Scaling factor for market in configured decimals

maxAmountAccepted

function maxAmountAccepted(uint256 id_, address referrer_) external view returns (uint256)

Returns maximum amount of quote token accepted by the market

Parameters

NameTypeDescription

id_

uint256

ID of market

referrer_

address

Address of referrer, used to get fees to calculate accurate payout amount. Inputting the zero address will take into account just the protocol fee.

Returns

NameTypeDescription

_0

uint256

undefined

ownerOf

function ownerOf(uint256 id_) external view returns (address)

Returns the address of the market owner

Parameters

NameTypeDescription

id_

uint256

ID of market

Returns

NameTypeDescription

_0

address

undefined

payoutFor

function payoutFor(uint256 amount_, uint256 id_, address referrer_) external view returns (uint256)

Payout due for amount of quote tokens

Accounts for debt and control variable decay so it is up to date

Parameters

NameTypeDescription

amount_

uint256

Amount of quote tokens to spend

id_

uint256

ID of market

referrer_

address

Address of referrer, used to get fees to calculate accurate payout amount. Inputting the zero address will take into account just the protocol fee.

Returns

NameTypeDescription

_0

uint256

amount of payout tokens to be paid

pullOwnership

function pullOwnership(uint256 id_) external nonpayable

Accept ownership of a marketMust be market newOwner

The existing owner must call pushOwnership prior to the newOwner calling this function

Parameters

NameTypeDescription

id_

uint256

Market ID

purchaseBond

function purchaseBond(uint256 id_, uint256 amount_, uint256 minAmountOut_) external nonpayable returns (uint256 payout)

Exchange quote tokens for a bond in a specified marketMust be teller

Parameters

NameTypeDescription

id_

uint256

ID of the Market the bond is being purchased from

amount_

uint256

Amount to deposit in exchange for bond (after fee has been deducted)

minAmountOut_

uint256

Minimum acceptable amount of bond to receive. Prevents frontrunning

Returns

NameTypeDescription

payout

uint256

Amount of payout token to be received from the bond

pushOwnership

function pushOwnership(uint256 id_, address newOwner_) external nonpayable

Designate a new owner of a market. Must be existing market owner to call.

Doesn't change permissions until newOwner calls pullOwnership

Parameters

NameTypeDescription

id_

uint256

Market ID

newOwner_

address

New address to give ownership to

setAllowNewMarkets

function setAllowNewMarkets(bool status_) external nonpayable

Change the status of the auctioneer to allow creation of new markets

Setting to false and allowing active markets to end will sunset the auctioneer

Parameters

NameTypeDescription

status_

bool

Allow market creation (true) : Disallow market creation (false)

setCallbackAuthStatus

function setCallbackAuthStatus(address creator_, bool status_) external nonpayable

Change whether a market creator is allowed to use a callback address in their markets or notMust be guardian

Callback is believed to be safe, but a whitelist is implemented to prevent abuse

Parameters

NameTypeDescription

creator_

address

Address of market creator

status_

bool

Allow callback (true) : Disallow callback (false)

setDefaults

function setDefaults(uint32[6] defaults_) external nonpayable

Set the auctioneer defaultsMust be policy

The defaults set here are important to avoid edge cases in market behavior, e.g. a very short market reacts doesn't tune wellOnly applies to new markets that are created after the change

Parameters

NameTypeDescription

defaults_

uint32[6]

Array of default values 1. Tune interval - amount of time between tuning adjustments 2. Tune adjustment delay - amount of time to apply downward tuning adjustments 3. Minimum debt decay interval - minimum amount of time to let debt decay to zero 4. Minimum deposit interval - minimum amount of time to wait between deposits 5. Minimum market duration - minimum amount of time a market can be created for 6. Minimum debt buffer - the minimum amount of debt over the initial debt to trigger a market shutdown

setIntervals

function setIntervals(uint256 id_, uint32[3] intervals_) external nonpayable

Set market intervals to different values than the defaultsMust be market owner

Changing the intervals could cause markets to behave in unexpected way tuneInterval should be greater than tuneAdjustmentDelay

Parameters

NameTypeDescription

id_

uint256

Market ID

intervals_

uint32[3]

Array of intervals (3) 1. Tune interval - Frequency of tuning 2. Tune adjustment delay - Time to implement downward tuning adjustments 3. Debt decay interval - Interval over which debt should decay completely

Last updated