Cool Mango Bobcat
Medium
The ReputationMarket contract's use of the LMSR (Logarithmic Market Scoring Rule) library is vulnerable to state inconsistencies during Base L2 chain reorganizations. While the LMSR library implements secure mathematical calculations, the market state used as input to these calculations can become invalid during reorgs when the sequencer's state is invalidated by L1 batch data.
When Base nodes process conflicting L1 data, they automatically reorg to follow that data, which can result in changed vote counts in markets[profileId].votes
. This creates a window where trades could execute based on incorrect vote counts, price calculations could use invalidated state, and vote balances could temporarily reflect incorrect amounts.
This vulnerability affects core market mechanics and user funds through incorrect pricing, as the mathematical LMSR model remains correct but operates on potentially invalid state data.
The vulnerability manifests in the core interaction between ReputationMarket and LMSR:
// ReputationMarket.sol uses potentially unstable state for LMSR calculations
function _calcVotePrice(Market memory market, bool isPositive) private pure returns (uint256) {
uint256 odds = LMSR.getOdds(
market.votes[TRUST], // Could change during reorg
market.votes[DISTRUST], // Could change during reorg
market.liquidityParameter,
isPositive
);
}
The LMSR calculations themselves are mathematically sound:
// LMSR.sol - mathematically correct but depends on stable inputs
function getOdds(
uint256 yesVotes,
uint256 noVotes,
uint256 liquidityParameter,
bool isYes
) public pure returns (uint256 ratio)
An attacker can exploit this by monitoring market state in unsafe blocks. Upon identifying a potential reorg condition, they submit transactions based on current vote counts. If a reorg occurs, these transactions execute with outdated state assumptions, creating arbitrage opportunities from the price discrepancy.
The mitigation strategy requires a fundamental rework of how ReputationMarket interacts with LMSR to ensure stable state inputs.
At the contract level, market state readings should integrate with Base's block confirmation system:
contract ReputationMarket {
// Add block confirmation requirement
uint256 public constant SAFE_BLOCK_CONFIRMATIONS = 15; // Can be adjusted based on Base's specs
// Add safe block check modifier
modifier onlySafeBlock(uint256 blockNumber) {
require(
block.number - blockNumber >= SAFE_BLOCK_CONFIRMATIONS,
"Block not safe"
);
_;
}
// Modify trading functions to operate on safe blocks
function buyVotes(
uint256 profileId,
bool isPositive,
uint256 maxVotesToBuy,
uint256 minVotesToBuy
) public payable whenNotPaused activeMarket(profileId) nonReentrant {
// Store current block for validation
uint256 tradeBlock = block.number;
// Queue the trade
QueuedTrade memory trade = QueuedTrade({
profileId: profileId,
isPositive: isPositive,
amount: maxVotesToBuy,
minAmount: minVotesToBuy,
blockNumber: tradeBlock,
trader: msg.sender,
value: msg.value
});
queuedTrades.push(trade);
emit TradeQueued(profileId, tradeBlock, msg.sender);
}
// Add settlement function
function settleTrade(uint256 tradeIndex) public onlySafeBlock(queuedTrades[tradeIndex].blockNumber) {
QueuedTrade memory trade = queuedTrades[tradeIndex];
// Execute trade using confirmed state
_executeTrade(trade);
delete queuedTrades[tradeIndex];
}
}
This fix:
- Queues trades instead of executing immediately
- Waits for block confirmations before settlement
- Uses confirmed state for LMSR calculations
- Effective against reorg vulnerabilities
The tradeoff is some delay in trade execution, but it ensures safety of the market operations during reorg events.