Priceless Position Manager

Below, the GCR (“Global Collateralization Ratio”) is the ratio between the total amount of synthetic tokens in debt versus the total amount of collateral held in the contract. Note that this is only a ratio between token amounts, not token value; for example, a synthetic BTC with 1 sBTC of total synthetic debt and 1 USDC of collateral is actually 1.0, regardless of the price of Bitcoin.

State Variables

Note: we omit variables whose comments in Solidity do a good enough job of explaining their job. contractState is an enum with three possibilities:
  • Open: “normal” pre-expiry operation. Users can start liquidations only when the EMP is in this state. Will advance when expire() function is called after expirationTimestamp (or when emergencyShutdown is called).
  • ExpiredPriceRequested: The contract’s expire() function has been successfully called and we are waiting for a returned OptimisticOracle price. Will advance when settle() is called if there is a resolved price available for the OptimisticOracle.
  • ExpiredPriceReceived: The OptimisticOracle price has resolved and settle() has been called at least once.
Keep in mind that the widely used _onlyPreExpiration and _onlyPostExpiration modifiers don’t look at this state directly; instead, they look at whether expirationTimestamp has passed yet or not. positions is a mapping containing a PositionData struct, one for each sponsor (note that it’s not possible for one sponsor to have two positions). These PositionData structs track the sponsor’s outstandingTokens (how much in synthetic assets they owe to the position), their collateral (how much collateral is deposited), and information regarding withdrawal and transfer requests. tokenCurrency is the synthetic asset token address that was newly created upon EMP construction. This is the token in which debts are tallied, and all tokenCurreny “out in the wild” correspond to some sponsor’s position with collateral, which initially minted them. priceIdentifier: This identifies what price the synthetic asset should follow. PriceIdentifier must be one of the approved identifiers for the UMA system. As one example, the ETHUSD price feed is an approved feed that dictates specifically how this price must be calculated. A special case is the price identifier NUMERICAL, which relies on ancillaryData described. ancillaryData: Carries additional information that might be necessary to calculate the price specified by priceIdentifier. In the case of NUMERICAL, the ancillary data must pose a question that can be answered numerically. For more information, see Creating a Novel Synthetic Asset (using the NUMERICAL identifier). withdrawalLiveness: If a withdrawal would lower the GCR, it can’t be done instantly and must go through a waiting period. This variable defines how long that waiting period is. See the Solidity comments for more info. minSponsorTokens: If a user wants to become a sponsor, they must create a position that mints at least this many synthetic tokens, and can never go below this amount (except for paying it back in totality, thus removing it). This must be set to avoid any given position being so small that a liquidator is not incentivized to liquidate it (due to gas costs making it prohibitive).
  • It should be a considerable amount such that there is an incentive for the liquidator to liquidate it.
  • It should be easily mintable by a potential sponsor, i.e. it should not be a huge value.
  • The minSponsorTokens should make sense in collateral amount i.e. USDC. So the value should be, let’s say, worth 100 USDC or 10 USDC and so forth. It cannot be equal to 0.0001 USDC etc.
expiryPrice: Only set while when contractState == ExpiredPriceResolved. Stores the price the OptimisticOracle returned. ooReward: The amount of collateral that is sent to the OptimisticOracle as a reward to the proposer. This is also the bond that a liquidator or disputer must submit. During expiration, this cost is socialized to all sponsors to pay for the OptimisticOracle price request. financialProductLibrary: Always set to the zero address, and thus has no bearing on contract behaviour.

External/Public State-Changing Functions

