Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/evm-contracts/contracts/DuelOutcomeOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ contract DuelOutcomeOracle is AccessControl {
if (duel.status != DuelStatus.LOCKED) revert DuelNotLocked();
if (block.timestamp < duel.betCloseTs) revert BettingWindowActive();
if (winner != Side.A && winner != Side.B) revert InvalidWinner();
if (duelEndTs < duel.betCloseTs) revert InvalidDuelEnd();
_validateResultTime(duel, duelEndTs);

id = proposalId(duelKey, resultHash, replayHash);
if (proposals[id].exists) revert ProposalExists();
Expand Down Expand Up @@ -345,7 +345,7 @@ contract DuelOutcomeOracle is AccessControl {
_requireSettleable(duel);
if (duel.status != DuelStatus.CHALLENGED) revert NotChallenged();
if (winner != Side.A && winner != Side.B) revert InvalidWinner();
if (duelEndTs < duel.betCloseTs) revert InvalidDuelEnd();
_validateResultTime(duel, duelEndTs);

delete proposals[duel.activeProposalId];

Expand Down Expand Up @@ -407,4 +407,10 @@ contract DuelOutcomeOracle is AccessControl {
if (duel.status == DuelStatus.RESOLVED) revert DuelAlreadyResolved();
if (duel.status == DuelStatus.CANCELLED) revert DuelAlreadyCancelled();
}

function _validateResultTime(DuelState storage duel, uint64 duelEndTs) private view {
if (duelEndTs < duel.betCloseTs) revert InvalidDuelEnd();
if (duelEndTs < duel.duelStartTs) revert InvalidDuelEnd();
if (duelEndTs > block.timestamp) revert InvalidDuelEnd();
}
}
13 changes: 10 additions & 3 deletions packages/evm-contracts/scripts/simulate-adversarial-localnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,17 +415,24 @@ async function runArbitrageScenario(
throw new Error(`arb bot stake is not discounted: ${combinedStake.toString()}`);
}

const latestBlock = await fixture.provider.getBlock("latest");
const now = BigInt(latestBlock?.timestamp ?? Math.floor(Date.now() / 1000));
await lockMarket(fixture, duel, "arbitrage");
const storedDuel = await fixture.oracle.getDuel(duel);
await fixture.provider.send("evm_setNextBlockTimestamp", [
Number(storedDuel.duelStartTs + 1n),
]);
await fixture.provider.send("evm_mine", []);
const resultBlock = await fixture.provider.getBlock("latest");
const duelEndTs = BigInt(
resultBlock?.timestamp ?? Number(storedDuel.duelStartTs + 1n),
);
await (
await fixture.oracle.connect(fixture.reporter).proposeResult(
duel,
SIDE_A,
77,
ethers.keccak256(ethers.toUtf8Bytes("arb-replay")),
ethers.keccak256(ethers.toUtf8Bytes("arb-result")),
now + 180n,
duelEndTs,
"arb-resolved",
)
).wait();
Expand Down
6 changes: 5 additions & 1 deletion packages/evm-contracts/scripts/simulate-localnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,17 @@ async function main() {
"localnet-simulation-locked",
DUEL_STATUS_LOCKED,
);
await ethers.provider.send("evm_setNextBlockTimestamp", [Number(now + 121n)]);
await ethers.provider.send("evm_mine", []);
const resultBlock = await ethers.provider.getBlock("latest");
const duelEndTs = BigInt(resultBlock?.timestamp ?? Number(now + 121n));
await oracle.connect(reporter).proposeResult(
duel,
SIDE_A,
42,
ethers.keccak256(ethers.toUtf8Bytes("replay")),
ethers.keccak256(ethers.toUtf8Bytes("result")),
now + 180n,
duelEndTs,
"resolved",
);
await ethers.provider.send("evm_increaseTime", [3600]);
Expand Down
19 changes: 12 additions & 7 deletions packages/evm-contracts/test/ExploitSuite.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,14 @@ contract ExploitSuiteTest is Test {
"valid",
DuelOutcomeOracle.DuelStatus.LOCKED
);
vm.warp(nowTs + 150);
oracle.proposeResult(
duelKey,
DuelOutcomeOracle.Side.A,
123,
bytes32(0),
bytes32(0),
uint64(block.timestamp + 200),
nowTs + 150,
"proposed"
);
vm.stopPrank();
Expand Down Expand Up @@ -159,14 +160,15 @@ contract ExploitSuiteTest is Test {
DuelOutcomeOracle.DuelStatus.LOCKED
);

vm.warp(nowTs + 150);
vm.prank(reporter);
oracle.proposeResult(
duelKey,
DuelOutcomeOracle.Side.A,
123,
bytes32(0),
bytes32(0),
uint64(block.timestamp + 200),
nowTs + 150,
"proposed"
);

Expand Down Expand Up @@ -300,15 +302,15 @@ contract ExploitSuiteTest is Test {
DuelOutcomeOracle.DuelStatus.LOCKED
);

vm.warp(nowTs + 101);
vm.warp(nowTs + 150);
vm.prank(reporter);
oracle.proposeResult(
duelKey,
DuelOutcomeOracle.Side.A,
1,
bytes32(0),
bytes32(0),
uint64(block.timestamp + 50),
nowTs + 150,
"proposed"
);

Expand Down Expand Up @@ -384,14 +386,15 @@ contract ExploitSuiteTest is Test {
vm.prank(alice);
clob.cancelOrder(duelKey, 0, 1);

vm.warp(nowTs + 150);
vm.prank(reporter);
oracle.proposeResult(
duelKey,
DuelOutcomeOracle.Side.A,
1,
bytes32(0),
bytes32(0),
uint64(block.timestamp + 150),
nowTs + 150,
"proposed"
);

Expand Down Expand Up @@ -446,14 +449,15 @@ contract ExploitSuiteTest is Test {
DuelOutcomeOracle.DuelStatus.LOCKED
);

vm.warp(nowTs + 150);
vm.prank(reporter);
oracle.proposeResult(
duelKey,
DuelOutcomeOracle.Side.A,
1,
bytes32(0),
bytes32(0),
uint64(block.timestamp + 50),
nowTs + 150,
"proposed"
);

Expand Down Expand Up @@ -514,8 +518,9 @@ contract ExploitSuiteTest is Test {
nowTs, nowTs + 100, nowTs + 150,
"locked", DuelOutcomeOracle.DuelStatus.LOCKED
);
vm.warp(nowTs + 150);
vm.prank(reporter);
oracle.proposeResult(duelKey, DuelOutcomeOracle.Side.B, 1, bytes32(0), bytes32(0), uint64(block.timestamp + 50), "p");
oracle.proposeResult(duelKey, DuelOutcomeOracle.Side.B, 1, bytes32(0), bytes32(0), nowTs + 150, "p");
vm.warp(block.timestamp + disputeWindow + 1);
vm.prank(finalizer);
oracle.finalizeResult(duelKey, "resolved");
Expand Down
4 changes: 3 additions & 1 deletion packages/evm-contracts/test/GoldClobSettlement.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ contract GoldClobSettlementTest is Test {

function _resolveDuel(bytes32 duel, DuelOutcomeOracle.Side winner) private {
_lockDuel(duel);
DuelOutcomeOracle.DuelState memory d = oracle.getDuel(duel);
if (block.timestamp < d.duelStartTs) vm.warp(d.duelStartTs);

vm.prank(reporter);
oracle.proposeResult(
Expand All @@ -221,7 +223,7 @@ contract GoldClobSettlementTest is Test {
42,
_hashLabel("replay"),
_hashLabel("result"),
uint64(block.timestamp + 180),
d.duelStartTs,
"resolved"
);
vm.warp(block.timestamp + 3_600);
Expand Down
3 changes: 2 additions & 1 deletion packages/evm-contracts/test/GoldClobStress.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ contract GoldClobStressTest is Test {
vm.prank(reporter);
oracle.upsertDuel(duel, d.participantAHash, d.participantBHash,
d.betOpenTs, d.betCloseTs, d.duelStartTs, "", DuelOutcomeOracle.DuelStatus.LOCKED);
if (block.timestamp < d.duelStartTs) vm.warp(d.duelStartTs);
vm.prank(reporter);
oracle.proposeResult(duel, winner, 42, keccak256("r"), keccak256("h"),
uint64(block.timestamp + 100), "");
d.duelStartTs, "");
vm.warp(block.timestamp + 3_601);
vm.prank(finalizer);
oracle.finalizeResult(duel, "");
Expand Down
70 changes: 61 additions & 9 deletions packages/evm-contracts/test/OracleFinality.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ contract OracleFinalityTest is Test {
bytes32 resultHash = keccak256(abi.encode("result", key));
bytes32 replayHash = keccak256(abi.encode("replay", key));

// Warp past betCloseTs (2_000) so proposing is allowed
if (block.timestamp < 2_001) vm.warp(2_001);
// Warp past duelStartTs so a result can exist.
if (block.timestamp < 4_000) vm.warp(4_000);

vm.prank(reporter);
oracle.proposeResult(key, DuelOutcomeOracle.Side.A, 42, replayHash, resultHash, 4_000, "");
Expand Down Expand Up @@ -111,8 +111,8 @@ contract OracleFinalityTest is Test {

bytes32 rh = keccak256("r");
bytes32 rp = keccak256("p");
// Warp past betCloseTs (2_000) so proposing is allowed
vm.warp(2_001);
// Warp past duelStartTs so a result can exist.
vm.warp(4_000);
vm.prank(reporter);
o.proposeResult(key, DuelOutcomeOracle.Side.A, 1, rp, rh, 4_000, "");

Expand Down Expand Up @@ -202,7 +202,7 @@ contract OracleFinalityTest is Test {

function test_noWinnerInProposed() public {
bytes32 key = _createLockedDuel(302);
vm.warp(2_001);
vm.warp(4_000);
vm.prank(reporter);
oracle.proposeResult(key, DuelOutcomeOracle.Side.B, 99,
keccak256("rp"), keccak256("rh"), 4_000, "");
Expand All @@ -212,7 +212,7 @@ contract OracleFinalityTest is Test {

function test_noWinnerInChallenged() public {
bytes32 key = _createLockedDuel(303);
vm.warp(2_001);
vm.warp(4_000);
vm.prank(reporter);
oracle.proposeResult(key, DuelOutcomeOracle.Side.A, 1,
keccak256("rp2"), keccak256("rh2"), 4_000, "");
Expand All @@ -222,6 +222,58 @@ contract OracleFinalityTest is Test {
assertEq(uint8(d.winner), 0);
}

function test_proposeRejectsResultBeforeDuelStart() public {
bytes32 key = _createLockedDuel(305);
vm.warp(3_000);

vm.prank(reporter);
vm.expectRevert(DuelOutcomeOracle.InvalidDuelEnd.selector);
oracle.proposeResult(key, DuelOutcomeOracle.Side.A, 1,
keccak256("early-replay"), keccak256("early-result"), 2_000, "");
}

function test_proposeRejectsFutureResultEnd() public {
bytes32 key = _createLockedDuel(306);
vm.warp(3_000);

vm.prank(reporter);
vm.expectRevert(DuelOutcomeOracle.InvalidDuelEnd.selector);
oracle.proposeResult(key, DuelOutcomeOracle.Side.A, 1,
keccak256("future-replay"), keccak256("future-result"), 3_001, "");
}

function test_reproposeRejectsResultBeforeDuelStart() public {
bytes32 key = _createLockedDuel(307);
vm.warp(4_000);

vm.prank(reporter);
oracle.proposeResult(key, DuelOutcomeOracle.Side.A, 1,
keccak256("replay"), keccak256("result"), 4_000, "");
vm.prank(challenger);
oracle.challengeResult(key, "");

vm.prank(reporter);
vm.expectRevert(DuelOutcomeOracle.InvalidDuelEnd.selector);
oracle.reproposeResult(key, DuelOutcomeOracle.Side.A, 2,
keccak256("early-replay-2"), keccak256("early-result-2"), 2_000, "");
}

function test_reproposeRejectsFutureResultEnd() public {
bytes32 key = _createLockedDuel(308);
vm.warp(4_000);

vm.prank(reporter);
oracle.proposeResult(key, DuelOutcomeOracle.Side.A, 1,
keccak256("replay-future"), keccak256("result-future"), 4_000, "");
vm.prank(challenger);
oracle.challengeResult(key, "");

vm.prank(reporter);
vm.expectRevert(DuelOutcomeOracle.InvalidDuelEnd.selector);
oracle.reproposeResult(key, DuelOutcomeOracle.Side.A, 2,
keccak256("future-replay-2"), keccak256("future-result-2"), 4_001, "");
}

function test_noWinnerInCancelled() public {
bytes32 key = _createLockedDuel(304);
vm.prank(pauser);
Expand Down Expand Up @@ -286,7 +338,7 @@ contract OracleFinalityTest is Test {

function test_onlyReporterCanPropose() public {
bytes32 key = _createLockedDuel(501);
vm.warp(2_001);
vm.warp(4_000);
vm.prank(other);
vm.expectRevert();
oracle.proposeResult(key, DuelOutcomeOracle.Side.A, 1,
Expand All @@ -295,7 +347,7 @@ contract OracleFinalityTest is Test {

function test_onlyFinalizerCanFinalize() public {
bytes32 key = _createLockedDuel(502);
vm.warp(2_001);
vm.warp(4_000);
vm.prank(reporter);
oracle.proposeResult(key, DuelOutcomeOracle.Side.A, 1,
keccak256("r3"), keccak256("h3"), 4_000, "");
Expand All @@ -308,7 +360,7 @@ contract OracleFinalityTest is Test {

function test_onlyChallengerCanChallenge() public {
bytes32 key = _createLockedDuel(503);
vm.warp(2_001);
vm.warp(4_000);
vm.prank(reporter);
oracle.proposeResult(key, DuelOutcomeOracle.Side.B, 1,
keccak256("r4"), keccak256("h4"), 4_000, "");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,12 @@ contract PmPerpsAdversarialTest is Test {
DuelOutcomeOracle.DuelStatus.LOCKED
);

vm.warp(setupDuelStart);
vm.prank(reporter);
duelOracle.proposeResult(
duelKey, DuelOutcomeOracle.Side(1), 42,
keccak256("r"), keccak256("h"),
uint64(block.timestamp), ""
setupDuelStart, ""
);

vm.warp(block.timestamp + 4000);
Expand Down
4 changes: 3 additions & 1 deletion packages/evm-contracts/test/fuzz/GoldClobFuzz.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ contract GoldClobFuzzTest is Test {

function _resolveDuel(bytes32 duel, DuelOutcomeOracle.Side winner) private {
_lockDuel(duel);
DuelOutcomeOracle.DuelState memory d = oracle.getDuel(duel);
if (block.timestamp < d.duelStartTs) vm.warp(d.duelStartTs);

vm.prank(reporter);
oracle.proposeResult(
Expand All @@ -252,7 +254,7 @@ contract GoldClobFuzzTest is Test {
42,
_hashLabel("replay"),
_hashLabel("result"),
uint64(block.timestamp + 180),
d.duelStartTs,
"resolved"
);
vm.warp(block.timestamp + 3_600);
Expand Down
9 changes: 6 additions & 3 deletions packages/evm-contracts/test/integration/FullLifecycle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,10 @@ contract FullLifecycleTest is Test {
d.betOpenTs, d.betCloseTs, d.duelStartTs, "",
DuelOutcomeOracle.DuelStatus.LOCKED);

if (block.timestamp < d.duelStartTs) vm.warp(d.duelStartTs);
vm.prank(reporter);
duelOracle.proposeResult(duel, DuelOutcomeOracle.Side.A, 42,
keccak256("r"), keccak256("h"), uint64(block.timestamp + 100), "");
keccak256("r"), keccak256("h"), d.duelStartTs, "");

vm.warp(block.timestamp + 3601);
vm.prank(finalizer);
Expand Down Expand Up @@ -259,10 +260,11 @@ contract FullLifecycleTest is Test {
duelOracle.upsertDuel(duels[i], d.participantAHash, d.participantBHash,
d.betOpenTs, d.betCloseTs, d.duelStartTs, "",
DuelOutcomeOracle.DuelStatus.LOCKED);
if (block.timestamp < d.duelStartTs) vm.warp(d.duelStartTs);
vm.prank(reporter);
duelOracle.proposeResult(duels[i], DuelOutcomeOracle.Side.A, 42,
keccak256(abi.encodePacked("r", i)), keccak256(abi.encodePacked("h", i)),
uint64(block.timestamp + 100), "");
d.duelStartTs, "");
vm.warp(block.timestamp + 3601);
vm.prank(finalizer);
duelOracle.finalizeResult(duels[i], "");
Expand Down Expand Up @@ -318,9 +320,10 @@ contract FullLifecycleTest is Test {
duelOracle.upsertDuel(duel, d.participantAHash, d.participantBHash,
d.betOpenTs, d.betCloseTs, d.duelStartTs, "",
DuelOutcomeOracle.DuelStatus.LOCKED);
if (block.timestamp < d.duelStartTs) vm.warp(d.duelStartTs);
vm.prank(reporter);
duelOracle.proposeResult(duel, DuelOutcomeOracle.Side.A, 42,
keccak256("r"), keccak256("h"), uint64(block.timestamp + 100), "");
keccak256("r"), keccak256("h"), d.duelStartTs, "");
vm.warp(block.timestamp + 3601);
vm.prank(finalizer);
duelOracle.finalizeResult(duel, "");
Expand Down
Loading
Loading