Fixed Product Market Maker
Overview
The FixedProductMarketMaker contract implements a market maker for prediction markets using a constant product market maker model. It works in conjunction with the ConditionalTokens contract to provide liquidity and facilitate trading in prediction markets.
Key Features and Considerations
-
Automated Market Making: The contract automatically provides liquidity and calculates prices based on the current state of the market.
-
Fee Mechanism: The contract includes a fee on trades, which accumulates in the fee pool and can be withdrawn by liquidity providers.
-
Time-Bound Markets: Markets have an end time, after which trading is no longer allowed.
-
Integration with ConditionalTokens: The contract works closely with the ConditionalTokens contract to manage the underlying tokens representing market positions.
-
Liquidity Provision: Users can add and remove liquidity, receiving shares in return that represent their portion of the liquidity pool and fee earnings.
-
Price Impact: Large trades will have a significant impact on prices due to the constant product formula.
Contract Details
- License: MIT
- Solidity Version: ^0.8.24
- Source Code: FPMM
Data Structures
State Variables
Contract References
ConditionalTokens public conditionalTokens;
IERC20 public collateralToken;
conditionalTokens
: Reference to the ConditionalTokens contract, which manages the creation and resolution of conditional tokens representing market positions.collateralToken
: The ERC20 token used as collateral for trades in this market. Any market actions such as adding liquidity, buying and selling outcome tokens are performed using this token.
Market Configuration
bytes32[] public conditionIds;
uint public fee;
uint public marketEndTime;
address public oracleAddress;
conditionIds
: Array of condition IDs this market maker is handling, corresponding to the conditions in the ConditionalTokens contract.fee
: The fee percentage charged on trades, expressed in basis points (e.g., 100 = 1%).marketEndTime
: Timestamp when trading in this market ends.oracleAddress
: Address of the oracle contract (typically the PredictionsOracle) that created this market.
Market State
bool public isInitialized;
uint public uniqueBuys;
uint[] outcomeSlotCounts;
bytes32[][] collectionIds;
uint[] positionIds;
isInitialized
: Boolean to check if the contract has been initializeduniqueBuys
: Counter for unique buy transactions in this market.outcomeSlotCounts
: Array of outcome slot counts for each conditioncollectionIds
: Nested array of collection IDs, representing the different combinations of outcomes that have been created.positionIds
: Array of position IDs, corresponding to the different tradable positions in the ConditionalTokens contract.
Fee Management
mapping(address => uint256) withdrawnFees;
uint internal totalWithdrawnFees;
uint internal feePoolWeight;
withdrawnFees
: Mapping of address to withdrawn fees, tracking how much each liquidity provider has withdrawn.totalWithdrawnFees
: Total fees withdrawn from the contract.feePoolWeight
: Weight of the fee pool, used to calculate the share of fees each liquidity provider receives.
Constants
uint constant ONE = 10**18;
uint public MUL_FACTOR = 1000000000;
ONE
: Represents 1 token in 18 decimal format (1e18)MUL_FACTOR
: Multiplication factor for calculations, used to perform arithmetic operations with fixed-point numbers.
Events
FPMMFundingAdded
event FPMMFundingAdded(
address indexedfunder,
uint[] amountsAdded,
uint sharesMinted
);
funder
: The address of the account adding funding to the market.amountsAdded
: An array of amounts added for each outcome position.sharesMinted
: The number of liquidity provider shares minted for the funder.
Emitted when a liquidity provider adds funding to the market. This event is triggered in the addFunding() function.
FPMMFundingRemoved
event FPMMFundingRemoved(
address indexed funder,
uint[] amountsRemoved,
uint collateralRemovedFromFeePool,
uint sharesBurnt
);
funder
: The address of the account removing funding from the market.amountsRemoved
: An array of amounts removed for each outcome position.collateralRemovedFromFeePool
: The amount of collateral removed from the fee pool.sharesBurnt
: The number of liquidity provider shares burnt from the funder.
Emitted when a liquidity provider removes funding from the market. This event is triggered in the removeFunding() function.
FPMMBuy
event FPMMBuy(
address indexed buyer,
uint investmentAmount,
uint feeAmount,
uint indexed outcomeIndex,
uint outcomeTokensBought
);
buyer
: The address of the account buying outcome tokens.investmentAmount
: The total amount of collateral invested in the purchase.feeAmount
: The fee amount charged for the transaction.outcomeIndex
: The index of the outcome being bought.outcomeTokensBought
: The number of outcome tokens received by the buyer.
Emitted when a user buys outcome tokens. This event is triggered in the buy() and buyOnBehalf() functions.
FPMMSell
event FPMMSell(
address indexed seller,
uint returnAmount,
uint feeAmount,
uint indexed outcomeIndex,
uint outcomeTokensSold
);
seller
: The address of the account selling outcome tokens.returnAmount
: The amount of collateral returned to the seller.feeAmount
: The fee amount charged for the transaction.outcomeIndex
: The index of the outcome being sold.outcomeTokensSold
: The number of outcome tokens sold by the seller.
Emitted when a user sells outcome tokens. This event is triggered in the sell() function.
These events provide a comprehensive log of all major actions within the FixedProductMarketMaker, allowing for easy tracking of liquidity additions/removals and trading activities. They are crucial for off-chain systems to monitor and analyze market behavior.
Function Signatures and Descriptions
initialize
function initialize(
ConditionalTokens _conditionalTokens,
IERC20 _collateralToken,
bytes32[] calldata _conditionIds,
uint _fee,
uint _marketEndTime,
address _oracleAddress
) external
_conditionalTokens
: Address of the ConditionalTokens contract._collateralToken
: Address of the ERC20 token used as collateral._conditionIds
: Array of condition IDs this market maker will manage._fee
: The fee percentage charged on trades, expressed in basis points (e.g., 100 = 1%)._marketEndTime
: Timestamp when trading in this market ends._oracleAddress
: Address of the PredictionsOracle contract that created this market.
Initializes the FPMM with the necessary parameters. This function can only be called once and sets up the core configuration for the market.
addFunding
function addFunding(uint addedFunds, uint[] calldata distributionHint) external onlyActive
addedFunds
: Amount of collateral tokens in wei to add as funding.distributionHint
: Array suggesting the initial distribution of funds across outcomes. For example, if the market has two outcomes, the hint could be [50, 50] to indicate that the funder wants to allocate 50% of the funds to the first outcome and 50% to the second. Distribution hints can only be provided on the first funding, any subsequent funding must not have a hint and will be distrubted according to the current ratio of the outcomes.
Allows users to add funding to the market. This increases the liquidity in the market and mints liquidity provider tokens to the funder. Can only be called when the market is active.
removeFunding
function removeFunding(uint sharesToBurn) external
sharesToBurn
: Amount of liquidity provider shares to burn and withdraw funding for.
Allows users to remove funding from the market. This decreases the liquidity in the market and burns the specified amount of liquidity provider tokens.
buy
function buy(uint investmentAmount, uint outcomeIndex, uint minOutcomeTokensToBuy) external onlyActive
investmentAmount
: Amount of collateral tokens in wei to invest in the purchase.outcomeIndex
: Index of the outcome to buy tokens for.minOutcomeTokensToBuy
: Minimum number of outcome tokens to receive (slippage protection).
Allows users to buy outcome tokens. This function can only be called when the market is active and when the oracle address is not set. If the oracle address is set, market actions must be performed through the oracle.
buyOnBehalf
function buyOnBehalf(uint investmentAmount, uint outcomeIndex, uint minOutcomeTokensToBuy, address buyerAddress) external onlyActive returns (uint)
investmentAmount
: Amount of collateral tokens in wei to invest in the purchase.outcomeIndex
: Index of the outcome to buy tokens for.minOutcomeTokensToBuy
: Minimum number of outcome tokens to receive (slippage protection).buyerAddress
: Address of the user to deliver the outcome tokens to.
Allows the oracle to buy outcome tokens on behalf of a user. This function can only be called when the market is active and when the oracle address is set.
sell
function sell(uint returnAmount, uint outcomeIndex, uint maxOutcomeTokensToSell) external onlyActive
returnAmount
: Amount of collateral tokens in wei expected to receive.outcomeIndex
: Index of the outcome to sell tokens for.maxOutcomeTokensToSell
: Maximum number of outcome tokens to sell (slippage protection).
Allows users to sell outcome tokens. This function can only be called when the market is active and selling is enabled.
calcBuyAmount
function calcBuyAmount(uint investmentAmount, uint outcomeIndex) public view returns (uint)
investmentAmount
: Amount of collateral tokens in wei to invest.outcomeIndex
: Index of the outcome to calculate for.
Calculates the amount of outcome tokens that can be bought with a given investment amount. This function is useful for estimating trades before executing them.
calcSellAmount
function calcSellAmount(uint returnAmount, uint outcomeIndex) public view returns (uint outcomeTokenSellAmount)
returnAmount
: Amount of collateral tokens in wei expected to receive.outcomeIndex
: Index of the outcome to calculate for.
Calculates the amount of outcome tokens that need to be sold to receive a given return amount. This function is useful for estimating trades before executing them.
calculateProbabilities
function calculateProbabilities() public view returns (uint[] memory)
Calculates the implied probabilities for each outcome based on the current market state. This function is useful for understanding the current market sentiment and for calculating the fair price of an outcome token.
getPoolBalances
function getPoolBalances() private view returns (uint[] memory)
Returns an array of the current balances of all outcome tokens in the pool. This function is used internally to calculate various market parameters.
getAddressBalances
function getAddressBalances(address userAddress) public view returns (uint[] memory)
userAddress
: The address of the user to get balances for.
Returns an array of the current balances of all outcome tokens for a specific address. This function is useful for checking a user's position in the market.
sumArray
function sumArray(uint[] memory array) public pure returns (uint)
array
: An array of unsigned integers to sum.
A utility function that sums the elements of an array. This is used internally for various calculations.
collectedFees
function collectedFees() external view returns (uint)
Returns the total amount of fees collected by the market maker that have not yet been withdrawn.
feesWithdrawableBy
function feesWithdrawableBy(address account) public view returns (uint)
account
: The address of the account to check withdrawable fees for.
Calculates the amount of fees that can be withdrawn by a specific liquidity provider.
withdrawFees
function withdrawFees(address account) public
account
: The address of the account to withdraw fees for.
Allows a liquidity provider to withdraw their share of the collected fees.
getPositionIds
function getPositionIds() external view returns (uint[] memory)
Returns an array of ConditionalTokens position IDs corresponding to the different tradable positions in the market. This function can be used to retrieve all valid position IDs for the market.
Constant Product Market Maker Model
The FPMM uses a constant product market maker model, similar to Uniswap v1. This model ensures that the product of the balances of all outcome tokens remains constant after each trade (excluding fees).
Key Principles:
- Constant Product: The product of the balances of all outcome tokens remains constant after each trade (excluding fees).
- Price Calculation: The price of an outcome token is determined by the ratio of the balances of the outcome tokens.
- Liquidity: The liquidity in the market affects the price impact of trades. More liquidity means less price slippage for a given trade size.
Mathematical Model:
For a market with two outcomes (A and B), the constant product formula is:
k = a * b
Where:
k
is the constant producta
is the balance of outcome A tokensb
is the balance of outcome B tokens
When trading, the new balances must satisfy:
k = (a + Δa) * (b - Δb)
Where Δa is the amount of A tokens added to the pool, and Δb is the amount of B tokens removed.
Implementation in Code:
The key functions implementing this model are calcBuyAmount and calcSellAmount. Let's look at calcBuyAmount:
function calcBuyAmount(uint investmentAmount, uint outcomeIndex) public view returns (uint) {
uint[] memory poolBalances = getPoolBalances();
uint investmentAmountMinusFees = investmentAmount.sub(investmentAmount.mul(fee) / ONE);
uint buyTokenPoolBalance = poolBalances[outcomeIndex];
uint endingOutcomeBalance = buyTokenPoolBalance.mul(ONE);
for(uint i = 0; i < poolBalances.length; i++) {
if(i != outcomeIndex) {
uint poolBalance = poolBalances[i];
endingOutcomeBalance = endingOutcomeBalance.mul(poolBalance).ceildiv(
poolBalance.add(investmentAmountMinusFees)
);
}
}
return buyTokenPoolBalance.add(investmentAmountMinusFees).sub(endingOutcomeBalance.ceildiv(ONE));
}
This function calculates how many outcome tokens a user will receive for a given investment amount, ensuring that the product of all pool balances remains constant (adjusted for fees).
Explanation of calcBuyAmount:
- Get current pool balances for all outcomes.
- Calculate the investment amount minus fees.
- Get the current balance of the outcome token being bought.
- Initialize the ending balance of the outcome token.
- For all other outcomes:
- Multiply the ending balance by the current balance of the other outcome.
- Divide by the sum of the other outcome's balance and the investment amount.
- The buy amount is the difference between the new balance (current balance + investment) and the calculated ending balance.
This ensures that the product of all balances remains constant (excluding fees), maintaining the core principle of the constant product market maker model.
By implementing this constant product market maker model, the FPMM contract provides automated and continuous liquidity for prediction markets, allowing for efficient price discovery and trading of outcome tokens.