mirror of
https://github.com/luxfi/uma.git
synced 2026-10-30 15:14:44 +00:00
215 lines
9.8 KiB
TypeScript
215 lines
9.8 KiB
TypeScript
import { ExpandedERC20Ethers, MockOracleAncillaryEthers, OptimisticOracleV3Ethers } from "@uma/contracts-node";
|
|
import { createNewLogger, spyLogIncludes, spyLogLevel, SpyTransport } from "@uma/financial-templates-lib";
|
|
import { assert } from "chai";
|
|
import sinon from "sinon";
|
|
import { defaultLiveness } from "./constants";
|
|
import {
|
|
ContractTransaction,
|
|
getBlockNumberFromTx,
|
|
hardhatTime,
|
|
hre,
|
|
Provider,
|
|
Signer,
|
|
toUtf8Bytes,
|
|
toUtf8String,
|
|
} from "./utils";
|
|
import { umaEcosystemFixture } from "./fixtures/UmaEcosystem.Fixture";
|
|
import { optimisticOracleV3Fixture } from "./fixtures/OptimisticOracleV3.Fixture";
|
|
import { MonitoringParams, BotModes } from "../src/monitor-oo-v3/common";
|
|
import { monitorAssertions } from "../src/monitor-oo-v3/MonitorAssertions";
|
|
import { monitorDisputes } from "../src/monitor-oo-v3/MonitorDisputes";
|
|
import { monitorSettlements } from "../src/monitor-oo-v3/MonitorSettlements";
|
|
|
|
const ethers = hre.ethers;
|
|
|
|
// Get assertionId from the first AssertionMade event in the assertion transaction.
|
|
const getAssertionId = async (
|
|
tx: ContractTransaction,
|
|
optimisticOracleV3: OptimisticOracleV3Ethers
|
|
): Promise<string> => {
|
|
await tx.wait();
|
|
return (
|
|
await optimisticOracleV3.queryFilter(optimisticOracleV3.filters.AssertionMade(), tx.blockNumber, tx.blockNumber)
|
|
)[0].args.assertionId;
|
|
};
|
|
|
|
// Create monitoring params for single block to pass to monitor modules.
|
|
const createMonitoringParams = async (blockNumber: number): Promise<MonitoringParams> => {
|
|
// Bot modes are not used as we are calling monitor modules directly.
|
|
const botModes: BotModes = {
|
|
assertionsEnabled: false,
|
|
disputesEnabled: false,
|
|
settlementsEnabled: false,
|
|
};
|
|
return {
|
|
provider: ethers.provider as Provider,
|
|
chainId: (await ethers.provider.getNetwork()).chainId,
|
|
blockRange: { start: blockNumber, end: blockNumber },
|
|
pollingDelay: 0,
|
|
botModes,
|
|
};
|
|
};
|
|
|
|
describe("OptimisticOracleV3Monitor", function () {
|
|
let mockOracle: MockOracleAncillaryEthers;
|
|
let bondToken: ExpandedERC20Ethers;
|
|
let optimisticOracleV3: OptimisticOracleV3Ethers;
|
|
let deployer: Signer;
|
|
let asserter: Signer;
|
|
let disputer: Signer;
|
|
|
|
const claim = toUtf8Bytes("This is just a test claim");
|
|
|
|
beforeEach(async function () {
|
|
// Signer from ethers and hardhat-ethers are not version compatible, thus, we cannot use the SignerWithAddress.
|
|
[deployer, asserter, disputer] = (await ethers.getSigners()) as Signer[];
|
|
|
|
// Get contract instances.
|
|
mockOracle = (await umaEcosystemFixture()).mockOracle;
|
|
const optimisticOracleV3Contracts = await optimisticOracleV3Fixture();
|
|
bondToken = optimisticOracleV3Contracts.bondToken;
|
|
optimisticOracleV3 = optimisticOracleV3Contracts.optimisticOracleV3;
|
|
|
|
// Fund asserter and disputer with minimum bond amount and approve Optimistic Oracle V3 to spend bond tokens.
|
|
const minimumBondAmount = await optimisticOracleV3.getMinimumBond(bondToken.address);
|
|
await bondToken.addMinter(await deployer.getAddress());
|
|
await bondToken.mint(await asserter.getAddress(), minimumBondAmount);
|
|
await bondToken.mint(await disputer.getAddress(), minimumBondAmount);
|
|
await bondToken.connect(asserter).approve(optimisticOracleV3.address, minimumBondAmount);
|
|
await bondToken.connect(disputer).approve(optimisticOracleV3.address, minimumBondAmount);
|
|
});
|
|
it("Monitor assertion", async function () {
|
|
// Make assertion.
|
|
const assertionTx = await optimisticOracleV3
|
|
.connect(asserter)
|
|
.assertTruthWithDefaults(claim, await asserter.getAddress());
|
|
const assertionBlockNumber = await getBlockNumberFromTx(assertionTx);
|
|
const assertionId = await getAssertionId(assertionTx, optimisticOracleV3);
|
|
|
|
// Call monitorAssertions directly for the block when the assertion was made.
|
|
const spy = sinon.spy();
|
|
const spyLogger = createNewLogger([new SpyTransport({}, { spy: spy })]);
|
|
await monitorAssertions(spyLogger, await createMonitoringParams(assertionBlockNumber));
|
|
|
|
// When calling monitoring module directly there should be only one log (index 0) with the assertion caught by spy.
|
|
assert.equal(spy.getCall(0).lastArg.at, "OOv3Monitor");
|
|
assert.equal(spy.getCall(0).lastArg.message, "Assertion made 🙋");
|
|
assert.equal(spyLogLevel(spy, 0), "warn");
|
|
assert.isTrue(spyLogIncludes(spy, 0, assertionId));
|
|
assert.isTrue(spyLogIncludes(spy, 0, assertionTx.hash));
|
|
assert.isTrue(spyLogIncludes(spy, 0, toUtf8String(claim)));
|
|
assert.equal(spy.getCall(0).lastArg.notificationPath, "optimistic-oracle");
|
|
assert.equal(
|
|
JSON.stringify(spy.getCall(0).lastArg.discordPaths),
|
|
JSON.stringify(["oo-fact-checking", "oo-events"])
|
|
);
|
|
});
|
|
it("Monitor truthful settlement", async function () {
|
|
// Make assertion.
|
|
const assertionTx = await optimisticOracleV3
|
|
.connect(asserter)
|
|
.assertTruthWithDefaults(claim, await asserter.getAddress());
|
|
const assertionId = await getAssertionId(assertionTx, optimisticOracleV3);
|
|
|
|
// Settle assertion after the liveness period.
|
|
await hardhatTime.increase(defaultLiveness);
|
|
const settlementTx = await optimisticOracleV3.connect(asserter).settleAssertion(assertionId);
|
|
const settlementBlockNumber = await getBlockNumberFromTx(settlementTx);
|
|
|
|
// Call monitorSettlements directly for the block when the settlement was made.
|
|
const spy = sinon.spy();
|
|
const spyLogger = createNewLogger([new SpyTransport({}, { spy: spy })]);
|
|
await monitorSettlements(spyLogger, await createMonitoringParams(settlementBlockNumber));
|
|
|
|
// When calling monitoring module directly there should be only one log (index 0) with the settlement caught by spy.
|
|
assert.equal(spy.getCall(0).lastArg.at, "OOv3Monitor");
|
|
assert.equal(spy.getCall(0).lastArg.message, "Assertion settled 🔗");
|
|
assert.equal(spyLogLevel(spy, 0), "info");
|
|
assert.isTrue(spyLogIncludes(spy, 0, assertionId));
|
|
assert.isTrue(spyLogIncludes(spy, 0, settlementTx.hash));
|
|
assert.isTrue(spyLogIncludes(spy, 0, toUtf8String(claim)));
|
|
assert.isTrue(spyLogIncludes(spy, 0, "Result: assertion was true"));
|
|
assert.equal(spy.getCall(0).lastArg.notificationPath, "optimistic-oracle");
|
|
assert.equal(
|
|
JSON.stringify(spy.getCall(0).lastArg.discordPaths),
|
|
JSON.stringify(["oo-fact-checking", "oo-events"])
|
|
);
|
|
});
|
|
it("Monitor dispute", async function () {
|
|
// Make assertion.
|
|
const assertionTx = await optimisticOracleV3
|
|
.connect(asserter)
|
|
.assertTruthWithDefaults(claim, await asserter.getAddress());
|
|
const assertionId = await getAssertionId(assertionTx, optimisticOracleV3);
|
|
|
|
// Dispute assertion.
|
|
const disputeTx = await optimisticOracleV3
|
|
.connect(disputer)
|
|
.disputeAssertion(assertionId, await disputer.getAddress());
|
|
const disputeBlockNumber = await getBlockNumberFromTx(disputeTx);
|
|
|
|
// Call monitorDisputes directly for the block when the dispute was made.
|
|
const spy = sinon.spy();
|
|
const spyLogger = createNewLogger([new SpyTransport({}, { spy: spy })]);
|
|
await monitorDisputes(spyLogger, await createMonitoringParams(disputeBlockNumber));
|
|
|
|
// When calling monitoring module directly there should be only one log (index 0) with the dispute caught by spy.
|
|
assert.equal(spy.getCall(0).lastArg.at, "OOv3Monitor");
|
|
assert.equal(spy.getCall(0).lastArg.message, "Assertion disputed ❌");
|
|
assert.equal(spyLogLevel(spy, 0), "error");
|
|
assert.isTrue(spyLogIncludes(spy, 0, assertionId));
|
|
assert.isTrue(spyLogIncludes(spy, 0, disputeTx.hash));
|
|
assert.isTrue(spyLogIncludes(spy, 0, toUtf8String(claim)));
|
|
assert.equal(spy.getCall(0).lastArg.notificationPath, "optimistic-oracle");
|
|
assert.equal(
|
|
JSON.stringify(spy.getCall(0).lastArg.discordPaths),
|
|
JSON.stringify(["oo-fact-checking", "oo-events"])
|
|
);
|
|
});
|
|
it("Monitor settlement of false assertion", async function () {
|
|
// Make assertion.
|
|
const assertionTx = await optimisticOracleV3
|
|
.connect(asserter)
|
|
.assertTruthWithDefaults(claim, await asserter.getAddress());
|
|
const assertionId = await getAssertionId(assertionTx, optimisticOracleV3);
|
|
|
|
// Dispute assertion.
|
|
const disputeTx = await optimisticOracleV3
|
|
.connect(disputer)
|
|
.disputeAssertion(assertionId, await disputer.getAddress());
|
|
|
|
// Get oracle request from the first PriceRequestAdded event in the dispute transaction.
|
|
const oracleRequest = (
|
|
await mockOracle.queryFilter(mockOracle.filters.PriceRequestAdded(), disputeTx.blockNumber, disputeTx.blockNumber)
|
|
)[0].args;
|
|
|
|
// Resolve assertion as false.
|
|
await mockOracle
|
|
.connect(disputer)
|
|
.pushPrice(oracleRequest.identifier, oracleRequest.time, oracleRequest.ancillaryData, 0);
|
|
|
|
// Settle assertion.
|
|
const settlementTx = await optimisticOracleV3.connect(disputer).settleAssertion(assertionId);
|
|
const settlementBlockNumber = await getBlockNumberFromTx(settlementTx);
|
|
|
|
// Call monitorSettlements directly for the block when the settlement was made.
|
|
const spy = sinon.spy();
|
|
const spyLogger = createNewLogger([new SpyTransport({}, { spy: spy })]);
|
|
await monitorSettlements(spyLogger, await createMonitoringParams(settlementBlockNumber));
|
|
|
|
// When calling monitoring module directly there should be only one log (index 0) with the settlement caught by spy.
|
|
assert.equal(spy.getCall(0).lastArg.at, "OOv3Monitor");
|
|
assert.equal(spy.getCall(0).lastArg.message, "Assertion settled 🔗");
|
|
assert.equal(spyLogLevel(spy, 0), "info");
|
|
assert.isTrue(spyLogIncludes(spy, 0, assertionId));
|
|
assert.isTrue(spyLogIncludes(spy, 0, settlementTx.hash));
|
|
assert.isTrue(spyLogIncludes(spy, 0, toUtf8String(claim)));
|
|
assert.isTrue(spyLogIncludes(spy, 0, "Result: assertion was false"));
|
|
assert.equal(spy.getCall(0).lastArg.notificationPath, "optimistic-oracle");
|
|
assert.equal(
|
|
JSON.stringify(spy.getCall(0).lastArg.discordPaths),
|
|
JSON.stringify(["oo-fact-checking", "oo-events"])
|
|
);
|
|
});
|
|
});
|