A Solidity implementation of the Logarithmic Market Scoring Rule (LMSR) for pricing prediction market shares. This contract computes share prices and trade costs based on outstanding share quantities using fixed-point arithmetic.
LMSR is a cost-function-based market maker that determines prices based on the total quantities of shares outstanding for each outcome. It's designed to incentivize accurate probability estimates in prediction markets by maintaining a constant liquidity pool.
- Shares: Each outcome has an outstanding quantity (
q_irepresenting how many shares of that outcome are held). - Price: The probability expressed as a fixed-point ratio (64.64 bit format via ABDKMath64x64). For a binary market,
price(q1) + price(q2) ≈ 1.0. - Arbitrary Constant (
b): A scaling factor that controls price sensitivity. Higherbvalues make prices flatter; lower values create steeper curves. Can be chosen via strategy (Static, LargestRunner, or Average). - Trade Cost: The price difference between two states, computed as
cost_atFinal - cost_atInitial.
# Clone the repository
git clone https://github.com/user/LSMR-contract.git
cd LSMR-contract
# Install dependencies
npm install# Compile the contract
npm run build
# Run tests
npm testimport {LMSR} from "./contracts/LMSR.sol";
contract MyMarket {
LMSR public lmsr;
function getPriceForOutcome(uint128[] memory quantities, uint256 outcomeIndex) external view returns (int128) {
// Calculate price using Average strategy for b
return lmsr.calculatePriceForOutcome(quantities, outcomeIndex);
}
function getPriceWithCustomB(uint128[] memory quantities, uint256 outcomeIndex, uint128 b) external view returns (int128) {
// Calculate price with explicit b value
return lmsr.calculatePriceForOutcome(quantities, outcomeIndex, b);
}
}// Binary market pricing
int128 price = lmsr.calculatePrice(q1, q2); // Uses Average strategy
// Triple market
int128 price = lmsr.calculatePriceTriple(quantities); // quantities[0] is priced
// N-outcome market (linear pricing)
int128 price = lmsr.calculatePriceBatch(quantities); // quantities[0] is priced// Cost to trade from initial quantities to final quantities
int128 cost = lmsr.calculateTradeCostForOutcome(
initialQuantities,
finalQuantities,
outcomeIndex
);
// Or with legacy API
int128 cost = lmsr.calculateTradeCost(
q1_initial, q2_initial,
q1_final, q2_final
);The ArbitraryConstantStrategy enum controls how b is derived:
enum ArbitraryConstantStrategy {
Static, // Fixed constant (1,000,000)
LargestRunner, // Largest q_i (most aggressive pricing)
Average // Average of all q_i (balanced)
}
// Pass strategy directly to outcome-based methods
int128 price = lmsr.calculatePriceForOutcome(quantities, 0, ArbitraryConstantStrategy.LargestRunner);
// Or compute b directly
uint128 b = lmsr.getArbitraryConstantByStrategy(quantities, ArbitraryConstantStrategy.Average);Outcome-based (canonical, recommended for new code):
calculatePriceForOutcome(uint128[] memory _qs, uint256 outcomeIndex, [uint128 b | ArbitraryConstantStrategy])calculateTradeCostForOutcome(...)calculatePriceBatchForOutcome(...)– uses linear pricing (q_i / bratios)- Makes the priced outcome explicit; strategy is a parameter, not implicit
Legacy shape-based (backwards compatible):
calculatePrice(q1, q2),calculatePriceTriple(uint128[]),calculatePriceBatch(...)- All hardcode
outcomeIndex = 0and useArbitraryConstantStrategy.Average - Useful for existing integrations
The contract implements two pricing models:
-
Exponential Pricing (canonical, used in
calculatePriceForOutcome):price = e^(q_i/b) / Σ(e^(q_j/b))Reflects traditional LMSR cost functions; convex curves.
-
Ratio Pricing ("linear", used in
calculatePriceBatch*):price = (q_i/b) / Σ(q_j/b)Simplified linear model; better for certain market designs.
Do not mix these semantics—choose based on your market's requirements.
// In tests or scripts using ethers.js
const quantities = [ethers.toBigInt(1000), ethers.toBigInt(800)];
const outcomeIndex = 0;
// Get price for outcome 0
const price = await lmsr.calculatePriceForOutcome(quantities, outcomeIndex);
console.log("Price (64.64 fixed-point):", price.toString());
// Compute trade cost: buy 200 more shares of outcome 0
const newQuantities = [ethers.toBigInt(1200), ethers.toBigInt(800)];
const cost = await lmsr.calculateTradeCostForOutcome(quantities, newQuantities, outcomeIndex);
console.log("Trade cost:", cost.toString());All returned prices and costs are 64.64 fixed-point integers (type int128), not floats.
1.0in 64.64 format =0x10000000000000000(2^64 as int128)- To convert to decimal: divide by 2^64
- The ABDK library handles all math; results are always in this format
- No state: This is a pure math engine—all methods are
pureorview. - Fixed compiler: Solidity 0.8.20 via Hardhat.
- Only runtime dependency:
abdk-libraries-solidityfor fixed-point arithmetic. - Validation: Raises
InvalidInput(reason)for short arrays,InvalidOutcomeIndex()for out-of-bounds indices.
ISC