Skip to content

Commit

Permalink
feat(sdk): allow oo client to fetch request by transaction and option… (
Browse files Browse the repository at this point in the history
UMAprotocol#3819)

* feat(sdk): allow oo client to fetch request by transaction and optional event index

Signed-off-by: David <[email protected]>

* improve(sdk): improve logic in setactiverequestbytransaction, improve ethers type exports

Signed-off-by: David <[email protected]>
  • Loading branch information
daywiss authored Feb 7, 2022
1 parent 34afe1c commit 5cd7e7b
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 15 deletions.
6 changes: 4 additions & 2 deletions packages/sdk/src/clients/optimisticOracle/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { OptimisticOracleEthers, OptimisticOracleEthers__factory } from "@uma/contracts-node";
import { OptimisticOracleEthers, OptimisticOracleEthers__factory, getOptimisticOracleAbi } from "@uma/contracts-node";
import type { SignerOrProvider, GetEventType } from "../..";
import { Event, BigNumberish } from "ethers";
import { Event, BigNumberish, utils } from "ethers";

export type Instance = OptimisticOracleEthers;
const Factory = OptimisticOracleEthers__factory;
Expand All @@ -9,6 +9,8 @@ export function connect(address: string, provider: SignerOrProvider): Instance {
return Factory.connect(address, provider);
}

export const contractInterface = new utils.Interface(getOptimisticOracleAbi());

export type RequestPrice = GetEventType<Instance, "RequestPrice">;
export type ProposePrice = GetEventType<Instance, "ProposePrice">;
export type DisputePrice = GetEventType<Instance, "DisputePrice">;
Expand Down
15 changes: 12 additions & 3 deletions packages/sdk/src/oracle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Commands on the client are currently:
- proposePrice
- disputePrice
- switchOrAddChain
- setActiveRequestByTransaction
These functions are all syncronous and can throw errors, but on success will return a string which represents the id of the command issued.
You can fetch a command through:
Expand All @@ -153,9 +154,11 @@ The oracle client is what manages state, emits state updates and lets you intera
### Contruction
This uses a factory for construction.
This uses a factory for construction.
See `oracle/types/state.PartialConfig` for your full configuration object.
See `oracle/types/state.State` for full application state object.
`oracle.client.factory(config: oracle.types.state.Config, emit: oracle.services.store.Emit): Client`
`oracle.client.factory(config: oracle.types.state.PartialConfig, emit: (state: oracle.types.state.State, prev: oracle.types.state.State) => void): Client`
### setUser
Expand All @@ -173,7 +176,7 @@ Clears the current user.
Tell the client what request you want to interact with.
`client.setActiveRequest(params: oracle.types.state.InputRequest): string`
`client.setActiveRequest(params: {chainId:number, requester: string; identifier: string, timestamp: number, ancillaryData: string }): string`
### approveCollateral
Expand All @@ -199,6 +202,12 @@ This requires the `metadata` option on the config for each chain is set.
`switchOrAddChain(): string`
### setActiveRequestByTransaction
Tell the client what request you want to interact with by specifying the transaction hash, chainId and optional eventIndex.
`client.setActiveRequestByTransaction(params: { chainId: number; transactionHash: string; eventIndex?: number }): string`
## Types
For detailed types, see `oracle/types/state.ts`. There are also a few types in `oracle/services/store/index.ts`.
Expand Down
5 changes: 4 additions & 1 deletion packages/sdk/src/oracle/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Store, { Emit } from "./store";
import type { state } from "./types";
import { InputRequest, User } from "./types/state";
import { Update } from "./services/update";
import { StateMachine } from "./services/statemachines/statemachine";
import { StateMachine, setActiveRequestByTransaction } from "./services/statemachines";
import { loop } from "../utils";
import { toWei } from "../across/utils";
import { defaultConfig } from "./utils";
Expand All @@ -23,6 +23,9 @@ export class Client {
const requester = ethers.utils.getAddress(params.requester);
return this.sm.types.setActiveRequest.create({ ...params, requester });
}
setActiveRequestByTransaction(params: setActiveRequestByTransaction.Params): string {
return this.sm.types.setActiveRequestByTransaction.create(params);
}
approveCollateral(): string {
const { checkTxIntervalSec } = this.store.read().chainConfig();
const request = this.store.read().request();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function Handlers(store: Store): GenericHandlers<Params, Memory> {
// we set multiplier to 1 so we dont grow the range on success, this tends to create more errors and slow down querying
memory.state = rangeSuccessDescending({ ...rangeState, multiplier: 1 });
} catch (err) {
memory.error = err;
memory.error = (err as unknown) as Error;
// the provider threw an error so we will reduce our range by moving startblock closer to endblock next iteration
memory.state = rangeFailureDescending(rangeState);
}
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/oracle/services/statemachines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * as switchOrAddChain from "./switchOrAddChain";
export * as pollActiveRequest from "./pollActiveRequest";
export * as fetchPastEvents from "./fetchPastEvents";
export * as pollNewEvents from "./pollNewEvents";
export * as setActiveRequestByTransaction from "./setActiveRequestByTransaction";

export * from "./statemachine";
export * from "./utils";
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function Handlers(store: Store): GenericHandlers<Params, Memory> {
}
} catch (err) {
// store an error for an iteration if we need to debug
memory.error = err;
memory.error = (err as unknown) as Error;
}
// just count how many iterations we do as a kind of sanity check
memory.iterations++;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import assert from "assert";
import { Update } from "../update";
import Store from "../../store";
import { Handlers as GenericHandlers } from "../../types/statemachine";
import { optimisticOracle } from "../../../clients";

// required exports for state machine
export type Params = { chainId: number; transactionHash: string; eventIndex?: number };
export type Memory = { error?: Error };
export function initMemory(): Memory {
return {};
}
export function Handlers(store: Store): GenericHandlers<Params, Memory> {
const update = new Update(store);
return {
async start(params: Params, memory: Memory) {
const { chainId, transactionHash, eventIndex = 0 } = params;

// have to do all of this to fetch the identifier, ancData, requester and timestamp from the request
const provider = store.read().provider(chainId);
const receipt = await provider.getTransactionReceipt(transactionHash);
const oracleAddress = store.read().oracleAddress(chainId);
// filter out logs that originate from oracle contract
const oracleLogs = receipt.logs.filter((log) => log.address.toLowerCase() === oracleAddress.toLowerCase());
// decode logs using abi
const decodedLogs = oracleLogs.map((log) => optimisticOracle.contractInterface.parseLog(log));

// this is the event we care about, we index into the appropriate oracle event generated from this tx
const log = decodedLogs[eventIndex];
// we dont actually know the type of the log, so we need to do some validation before continuing
assert(log, `Unable to find optimistic oracle event at ${transactionHash} eventIndex ${eventIndex}`);
assert(log.args, `Unable to find optimistic oracle event args at ${transactionHash} eventIndex ${eventIndex}`);
assert(
log.args.timestamp,
`Unable to find optimistic oracle event.timestamp at ${transactionHash} eventIndex ${eventIndex}`
);
assert(
log.args.requester,
`Unable to find optimistic oracle event.requester at ${transactionHash} eventIndex ${eventIndex}`
);
assert(
log.args.ancillaryData,
`Unable to find optimistic oracle event.ancillaryData at ${transactionHash} eventIndex ${eventIndex}`
);
assert(
log.args.identifier,
`Unable to find optimistic oracle event.identifier at ${transactionHash} eventIndex ${eventIndex}`
);

// we can parse out the necessary params to kick off fetching the state of the request
const requestInput = {
timestamp: log.args.timestamp,
requester: log.args.requester,
ancillaryData: log.args.ancillaryData,
identifier: log.args.identifier,
chainId,
};

store.write((write) => write.inputs().request(requestInput));

try {
// these could fail at any point if user isnt set, but thats ok, state machine will catch error, and use can inspect.
// this will rerun when user is set.
await update.oracle();
// get current time of chain when switching request
await update.currentTime();
await update.request();
await update.collateralProps();
// order is important, these should be last because they depend on user being set
await update.userCollateralBalance();
await update.oracleAllowance();
} catch (err) {
// its ok to ignore these errors
memory.error = (err as unknown) as Error;
}
return "done";
},
};
}
21 changes: 21 additions & 0 deletions packages/sdk/src/oracle/services/statemachines/statemachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as pollActiveRequest from "./pollActiveRequest";
import * as pollActiveUser from "./pollActiveUser";
import * as fetchPastEvents from "./fetchPastEvents";
import * as pollNewEvents from "./pollNewEvents";
import * as setActiveRequestByTransaction from "./setActiveRequestByTransaction";

/**
* StateMachine. This class will be used to handle all change requests by the user, including setting state which
Expand Down Expand Up @@ -46,6 +47,10 @@ export class StateMachine {
[ContextType.pollActiveUser]: ContextManager<pollActiveUser.Params, pollActiveUser.Memory>;
[ContextType.fetchPastEvents]: ContextManager<fetchPastEvents.Params, fetchPastEvents.Memory>;
[ContextType.pollNewEvents]: ContextManager<pollNewEvents.Params, pollNewEvents.Memory>;
[ContextType.setActiveRequestByTransaction]: ContextManager<
setActiveRequestByTransaction.Params,
setActiveRequestByTransaction.Memory
>;
};
constructor(private store: Store) {
// need to initizlie state types here manually for each new context type
Expand Down Expand Up @@ -116,6 +121,15 @@ export class StateMachine {
pollNewEvents.initMemory,
this.handleCreate
),
[ContextType.setActiveRequestByTransaction]: new ContextManager<
setActiveRequestByTransaction.Params,
setActiveRequestByTransaction.Memory
>(
ContextType.setActiveRequestByTransaction,
setActiveRequestByTransaction.Handlers(store),
setActiveRequestByTransaction.initMemory,
this.handleCreate
),
};
}
private saveContext(context: Context<unknown, unknown & Memory>) {
Expand Down Expand Up @@ -233,6 +247,13 @@ export class StateMachine {
);
break;
}
case ContextType.setActiveRequestByTransaction: {
next = await this.types[context.type].step(
(context as unknown) as Context<setActiveRequestByTransaction.Params, setActiveRequestByTransaction.Memory>,
now
);
break;
}
default: {
throw new Error("Unable to handle type: " + context.type);
}
Expand Down
14 changes: 8 additions & 6 deletions packages/sdk/src/oracle/store/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export default class Read {
assert(chainId, "ChainId is not set");
return chainId;
};
requestChain = (): Partial<Chain> => {
const chainId = this.requestChainId();
requestChain = (optionalChainId?: number): Partial<Chain> => {
const chainId = optionalChainId || this.requestChainId();
const chain = this.state?.chains?.[chainId];
assert(chain, "Chain not set");
return chain;
Expand All @@ -58,8 +58,8 @@ export default class Read {
assert(address, "User address is not set");
return address;
};
oracleAddress = (): string => {
const chain = this.requestChain();
oracleAddress = (optionalChainId?: number): string => {
const chain = this.requestChain(optionalChainId);
const address = chain?.optimisticOracle?.address;
assert(address, "Optimistic oracle address not set");
return address;
Expand Down Expand Up @@ -125,8 +125,10 @@ export default class Read {
assert(result, "Token not supported on chain " + chainId);
return result;
};
command = (id: string): Context<unknown, unknown & Memory> | undefined => {
return this.state?.commands?.[id];
command = (id: string): Context<unknown, unknown & Memory> => {
const result = this.state?.commands?.[id];
assert(result, "Unable to find command " + id);
return result;
};
tokenService = (chainId: number, address: string): Erc20 => {
const result = this.state?.services?.chains?.[chainId]?.erc20s?.[address];
Expand Down
32 changes: 32 additions & 0 deletions packages/sdk/src/oracle/tests/client.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,36 @@ describe("Oracle Client", function () {
assert.ok(result[types.state.Flag.RequestSettled]);
assert.ok(result[types.state.Flag.InsufficientApproval]);
});
test("setActiveRequestByTransaction", async function (done) {
const transactionHash = "0x91720719f4768e10849ebb5f41690488f7060e10534c5c4f15e69b7dc494502a";
const chainId = 1;
const id = client.setActiveRequestByTransaction({ transactionHash, chainId });
const expectedExpiration = 1630368843;

const stop = setInterval(() => {
const result = store.read().command(id);
if (result.done) {
assert.ok(!result.error);
assert.ok(store.read().request());
assert.equal(store.read().request().expirationTime, expectedExpiration);
clearInterval(stop);
done();
}
}, 1000);
});
test("setActiveRequestByTransaction failure", async function (done) {
const transactionHash = "0x91720719f4768e10849ebb5f41690488f7060e10534c5c4f15e69b7dc494502a";
const chainId = 1;
const eventIndex = 1;
const id = client.setActiveRequestByTransaction({ transactionHash, chainId, eventIndex });

const stop = setInterval(() => {
const result = store.read().command(id);
if (result.done) {
assert.ok(result.error);
clearInterval(stop);
done();
}
}, 1000);
});
});
7 changes: 6 additions & 1 deletion packages/sdk/src/oracle/types/ethers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { Event } from "ethers";
import { LogDescription, Interface } from "@ethersproject/abi";

export type { Signer, BigNumber, BigNumberish, Contract } from "ethers";
export type { Overrides } from "@ethersproject/contracts";
export { Provider, JsonRpcSigner, JsonRpcProvider, Web3Provider, FallbackProvider } from "@ethersproject/providers";
export { TransactionRequest, TransactionReceipt, TransactionResponse } from "@ethersproject/abstract-provider";
export type { Event };
export type { Event, LogDescription };

// taken from ethers code https://github.com/ethers-io/ethers.js/blob/master/packages/abi/src.ts/interface.ts#L654
export type Log = Parameters<Interface["parseLog"]>[0];
1 change: 1 addition & 0 deletions packages/sdk/src/oracle/types/statemachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum ContextType {
pollActiveUser = "pollActiveUser",
fetchPastEvents = "fetchPastEvents",
pollNewEvents = "pollNewEvents",
setActiveRequestByTransaction = "setActiveRequestByTransaction",
}

export type ContextProps = {
Expand Down

0 comments on commit 5cd7e7b

Please sign in to comment.