The Seer Protocol's MarketFactory contract creates prediction markets linked to Reality.eth questions. The askRealityQuestion function is responsible for submitting these questions to Reality.eth.
The askRealityQuestion function uses a user-provided openingTime parameter when creating or reusing Reality.eth questions. However, it doesn't validate this parameter against the current block timestamp, potentially allowing the creation of markets with questions that open in the past.
Markets could be created with questions that are immediately open for answering, potentially leading to rapid and unexpected resolutions.
Malicious actors could exploit this to create markets that resolve quickly based on past events, taking advantage of information asymmetry.
The integrity of the prediction market system could be compromised, as markets might not provide a fair opportunity for all participants to engage.
Scenario
A user creates a market with an openingTime set to a past timestamp.
The Reality.eth question is immediately open for answering.
The user or an accomplice quickly provides an answer to the question.
The market resolves based on this answer before most participants are aware of its existence.
Probability
High. This issue can be easily triggered by any user creating a market, either accidentally or intentionally.
Fix
Implement a check in the askRealityQuestion function to ensure the openingTime is in the future:
function askRealityQuestion(
string memory encodedQuestion,
uint256 templateId,
uint32 openingTime,
uint256 minBond
) internal returns (bytes32) {
require(openingTime > block.timestamp, "Opening time must be in the future");
// Rest of the function remains the same
...
}
Consider adding an upper bound to the openingTime as well, to prevent markets from being created too far in the future.
Poc
describe("askRealityQuestion vulnerability", function () {
it("allows creation of markets with questions opening in the past", async function () {
const pastOpeningTime = await time.latest() - 3600; // 1 hour in the past
const futureOpeningTime = await time.latest() + 3600; // 1 hour in the future
// Create a market with a past opening time (this should ideally fail, but currently doesn't)
await expect(
marketFactory.createCategoricalMarket({
...categoricalMarketParams,
openingTime: pastOpeningTime,
})
).to.emit(marketFactory, "NewMarket");
// Create a market with a future opening time (this should succeed)
await expect(
marketFactory.createCategoricalMarket({
...categoricalMarketParams,
openingTime: futureOpeningTime,
})
).to.emit(marketFactory, "NewMarket");
// Check that both markets were created
expect(await marketFactory.marketCount()).to.equal(2);
// Get the created markets
const markets = await marketFactory.allMarkets();
const pastMarket = await ethers.getContractAt("Market", markets[0]);
const futureMarket = await ethers.getContractAt("Market", markets[1]);
// Check the opening times of the created questions
const pastQuestionId = (await pastMarket.questionsIds())[0];
const futureQuestionId = (await futureMarket.questionsIds())[0];
const pastQuestionOpeningTime = await realitio.getOpeningTS(pastQuestionId);
const futureQuestionOpeningTime = await realitio.getOpeningTS(futureQuestionId);
// Both should be in the past and future respectively
expect(pastQuestionOpeningTime).to.be.lt(await time.latest());
expect(futureQuestionOpeningTime).to.be.gt(await time.latest());
// Demonstrate that the past market can be answered immediately
const answerer = (await ethers.getSigners())[1];
await realitio.connect(answerer).submitAnswer(
pastQuestionId,
ethers.encodeBytes32String("1"), // Assuming "1" is a valid answer
0, // Current bond
{ value: ethers.parseEther(MIN_BOND) }
);
// Check that the answer was accepted
const lastAnswerTime = await realitio.getAnswerTimestamp(pastQuestionId);
expect(lastAnswerTime).to.be.gt(0);
});
});
This test does the following:
It creates two markets: one with an opening time in the past and one in the future.
It verifies that both markets are created successfully (which demonstrates the bug, as the past market should ideally fail).
It checks the actual opening times of the Reality.eth questions for both markets.
It demonstrates that the market with the past opening time can be answered immediately, which shouldn't be possible in a properly functioning system.
Github username: -- Twitter username: -- Submission hash (on-chain): 0x4dc4a8bee494100f2103fb12adc9d2fb24a785ccbc5ec3e15d4a2e88f907c52c Severity: medium
Description:
Details
The Seer Protocol's
MarketFactory
contract creates prediction markets linked to Reality.eth questions. TheaskRealityQuestion
function is responsible for submitting these questions to Reality.eth.The
askRealityQuestion
function uses a user-providedopeningTime
parameter when creating or reusing Reality.eth questions. However, it doesn't validate this parameter against the current block timestamp, potentially allowing the creation of markets with questions that open in the past.Code Snippet
Impact
Scenario
openingTime
set to a past timestamp.Probability
High. This issue can be easily triggered by any user creating a market, either accidentally or intentionally.
Fix
Implement a check in the
askRealityQuestion
function to ensure theopeningTime
is in the future:Consider adding an upper bound to the openingTime as well, to prevent markets from being created too far in the future.
Poc
This test does the following: