Skip to content

Commit

Permalink
Merge pull request #14 from lidofinance/feature/omnibus
Browse files Browse the repository at this point in the history
Omnibus
  • Loading branch information
avsetsin authored Oct 9, 2023
2 parents 065d6c0 + 176bca1 commit 72585e6
Show file tree
Hide file tree
Showing 24 changed files with 428 additions and 78 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"commander": "^11.0.0",
"ethers": "^6.2.2",
"node-fetch": "^2",
"prompts": "^2.4.2"
"prompts": "^2.4.2",
"string-argv": "^0.3.2"
},
"optionalDependencies": {
"@chainsafe/blst": "^0.2.8"
Expand Down
45 changes: 26 additions & 19 deletions programs/common/consensus.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { authorizedCall, getLatestBlock } from '@utils';
import { authorizedCall, getLatestBlock, getProvider } from '@utils';
import { Command } from 'commander';
import { Contract, EventLog, formatEther } from 'ethers';

Expand All @@ -12,7 +12,8 @@ export const addConsensusCommands = (command: Command, contract: Contract) => {
const table = await Promise.all(
addresses.map(async (address, index) => {
const lastReportedSlot = Number(lastReportedSlots[index]);
const balanceBigint = (await contract.runner?.provider?.getBalance(address)) || 0;
const provider = getProvider(contract);
const balanceBigint = (await provider.getBalance(address)) || 0n;
const balance = formatEther(balanceBigint);

return {
Expand Down Expand Up @@ -228,28 +229,34 @@ export const addConsensusCommands = (command: Command, contract: Contract) => {
};

const [members]: [string[]] = await contract.getMembers();
const membersMap = members.reduce((acc, member) => {
acc[compactHash(member)] = '-';
return acc;
}, {} as Record<string, string>);
const membersMap = members.reduce(
(acc, member) => {
acc[compactHash(member)] = '-';
return acc;
},
{} as Record<string, string>,
);

const events = await contract.queryFilter('ReportReceived', fromBlock, toBlock);
const groupedByRefSlot = events.reduce((acc, event) => {
if (!(event instanceof EventLog)) {
console.warn('log is not parsed');
return acc;
}
const groupedByRefSlot = events.reduce(
(acc, event) => {
if (!(event instanceof EventLog)) {
console.warn('log is not parsed');
return acc;
}

const refSlot = Number(event.args[0]);
const member = compactHash(event.args[1]);
const refSlot = Number(event.args[0]);
const member = compactHash(event.args[1]);

if (!acc[refSlot]) {
acc[refSlot] = { ...membersMap };
}
if (!acc[refSlot]) {
acc[refSlot] = { ...membersMap };
}

acc[refSlot][member] = 'H';
return acc;
}, {} as Record<number, Record<string, string>>);
acc[refSlot][member] = 'H';
return acc;
},
{} as Record<number, Record<string, string>>,
);

console.table(groupedByRefSlot);
});
Expand Down
6 changes: 2 additions & 4 deletions programs/common/logs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getLatestBlock } from '@utils';
import { getLatestBlock, getProvider } from '@utils';
import { Command } from 'commander';
import { Contract, EventLog, Filter } from 'ethers';

Expand All @@ -9,9 +9,7 @@ export const addLogsCommands = (command: Command, contract: Contract) => {
.option('-b, --blocks <number>', 'blocks', '7200')
.option('-e, --event-name <string>', 'event name')
.action(async (options) => {
const provider = contract.runner?.provider;
if (!provider) throw new Error('Provider is not set');

const provider = getProvider(contract);
const { blocks, eventName } = options;

const latestBlock = await getLatestBlock();
Expand Down
1 change: 1 addition & 0 deletions programs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './exit-bus-oracle';
export * from './lido';
export * from './locator';
export * from './nor';
export * from './omnibus';
export * from './oracle-config';
export * from './role';
export * from './sanity-checker';
Expand Down
29 changes: 29 additions & 0 deletions programs/omnibus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { program } from '@command';
import { checkTmCanForward, forwardVoteFromTm } from '@utils';
import { printVoteTxData, promptVoting } from './omnibus/';

const omnibus = program.command('omnibus').description('preparing and launching batches of calls through voting');

omnibus
.command('prepare')
.description('prepare omnibus script')
.action(async () => {
const voteTxData = await promptVoting();
if (!voteTxData) return;

await printVoteTxData(voteTxData);
});

omnibus
.command('run')
.description('run omnibus script')
.action(async () => {
const canForward = await checkTmCanForward();
if (!canForward) return;

const voteTxData = await promptVoting();
if (!voteTxData) return;

await printVoteTxData(voteTxData);
await forwardVoteFromTm(voteTxData.newVoteCalldata);
});
5 changes: 5 additions & 0 deletions programs/omnibus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './print-vote';
export * from './prompt-amount';
export * from './prompt-call';
export * from './prompt-description';
export * from './prompt-voting';
23 changes: 23 additions & 0 deletions programs/omnibus/print-vote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { tmContract } from '@contracts';
import { green } from 'chalk';
import { printTx } from 'utils';
import { VoteTxData } from './prompt-voting';

export const printVoteTxData = async (voteTxData: VoteTxData) => {
const { voteEvmScript, newVoteCalldata, description } = voteTxData;
console.log('');
console.log(green('vote calls evmScript:'));
console.log(voteEvmScript);

console.log('');
console.log(green('vote description (meta):'));
console.log(description);

console.log('');
console.log(green('newVote() calldata:'));
console.log(newVoteCalldata);

console.log('');

await printTx(tmContract, 'forward', [newVoteCalldata]);
};
11 changes: 11 additions & 0 deletions programs/omnibus/prompt-amount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import prompts from 'prompts';

export const promptAmountOfCalls = async () => {
const { amount } = await prompts({
type: 'number',
name: 'amount',
message: 'enter amount of calls',
});

return amount;
};
29 changes: 29 additions & 0 deletions programs/omnibus/prompt-call.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { gray, bold, green } from 'chalk';
import prompts from 'prompts';

export const printCallExample = () => {
console.log('');
console.log(bold('enter calls one by one'));
console.log(`format: ${gray('address "method_signature(uint256,string)" arg1 arg2')}`);
console.log(
`example: ${gray(
`0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC 'setNodeOperatorStakingLimit(uint256,uint64)' 0 150`,
)}`,
);
console.log('');
};

export const printCallsSuccess = () => {
console.log(green('filling the list of calls is completed'));
console.log('');
};

export const promptMethodCall = async (index: number) => {
const { methodCall } = await prompts({
type: 'text',
name: 'methodCall',
message: `enter call ${index + 1}`,
});

return methodCall;
};
28 changes: 28 additions & 0 deletions programs/omnibus/prompt-description.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { CallScriptAction, ParsedMethodCall } from '@utils';
import prompts from 'prompts';

export interface OmnibusScript extends ParsedMethodCall {
encoded: string;
call: CallScriptAction;
}

export const getDefaultOmnibusDescription = (omnibusScripts: OmnibusScript[]) => {
const callList = omnibusScripts
.map(({ address, args, methodName }, index) => `${index + 1}) call ${methodName}(${args}) at ${address}`)
.join('\n');

return `omnibus: \n${callList}`;
};

export const promptOmnibusDescription = async (omnibusScripts: OmnibusScript[]) => {
const defaultDescription = getDefaultOmnibusDescription(omnibusScripts);

const { description } = await prompts({
type: 'text',
name: 'description',
initial: defaultDescription,
message: 'enter voting description (use \\n for new line): \n',
});

return (description ?? '').split('\\n').join('\n');
};
51 changes: 51 additions & 0 deletions programs/omnibus/prompt-voting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { encodeCallScript, parseMethodCallToContract } from '@utils';
import { promptAmountOfCalls } from './prompt-amount';
import { printCallExample, printCallsSuccess, promptMethodCall } from './prompt-call';
import { OmnibusScript, promptOmnibusDescription } from './prompt-description';
import { agentOrDirect, votingNewVote } from '@scripts';

export interface VoteTxData {
voteEvmScript: string;
newVoteCalldata: string;
description: string;
}

export const promptVoting = async (): Promise<VoteTxData | void> => {
const amountOfCalls = await promptAmountOfCalls();
const omnibusScripts: OmnibusScript[] = [];

printCallExample();

for (let i = 0; i < amountOfCalls; i++) {
const methodCall = await promptMethodCall(i);

if (methodCall) {
try {
const parsedCall = parseMethodCallToContract(methodCall);
const { contract, method, args } = parsedCall;

const [encoded, call] = await agentOrDirect(contract, method, args);
omnibusScripts.push({ encoded, call, ...parsedCall });
} catch (error) {
console.warn((error as Error).message);
return;
}
} else {
console.warn('empty call, aborting');
return;
}
}

printCallsSuccess();

const description = await promptOmnibusDescription(omnibusScripts);

const voteEvmScript = encodeCallScript(omnibusScripts.map(({ call }) => call));
const [newVoteCalldata] = votingNewVote(voteEvmScript, description);

return {
voteEvmScript,
newVoteCalldata,
description,
};
};
4 changes: 3 additions & 1 deletion programs/staking-module/nor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { norContract } from '@contracts';
import { getNodeOperators } from './operators';
import { getProvider } from '@utils';

export type PenalizedNodeOperator = {
operatorId: number;
Expand All @@ -15,7 +16,8 @@ export const getPenalizedOperators = async () => {
const address = await norContract.getAddress();
const operators = await getNodeOperators(address);

const latestBlock = await norContract.runner?.provider?.getBlock('latest');
const provider = getProvider(norContract);
const latestBlock = await provider.getBlock('latest');
const lastBlockTimestamp = latestBlock?.timestamp;

if (lastBlockTimestamp == null) {
Expand Down
87 changes: 87 additions & 0 deletions scripts/agent-or-direct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { CallScriptAction, authorizedCallTest, encodeCallScript } from '@utils';
import { aragonAgentAddress, votingAddress } from '@contracts';
import { Contract } from 'ethers';
import { agentForward } from './agent';
import chalk from 'chalk';
import prompts from 'prompts';

const printCallSuccess = (from: string) => {
console.log(chalk`{green successfully called from {bold ${from}}, added to the list}`);
console.log('');
};

export const agentOrDirect = async (contract: Contract, method: string, args: unknown[] = []) => {
const call: CallScriptAction = {
to: await contract.getAddress(),
data: contract.interface.encodeFunctionData(method, args),
};

const errors = [];

try {
await authorizedCallTest(contract, method, args, aragonAgentAddress);
printCallSuccess('agent');

return encodeFromAgent(call);
} catch (error) {
errors.push(error);
}

try {
await authorizedCallTest(contract, method, args, votingAddress);
printCallSuccess('voting');

return encodeFromVoting(call);
} catch (error) {
errors.push(error);
}

console.log('');
console.warn(chalk`{red calls from voting and agent failed}`);

const from = await promptFrom();

if (from === 'agent') {
return encodeFromAgent(call);
}

if (from === 'voting') {
return encodeFromVoting(call);
}

console.dir(errors, { depth: null });
throw new Error('aborted');
};

export const promptFrom = async () => {
const { from } = await prompts({
type: 'select',
name: 'from',
message: 'what to do?',
choices: [
{ title: chalk`abort and show errors`, value: null },
{
title: chalk`add as a direct call {red (only choose if you know what you are doing)}`,
value: 'voting',
},
{
title: chalk`add as a forwarded call from agent {red (only choose if you know what you are doing)}`,
value: 'agent',
},
],
initial: 0,
});

return from;
};

export const encodeFromAgent = (call: CallScriptAction) => {
const encoded = encodeCallScript([call]);
const [agentEncoded, agentCall] = agentForward(encoded);
return [agentEncoded, agentCall] as const;
};

export const encodeFromVoting = (call: CallScriptAction) => {
const encoded = encodeCallScript([call]);
return [encoded, call] as const;
};
Loading

0 comments on commit 72585e6

Please sign in to comment.