requestTransferPosition, transferPositionPassedRequest, and cancelTransferPosition all allow a sponsor to transfer his position to another address. The new address must not already hold a position. These methods use the withdrawalLiveness value as a delay. depositTo and its wrapper deposit simply transfer collateralAmount collateral from the specified address to its sponsor position. This can be used to raise the CR of the position that is at risk of becoming undercollateralized. Conversely, withdraw allows a user to withdraw collateral from his position. In contrast to the below functions, this is instant, but can only be done if it does not result in the position having a lower CR than the GCR. requestWithdrawal, withdrawPassedRequest, and cancelWithdrawal behave similarly to the transferPosition function. One difference however is that a liquidation started on this position may essentially reset the countdown of the withdrawal. See Liquidatable’s createLiquidation function, near the end.
create is where new synthetic tokens are born! The user deposits collateralAmount collateral and mints numTokensnew synthetic tokens. He can set both of these parameters, but if this results in a sponsor position whose collateralization ratio is below the GCR, the call will fail. This ensures that new sponsors don’t ever move the total position closer to default. It will also fail if the position ends up with less than the minSponsorTokens amount. If successful, the user has a position with collateral in the collateral currency (usually USDC) and a debt of synthetic tokens (position.tokensOutstanding); he also has newly-minted synthetic tokens equal to this debt in his wallet, which he may send away or do whatever he wants with. repay burns numTokens synthetic asset tokens from the sponsor’s wallet, and reduces his position’s tokensOutstandingvalue by the amount burned (unless this would result in a value of less than minSponsorTokens, in which case the call reverts). Like deposit, this can be used to increase the CR of the position, but this time by repaying some of the debt of the position. redeem also burns numTokens synthetic asset tokens from the sponsor’s wallet and reduces tokensOutstanding, but instead of lowering its CR, collateral is returned from the position to the sponsor’s wallet. This will also revert if it results in a position’s tokensOutstanding going below minSponsorTokens - unless the tokensOutstanding goes to zero (meaning numTokens == tokensOutstanding), in which case all collateral is returned and the position is deleted. expire can be called by anyone, and can only be called if contractState == Open but the expirationTimestamp has passed. This function requests a price from the optimistic oracle, socializes the cost of the request to the sponsors, and sets contractState to ExpiredPriceRequested. emergencyShutdown does essentially the same thing as expire, but it must be called by the governor and can be called before expirationTimestamp has passed. settleExpired does a few different things. First, it checks that expire has already been called and that the oracle’s price has now resolved. If these checks pass, the following effects take place:
  • If this is the first time the function has been successfully called, it will store the price from the OptimisticOracle and move the contractState to ExpiredPriceReceived.
  • If the caller has any synthetic tokens in his wallet, these are burned and the caller is sent an equivalent amount of underlying collateral, according to the price. In other words, any synthetic tokens are “cashed out” for underlying collateral at market price.
  • If the caller has a sponsor position (and thus a debt of position.tokensOutstanding synthetic tokens), this debt is converted into a collateral amount and subtracted from the position’s collateral. Any leftover collateral is sent to the user.

Internal State-Changing Functions

_reduceSponsorPosition is not called within PricelessPositionManager code directly, instead only used in Liquidatable which inherits it. This function reduces a given sponsor’s position values for any of the passed variables for tokensToRemove (here meaning synthetic token debt), collateralToRemove, and withdrawalAmountToRemove, which removes collateral from any pending slow withdrawal. This function will revert if this operation results in a position having less than minSponsorTokens for tokensOutstanding, except in the case where tokensOutstanding and rawCollateral would reach zero, in which case the position is deleted. _incrementCollateralBalances and _decrementCollateralBalances simply add or subtract both from some local memory argument and the global tracker totalPositionCollateral, ensuring consistency between individual and global variables. _decrementCollateralBalancesCheckGCR does the same as the previous decrement function, but will revert if the resulting position CR is lower than the GCR. _requestOraclePrice_senderPays does the same thing as the previous function, but is used by the Liquidatable.dispute function and gets ooReward from msg.sender, rather than socializing the cost to sponsors. In other words, while the previous function charges “the EMP itself” (and all its sponsors) for the cost, this function expects msg.sender to pay. _getOraclePrice gets a price that has been resolved, after either of the two above functions have requested a price. If the price has not yet resolved the function will revert. Both _transformPrice and _transformPriceIdentifier simply return the price or identifier respectively, since financialContractLibrary is the zero address for the EMP.