From 4ab1e68b5ca9f80f56801a892a76dfabaa82a793 Mon Sep 17 00:00:00 2001 From: ryanwolhuter Date: Wed, 25 Oct 2023 09:37:59 +0200 Subject: [PATCH] add doc comments Signed-off-by: ryanwolhuter --- src/plugins/oSnap/types.ts | 88 +++++++++++++++++++++- src/plugins/oSnap/utils/getters.ts | 114 ++++++++++++++++------------- 2 files changed, 148 insertions(+), 54 deletions(-) diff --git a/src/plugins/oSnap/types.ts b/src/plugins/oSnap/types.ts index 893751d3..79459993 100644 --- a/src/plugins/oSnap/types.ts +++ b/src/plugins/oSnap/types.ts @@ -242,6 +242,17 @@ export type OsnapPluginData = { safe: GnosisSafe | null; }; +/** + * Represents the data associated with an assertion on the Optimistic Oracle V3 subgraph. + * + * @field `assertionId` field is the id of the assertion. + * @field `expirationTime` field is the time that the assertion's challenge period ends. + * @field `assertionHash` field is the transaction hash from when the assertion was made. + * @field `settlementHash` field is the transaction hash from when the assertion was settled. + * @field `disputeHash` field is the transaction hash from when the assertion was disputed. + * @field `assertionLogIndex` field is the log index of the transaction from when the assertion was made. + * @field `settlementResolution` field is whether or not the assertion was resolved in favor of the asserter. + */ export type AssertionGql = { assertionId: string; expirationTime: string; @@ -252,6 +263,15 @@ export type AssertionGql = { settlementResolution: boolean | null; }; +/** + * Represents the configuration of the Optimistic Governor module contract that was deployed for a given Safe. + * + * @field `moduleAddress` field is the address of the specific Optimistic Governor module contract that was deployed for a given Safe. + * @field `oracleAddress` field is the address of the Optimistic Oracle V3 contract. + * @field `rules` rules for this Optimistic Governor contract. + * @field `minimumBond` field is the minimum bond that is required for an assertion to be made on this Optimistic Governor contract. + * @field `challengePeriod` field is the challenge period that is required for an assertion to be made on this Optimistic Governor contract. + */ export type OGModuleDetails = { moduleAddress: string; oracleAddress: string; @@ -260,6 +280,14 @@ export type OGModuleDetails = { challengePeriod: BigNumber; }; +/** + * Represents the collateral configuration for a given Optimistic Governor contract. + * + * @field `erc20Contract` field is the ERC20 contract that is used for collateral. + * @field `address` field is the address of the ERC20 contract that is used for collateral. + * @field `symbol` field is the symbol of the ERC20 contract that is used for collateral. + * @field `decimals` field is the number of decimals that the ERC20 contract that is used for collateral has. + */ export type CollateralDetails = { erc20Contract: Contract; address: string; @@ -267,6 +295,21 @@ export type CollateralDetails = { decimals: BigNumber; }; +/** + * Event fired when an assertion is made on the Optimistic Oracle V3 contract. + * + * @field `assertionId` field is the id of the assertion. + * @field `domainId` field is the domain id of the assertion. + * @field `claim` field is the claim of the assertion. + * @field `asserter` field is the address of the asserter. + * @field `callbackRecipient` field is the address of the callback recipient. + * @field `escalationManager` field is the address of the escalation manager. + * @field `caller` field is the address of the caller. + * @field `expirationTime` field is the time that the assertion's challenge period ends. + * @field `currency` field is the currency that the assertion is made in. + * @field `bond` field is the bond that is required for the assertion. + * @field `identifier` field is the identifier of the assertion. + */ export type AssertionMadeEvent = Event & { args: { assertionId: string; // indexed @@ -283,6 +326,18 @@ export type AssertionMadeEvent = Event & { }; }; +/** + * Event fired when transactions are proposed on the Optimistic Governor contract. + * + * @field `proposer` field is the address of the proposer. + * @field `proposalTime` field is the time that the proposal was made. + * @field `assertionId` field is the id of the assertion. + * @field `proposal` field is the proposal that was made. + * @field `proposalHash` field is the hash of the proposal. + * @field `explanation` field is the explanation of the proposal, which in the case of oSnap is the ipfs url. + * @field `rules` field is the rules of the proposal. + * @field `challengeWindowEvents` field is the challenge window events of the proposal. + */ export type TransactionsProposedEvent = Event & { args: { proposer: string; // indexed @@ -299,6 +354,12 @@ export type TransactionsProposedEvent = Event & { }; }; +/** + * Event fired when an Optimistic Governor proposal's transactions are executed successfully. + * + * @field `proposalHash` field is the hash of the proposal. + * @field `assertionId` field is the id of the assertion. + */ export type ProposalExecutedEvent = Event & { args: { proposalHash: string; // indexed @@ -306,23 +367,42 @@ export type ProposalExecutedEvent = Event & { }; }; -export type AssertionDetails = { + +/** + * Represents the transaction hash and log index of an `AssertionMade` event. + * + * We need these for generating a link to the assertion on the Optimistic Oracle dapp. + */ +export type AssertionTransactionDetails = { assertionHash: string; assertionLogIndex: string; }; +/** + * Represents the state of a proposal on the Optimistic Governor contract. When an assertion is associated with the proposal, we also include the assertion transaction hash and log index so that we can create a link to the assertion on the Optimistic Oracle dapp. + * + * There are four states that a proposal can be in: + * + * - `can-propose-to-og`: The user can propose transactions to the Optimistic Governor contract. This is the initial state of a proposal. We also indicate if this proposal has been disputed in the Oracle, so that we can warn the user to exercise caution and avoid losing their bond. + * + * - `in-oo-challenge-period`: The user has proposed transactions to the Optimistic Governor contract, and the proposal is currently in the challenge period on the Optimistic Oracle contract. We also indicate when the challenge period ends, so that we can warn the user to wait until the challenge period ends before proposing new transactions. + * + * - `can-request-tx-execution`: The user has proposed transactions to the Optimistic Governor contract, and the challenge period has ended. The user can now request that the Optimistic Governor contract execute the transactions. + * + * - `transactions-executed`: The user has proposed transactions to the Optimistic Governor contract, the challenge period has ended, and the transactions have been executed by the Optimistic Governor contract. + */ export type OGProposalState = | { status: 'can-propose-to-og'; isDisputed: boolean; } - | (AssertionDetails & { + | (AssertionTransactionDetails & { status: 'in-oo-challenge-period'; expirationTime: number; }) - | (AssertionDetails & { + | (AssertionTransactionDetails & { status: 'can-request-tx-execution'; }) - | (AssertionDetails & { + | (AssertionTransactionDetails & { status: 'transactions-executed'; }); diff --git a/src/plugins/oSnap/utils/getters.ts b/src/plugins/oSnap/utils/getters.ts index 09183c26..4aa97a59 100644 --- a/src/plugins/oSnap/utils/getters.ts +++ b/src/plugins/oSnap/utils/getters.ts @@ -293,15 +293,20 @@ async function getAssertionGql(params: { } /** * Fetches the details of a given proposal from the Optimistic Governor subgraph. + * + * The subgraph uses the `assertionId` that comes from assertion events as the primary key for proposals. + * However, this `assertionId` will be deleted if the proposal is disputed, so we can't use it to query the subgraph. + * Instead, we use the `proposalHash` and `explanation` to query the subgraph. + * The `explanation` contains the ipfs url of the proposal, which is the only way to distinguish between proposals with the same `proposalHash`. + * This means we must use a `where` clause to filter the results, which is not ideal. */ async function getOgProposalGql(params: { network: Network; explanation: string; moduleAddress: string; - transactions: OptimisticGovernorTransaction[]; + proposalHash: string; }) { - const { network, transactions, explanation, moduleAddress } = params; - const proposalHash = getProposalHashFromTransactions(transactions); + const { network, explanation, moduleAddress, proposalHash } = params; const encodedExplanation = pack( ['bytes'], [toUtf8Bytes(explanation.replace(/^0x/, ''))] @@ -330,6 +335,11 @@ async function getOgProposalGql(params: { return result?.proposals[0]; } +/** + * Fetches the details of a Optimistic Governor module's collateral token. + * + * Returns the address, symbol, and decimals of the collateral token, along with the token contract for further querying. + */ export async function getCollateralDetailsForProposal( provider: StaticJsonRpcProvider, moduleAddress: string @@ -353,6 +363,9 @@ export async function getCollateralDetailsForProposal( return { erc20Contract, address, symbol, decimals }; } +/** + * Fetches the allowance of a given collateral token for a given user. + */ export async function getUserCollateralAllowance( erc20Contract: Contract, userAddress: string, @@ -361,6 +374,9 @@ export async function getUserCollateralAllowance( return erc20Contract.allowance(userAddress, moduleAddress); } +/** + * Fetches the balance of a given collateral token for a given user. + */ export async function getUserCollateralBalance( erc20Contract: Contract, userAddress: string @@ -368,6 +384,11 @@ export async function getUserCollateralBalance( return erc20Contract.balanceOf(userAddress); } +/** + * Fetches the details of a given Optimistic Governor module from the chain. + * + * Performs a multicall to fetch the oracle address, rules, minimum bond, and challenge period. + */ export async function getOGModuleDetails(params: { provider: StaticJsonRpcProvider; network: Network; @@ -401,21 +422,30 @@ export async function getOGModuleDetails(params: { }; } +/** + * Fetches the state of an Optimistic Governor proposal from the chain. + * + * This is a fallback function that should only be used if the subgraph is not available, because it is very slow. + * + * The contract is designed in such a way that it deletes the `assertionId` from the proposal if the proposal is disputed, _or_ if the transactions are executed successfully. This means we can't tell the difference between a proposal that has not yet been proposed, has been disputed, or that has been executed by querying the chain. + * + * Instead, we must query the chain for the proposal events, and then query the chain for the execution events, and then compare the two to determine the state of the proposal. This is very slow. + */ export async function getOgProposalStateFromChain(params: { moduleDetails: OGModuleDetails; network: Network; + proposalHash: string; explanation: string; - transactions: OptimisticGovernorTransaction[]; }): Promise { - const { network, moduleDetails, explanation, transactions } = params; + const { network, moduleDetails, explanation, proposalHash } = params; + const { moduleAddress, oracleAddress } = moduleDetails; + const provider = getProvider(network); const latestBlock = (await provider.getBlock('latest')).number; - // modify this per chain. this should be updated with constants for all chains. start block is og deploy block. - // this needs to be optimized to reduce loading time, currently takes a long time to parse 3k blocks at a time. const oGstartBlock = getDeployBlock({ network, name: 'OptimisticGovernor' }); const oOStartBlock = getDeployBlock({ network, name: 'OptimisticOracleV3' }); const maxRange = 3000; - const { moduleAddress, oracleAddress, rules } = moduleDetails; + const moduleContract = new Contract( moduleAddress, OPTIMISTIC_GOVERNOR_ABI, @@ -426,15 +456,8 @@ export async function getOgProposalStateFromChain(params: { OPTIMISTIC_ORACLE_V3_ABI, provider ); - const proposalHash = getProposalHashFromTransactions(transactions); - const claimHash = getClaimHash({ - proposalHash, - explanation, - rules - }); const assertionId: string = await moduleContract.assertionIds(proposalHash); - const hasAssertionId = assertionIdIsNotZero(assertionId); if (hasAssertionId) { @@ -450,6 +473,7 @@ export async function getOgProposalStateFromChain(params: { }); // assertion ids are unique, so this will have only one result + // we need to get an event instead of getting the `Assertion` struct from the chain because the oracle dapp needs the assertion transaction hash and the log index to link to the oracle dapp. const assertionMadeEvent = assertionMadeEvents[0]; const expirationTime = assertionMadeEvent.args?.expirationTime.toNumber(); @@ -557,12 +581,16 @@ export async function getOgProposalStateFromChain(params: { }; } +/** + * Fetches the state of an Optimistic Governor proposal from the subgraph. + * + * This is the preferred method of fetching the state of a proposal, because it is much faster than querying the chain. + */ export async function getOGProposalStateGql(params: { network: Network; moduleAddress: string; proposalHash: string; explanation: string; - transactions: OptimisticGovernorTransaction[]; }): Promise { const { network } = params; const oGproposal = await getOgProposalGql(params); @@ -573,12 +601,13 @@ export async function getOGProposalStateGql(params: { const { executed, assertionId, deleted } = oGproposal; - if (deleted) { + const hasAssertionId = assertionIdIsNotZero(assertionId); + + // the subgraph records `ProposalDeleted` events, which are fired when a proposal is disputed. + if (!hasAssertionId && deleted) { return { status: 'can-propose-to-og', isDisputed: true }; } - const hasAssertionId = assertionIdIsNotZero(assertionId); - if (!hasAssertionId) { return { status: 'can-propose-to-og', isDisputed: false }; } @@ -634,10 +663,18 @@ export async function getOGProposalStateGql(params: { return { status: 'can-propose-to-og', isDisputed: false }; } +/** + * Querying for an assertion ID that does not map to a proposal hash will return '0x0000000000000000000000000000000000000000000000000000000000000000' + */ function assertionIdIsNotZero(assertionId: string) { return assertionId !== solidityZeroHexString; } +/** + * Fetches the state of an Optimistic Governor proposal. + * + * This function will attempt to fetch the state of a proposal from the subgraph, and if that fails, it will fall back to querying the chain. + */ export async function getOGProposalState(params: { moduleDetails: OGModuleDetails; network: Network; @@ -651,9 +688,8 @@ export async function getOGProposalState(params: { return await getOGProposalStateGql({ network, moduleAddress, - proposalHash, explanation, - transactions + proposalHash, }); } catch (error) { console.warn( @@ -664,11 +700,14 @@ export async function getOGProposalState(params: { network, moduleDetails, explanation, - transactions + proposalHash }); } } +/** + * The `proposalHash` as represented in the Optimistic Governor contract is the keccak256 hash of the transactions that make up the proposal. + */ export function getProposalHashFromTransactions( transactions: OptimisticGovernorTransaction[] ) { @@ -680,34 +719,6 @@ export function getProposalHashFromTransactions( ); } -export function getClaimHash(params: { - proposalHash: string; - explanation: string; - rules: string; -}) { - const { proposalHash, explanation, rules } = params; - - return pack( - ['string', 'bytes', 'bytes', 'bytes', 'bytes', 'bytes', 'bytes', 'bytes'], - [ - '', - pack(['string', 'string'], ['proposalHash', ':']), - toUtf8Bytes(proposalHash.replace(/^0x/, '')), - pack( - ['string', 'string', 'string', 'string'], - [',', 'explanation', ':', '"'] - ), - toUtf8Bytes(explanation.replace(/^0x/, '')), - pack( - ['string', 'string', 'string', 'string', 'string'], - ['"', ',', 'rules', ':', '"'] - ), - toUtf8Bytes(rules.replace(/^0x/, '')), - pack(['string'], ['"']) - ] - ); -} - /** * Returns the EIP-3770 prefix for a given network. * @@ -729,6 +740,9 @@ export function getSafeAppLink( return `${appUrl}${prefix}:${safeAddress}`; } +/** + * Returns the url for an Optimistic Governor proposal's assertion on the Optimistic Oracle dapp. + */ export function getOracleUiLink( chain: string, txHash: string,