Skip to content

Commit

Permalink
fix: fix some issues here and there
Browse files Browse the repository at this point in the history
  • Loading branch information
sakulstra committed Sep 12, 2023
1 parent 096c587 commit bd832fe
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/govv3/checks/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getContractName } from '../utils/solidityUtils';
/**
* Reports all emitted events from the proposal
*/
export const checkLogs: ProposalCheck = {
export const checkLogs: ProposalCheck<Awaited<ReturnType<PayloadsController['getPayload']>>> = {

Check failure on line 9 in src/govv3/checks/logs.ts

View workflow job for this annotation

GitHub Actions / release-node-alpha / release-alpha

Exported variable 'checkLogs' has or is using private name 'PayloadsController'.

Check failure on line 9 in src/govv3/checks/logs.ts

View workflow job for this annotation

GitHub Actions / release-node-alpha / release-alpha

Exported variable 'checkLogs' has or is using private name 'PayloadsController'.
name: 'Reports all events emitted from the proposal',
async checkProposal(proposal, sim, deps) {
let info = [];
Expand Down
5 changes: 3 additions & 2 deletions src/govv3/checks/selfDestruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import { Hex, PublicClient } from 'viem';
import { ProposalCheck } from './types';
import { toAddressLink } from '../utils/markdownUtils';
import { PayloadsController } from '../payloadsController';

/**
* Check all targets with code if they contain selfdestruct.
*/
export const checkTargetsNoSelfdestruct: ProposalCheck = {
export const checkTargetsNoSelfdestruct: ProposalCheck<Awaited<ReturnType<PayloadsController['getPayload']>>> = {
name: 'Check all targets do not contain selfdestruct',
async checkProposal(proposal, sim, publicClient) {
const allTargets = proposal.payload.actions.map((action) => action.target);
Expand All @@ -20,7 +21,7 @@ export const checkTargetsNoSelfdestruct: ProposalCheck = {
/**
* Check all touched contracts with code if they contain selfdestruct.
*/
export const checkTouchedContractsNoSelfdestruct: ProposalCheck = {
export const checkTouchedContractsNoSelfdestruct: ProposalCheck<undefined> = {
name: 'Check all touched contracts do not contain selfdestruct',
async checkProposal(proposal, sim, publicClient) {
const { info, warn, error } = await checkNoSelfdestructs([], sim.transaction.addresses, publicClient);
Expand Down
5 changes: 3 additions & 2 deletions src/govv3/checks/targets-verified.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Hex, PublicClient } from 'viem';
import { ProposalCheck } from './types';
import { TenderlySimulationResponse } from '../../utils/tenderlyClient';
import { PayloadsController } from '../../../dist';

/**
* Check all targets with code are verified on Etherscan
*/
export const checkTargetsVerifiedEtherscan: ProposalCheck = {
export const checkTargetsVerifiedEtherscan: ProposalCheck<Awaited<ReturnType<PayloadsController['getPayload']>>> = {
name: 'Check all targets are verified on Etherscan',
async checkProposal(proposal, sim, publicClient) {
const allTargets = proposal.payload.actions.map((action) => action.target);
Expand All @@ -18,7 +19,7 @@ export const checkTargetsVerifiedEtherscan: ProposalCheck = {
/**
* Check all touched contracts with code are verified on Etherscan
*/
export const checkTouchedContractsVerifiedEtherscan: ProposalCheck = {
export const checkTouchedContractsVerifiedEtherscan: ProposalCheck<undefined> = {
name: 'Check all touched contracts are verified on Etherscan',
async checkProposal(proposal, sim, publicClient) {
const info = await checkVerificationStatuses(sim, sim.transaction.addresses, publicClient);
Expand Down
4 changes: 2 additions & 2 deletions src/govv3/checks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ export type CheckResult = {
errors: string[];
};

export interface ProposalCheck {
export interface ProposalCheck<T> {
name: string;
checkProposal(
proposalInfo: Awaited<ReturnType<PayloadsController['getPayload']>>,
proposalInfo: T,
simulation: TenderlySimulationResponse,
publicClient: PublicClient
): Promise<CheckResult>;
Expand Down
14 changes: 3 additions & 11 deletions src/govv3/generatePayloadReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PayloadsController } from './payloadsController';
import { tenderlyDeepDiff } from './utils/tenderlyDeepDiff';
import { interpretStateChange } from './utils/stateDiffInterpreter';
import { getContractName } from './utils/solidityUtils';
import { boolToMarkdown, toTxLink } from './utils/markdownUtils';
import { boolToMarkdown, renderCheckResult, toTxLink } from './utils/markdownUtils';
import { checkTargetsNoSelfdestruct, checkTouchedContractsNoSelfdestruct } from './checks/selfDestruct';
import { CheckResult, ProposalCheck } from './checks/types';
import { checkLogs } from './checks/logs';
Expand All @@ -28,10 +28,10 @@ export async function generateReport({ payloadId, payloadInfo, simulation, publi
- actions: ${JSON.stringify(payload.actions, (key, value) => (typeof value === 'bigint' ? value.toString() : value))}
- createdAt: [${payload.createdAt}](${toTxLink(createdLog.transactionHash, false, publicClient)})\n`;
if (queuedLog) {
report += `- queuedAt: [${payload.createdAt}](${toTxLink(queuedLog.transactionHash, false, publicClient)})\n`;
report += `- queuedAt: [${payload.queuedAt}](${toTxLink(queuedLog.transactionHash, false, publicClient)})\n`;
}
if (executedLog) {
report += `- executedAt: [${payload.createdAt}](${toTxLink(executedLog.transactionHash, false, publicClient)})\n`;
report += `- executedAt: [${payload.executedAt}](${toTxLink(executedLog.transactionHash, false, publicClient)})\n`;
}
report += '\n';

Expand Down Expand Up @@ -142,11 +142,3 @@ export async function generateReport({ payloadId, payloadInfo, simulation, publi

return report;
}

function renderCheckResult(check: ProposalCheck, result: CheckResult) {
let response = `### Check: ${check.name} ${boolToMarkdown(!result.errors.length)}\n\n`;
if (result.errors.length) response += `#### Errors\n\n${result.errors.join('\n')}\n\n`;
if (result.warnings.length) response += `#### Warnings\n\n${result.warnings.join('\n')}\n\n`;
if (result.info.length) response += `#### Info\n\n${result.info.join('\n')}\n\n`;
return response;
}
143 changes: 143 additions & 0 deletions src/govv3/generateProposalReport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Hex, PublicClient, getAddress } from 'viem';
import { StateDiff, TenderlySimulationResponse } from '../utils/tenderlyClient';
import { PayloadsController } from './payloadsController';
import { tenderlyDeepDiff } from './utils/tenderlyDeepDiff';
import { interpretStateChange } from './utils/stateDiffInterpreter';
import { getContractName } from './utils/solidityUtils';
import { boolToMarkdown, renderCheckResult, toTxLink } from './utils/markdownUtils';
import { checkTargetsNoSelfdestruct, checkTouchedContractsNoSelfdestruct } from './checks/selfDestruct';
import { CheckResult, ProposalCheck } from './checks/types';
import { checkLogs } from './checks/logs';
import { checkTargetsVerifiedEtherscan, checkTouchedContractsVerifiedEtherscan } from './checks/targets-verified';
import { Governance, HUMAN_READABLE_STATE } from './governance';

type GenerateReportRequest = {
proposalId: bigint;
proposalInfo: Awaited<ReturnType<Governance['getProposal']>>;
simulation: TenderlySimulationResponse;
publicClient: PublicClient;
};

export async function generateReport({ proposalId, proposalInfo, simulation, publicClient }: GenerateReportRequest) {
const { proposal, executedLog, queuedLog, createdLog, payloadSentLog, votingActivatedLog } = proposalInfo;
// generate file header
let report = `## Proposal ${proposalId}
- state: ${HUMAN_READABLE_STATE[proposal.state as keyof typeof HUMAN_READABLE_STATE]}
- creator: ${proposal.creator}
- maximumAccessLevelRequired: ${proposal.accessLevel}
- payloads: ${JSON.stringify(proposal.payloads, (key, value) => (typeof value === 'bigint' ? value.toString() : value))}
- createdAt: [${proposal.creationTime}](${toTxLink(createdLog.transactionHash, false, publicClient)})\n`;
if (queuedLog) {
report += `- queuedAt: [${proposal.queuingTime}](${toTxLink(queuedLog.transactionHash, false, publicClient)})\n`;
}
if (executedLog) {
report += `- executedAt: [${executedLog.timestamp}](${toTxLink(
executedLog.transactionHash,
false,
publicClient
)})\n`;
}
report += '\n';

// check if simulation was successful
report += `### Simulation ${boolToMarkdown(simulation.transaction.status)}\n\n`;
if (!simulation.transaction.status) {
const txInfo = simulation.transaction.transaction_info;
const reason = txInfo.stack_trace ? txInfo.stack_trace[0].error_reason : 'unknown error';
report += `Transaction reverted with reason: ${reason}`;
} else {
// State diffs in the simulation are an array, so first we organize them by address.
const stateDiffs = simulation.transaction.transaction_info.state_diff.reduce((diffs, diff) => {
// TODO: double check if that's safe to skip
if (!diff.raw?.[0]) return diffs;
const addr = getAddress(diff.raw[0].address);
if (!diffs[addr]) diffs[addr] = [diff];
else diffs[addr].push(diff);
return diffs;
}, {} as Record<string, StateDiff[]>);

if (!Object.keys(stateDiffs).length) {
report += `No state changes detected`;
} else {
let stateChanges = '';
let warnings = '';
// Parse state changes at each address
for (const [address, diffs] of Object.entries(stateDiffs)) {
// Use contracts array to get contract name of address
stateChanges += `\n\`\`\`diff\n# ${getContractName(simulation.contracts, address)}\n`;

// Parse each diff. A single diff may involve multiple storage changes, e.g. a proposal that
// executes three transactions will show three state changes to the `queuedTransactions`
// mapping within a single `diff` element. We always JSON.stringify the values so structs
// (i.e. tuples) don't print as [object Object]
for (const diff of diffs) {
if (!diff.soltype) {
// In this branch, state change is not decoded, so return raw data of each storage write
// (all other branches have decoded state changes)
diff.raw.forEach((w) => {
const oldVal = JSON.stringify(w.original);
const newVal = JSON.stringify(w.dirty);
// info += `\n - Slot \`${w.key}\` changed from \`${oldVal}\` to \`${newVal}\``
stateChanges += tenderlyDeepDiff(oldVal, newVal, `Slot \`${w.key}\``);
});
} else if (diff.soltype.simple_type) {
// This is a simple type with a single changed value
// const oldVal = JSON.parse(JSON.stringify(diff.original))
// const newVal = JSON.parse(JSON.stringify(diff.dirty))
// info += `\n - \`${diff.soltype.name}\` changed from \`${oldVal}\` to \`${newVal}\``
stateChanges += tenderlyDeepDiff(diff.original, diff.dirty, diff.soltype.name);
} else if (diff.soltype.type.startsWith('mapping')) {
// This is a complex type like a mapping, which may have multiple changes. The diff.original
// and diff.dirty fields can be strings or objects, and for complex types they are objects,
// so we cast them as such
const keys = Object.keys(diff.original);
const original = diff.original as Record<string, any>;
const dirty = diff.dirty as Record<string, any>;
for (const k of keys as Hex[]) {
stateChanges += tenderlyDeepDiff(original[k], dirty[k], `\`${diff.soltype?.name}\` key \`${k}\``);
const interpretation = await interpretStateChange(
address,
diff.soltype?.name,
original[k],
dirty[k],
k,
publicClient
);
if (interpretation) stateChanges += `\n${interpretation}`;
stateChanges += '\n';
}
} else {
// TODO arrays and nested mapping are currently not well supported -- find a transaction
// that changes state of these types to inspect the Tenderly simulation response and
// handle it accordingly. In the meantime we show the raw state changes and print a
// warning about decoding the data
diff.raw.forEach((w) => {
const oldVal = JSON.stringify(w.original);
const newVal = JSON.stringify(w.dirty);
// info += `\n - Slot \`${w.key}\` changed from \`${oldVal}\` to \`${newVal}\``
stateChanges += tenderlyDeepDiff(oldVal, newVal, `Slot \`${w.key}\``);
warnings += `Could not parse state: add support for formatting type ${diff.soltype?.type} (slot ${w.key})\n`;
});
}
}
stateChanges += '```\n';
}

if (warnings) {
report += `#### Warnings\n`;
report += warnings;
}
report += `#### State Changes\n`;
report += stateChanges;
}
}
const checks = [checkLogs, checkTouchedContractsVerifiedEtherscan, checkTouchedContractsNoSelfdestruct];

for (const check of checks) {
const result = await check.checkProposal(proposalInfo, simulation, publicClient);
report += renderCheckResult(check, result);
}

return report;
}
9 changes: 9 additions & 0 deletions src/govv3/utils/markdownUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Hex, PublicClient } from 'viem';
import { CheckResult } from '../checks/types';

export function boolToMarkdown(value: boolean) {
if (value) return `:white_check_mark:`;
Expand All @@ -24,3 +25,11 @@ export function toTxLink(txn: Hex, md: boolean, client: PublicClient): string {
if (md) return `[${txn}](${link})`;
return link;
}

export function renderCheckResult(check: { name: string }, result: CheckResult) {
let response = `### Check: ${check.name} ${boolToMarkdown(!result.errors.length)}\n\n`;
if (result.errors.length) response += `#### Errors\n\n${result.errors.join('\n')}\n\n`;
if (result.warnings.length) response += `#### Warnings\n\n${result.warnings.join('\n')}\n\n`;
if (result.info.length) response += `#### Info\n\n${result.info.join('\n')}\n\n`;
return response;
}

0 comments on commit bd832fe

Please sign in to comment.