From 176bca171c7116f4534df879f4b815360188a76f Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 9 Oct 2023 14:06:54 +0300 Subject: [PATCH] feat: omnibus --- package.json | 3 +- programs/common/consensus.ts | 45 +++++++------ programs/common/logs.ts | 6 +- programs/index.ts | 1 + programs/omnibus.ts | 29 +++++++++ programs/omnibus/index.ts | 5 ++ programs/omnibus/print-vote.ts | 23 +++++++ programs/omnibus/prompt-amount.ts | 11 ++++ programs/omnibus/prompt-call.ts | 29 +++++++++ programs/omnibus/prompt-description.ts | 28 +++++++++ programs/omnibus/prompt-voting.ts | 51 +++++++++++++++ programs/staking-module/nor.ts | 4 +- scripts/agent-or-direct.ts | 87 ++++++++++++++++++++++++++ scripts/agent.ts | 4 +- scripts/index.ts | 1 + scripts/voting.ts | 10 +++ utils/authorized-call.ts | 65 +++++++++---------- utils/call-tx.ts | 2 +- utils/contract.ts | 25 ++++++++ utils/index.ts | 3 + utils/parse-method-call.ts | 37 +++++++++++ utils/print-tx.ts | 17 ++--- utils/voting.ts | 15 ++++- yarn.lock | 5 ++ 24 files changed, 428 insertions(+), 78 deletions(-) create mode 100644 programs/omnibus.ts create mode 100644 programs/omnibus/index.ts create mode 100644 programs/omnibus/print-vote.ts create mode 100644 programs/omnibus/prompt-amount.ts create mode 100644 programs/omnibus/prompt-call.ts create mode 100644 programs/omnibus/prompt-description.ts create mode 100644 programs/omnibus/prompt-voting.ts create mode 100644 scripts/agent-or-direct.ts create mode 100644 utils/contract.ts create mode 100644 utils/parse-method-call.ts diff --git a/package.json b/package.json index 99a8d72..c716331 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/programs/common/consensus.ts b/programs/common/consensus.ts index 2cb0ab4..a6ea627 100644 --- a/programs/common/consensus.ts +++ b/programs/common/consensus.ts @@ -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'; @@ -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 { @@ -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); + const membersMap = members.reduce( + (acc, member) => { + acc[compactHash(member)] = '-'; + return acc; + }, + {} as Record, + ); 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>); + acc[refSlot][member] = 'H'; + return acc; + }, + {} as Record>, + ); console.table(groupedByRefSlot); }); diff --git a/programs/common/logs.ts b/programs/common/logs.ts index 22e1df1..2dd08f9 100644 --- a/programs/common/logs.ts +++ b/programs/common/logs.ts @@ -1,4 +1,4 @@ -import { getLatestBlock } from '@utils'; +import { getLatestBlock, getProvider } from '@utils'; import { Command } from 'commander'; import { Contract, EventLog, Filter } from 'ethers'; @@ -9,9 +9,7 @@ export const addLogsCommands = (command: Command, contract: Contract) => { .option('-b, --blocks ', 'blocks', '7200') .option('-e, --event-name ', '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(); diff --git a/programs/index.ts b/programs/index.ts index 4fc2f05..8313dc5 100644 --- a/programs/index.ts +++ b/programs/index.ts @@ -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'; diff --git a/programs/omnibus.ts b/programs/omnibus.ts new file mode 100644 index 0000000..085117c --- /dev/null +++ b/programs/omnibus.ts @@ -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); + }); diff --git a/programs/omnibus/index.ts b/programs/omnibus/index.ts new file mode 100644 index 0000000..83c50b0 --- /dev/null +++ b/programs/omnibus/index.ts @@ -0,0 +1,5 @@ +export * from './print-vote'; +export * from './prompt-amount'; +export * from './prompt-call'; +export * from './prompt-description'; +export * from './prompt-voting'; diff --git a/programs/omnibus/print-vote.ts b/programs/omnibus/print-vote.ts new file mode 100644 index 0000000..0f25c9c --- /dev/null +++ b/programs/omnibus/print-vote.ts @@ -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]); +}; diff --git a/programs/omnibus/prompt-amount.ts b/programs/omnibus/prompt-amount.ts new file mode 100644 index 0000000..fd40668 --- /dev/null +++ b/programs/omnibus/prompt-amount.ts @@ -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; +}; diff --git a/programs/omnibus/prompt-call.ts b/programs/omnibus/prompt-call.ts new file mode 100644 index 0000000..4addbc4 --- /dev/null +++ b/programs/omnibus/prompt-call.ts @@ -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; +}; diff --git a/programs/omnibus/prompt-description.ts b/programs/omnibus/prompt-description.ts new file mode 100644 index 0000000..e366273 --- /dev/null +++ b/programs/omnibus/prompt-description.ts @@ -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'); +}; diff --git a/programs/omnibus/prompt-voting.ts b/programs/omnibus/prompt-voting.ts new file mode 100644 index 0000000..6e65651 --- /dev/null +++ b/programs/omnibus/prompt-voting.ts @@ -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 => { + 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, + }; +}; diff --git a/programs/staking-module/nor.ts b/programs/staking-module/nor.ts index 1143db0..ae343ac 100644 --- a/programs/staking-module/nor.ts +++ b/programs/staking-module/nor.ts @@ -1,5 +1,6 @@ import { norContract } from '@contracts'; import { getNodeOperators } from './operators'; +import { getProvider } from '@utils'; export type PenalizedNodeOperator = { operatorId: number; @@ -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) { diff --git a/scripts/agent-or-direct.ts b/scripts/agent-or-direct.ts new file mode 100644 index 0000000..0803613 --- /dev/null +++ b/scripts/agent-or-direct.ts @@ -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; +}; diff --git a/scripts/agent.ts b/scripts/agent.ts index 2910271..f3548b2 100644 --- a/scripts/agent.ts +++ b/scripts/agent.ts @@ -1,8 +1,8 @@ import { aragonAgentAddress, aragonAgentContract } from '@contracts'; -import { encodeCallScript } from '@utils'; +import { CallScriptAction, encodeCallScript } from '@utils'; export const agentForward = (votingData: string) => { - const call = { + const call: CallScriptAction = { to: aragonAgentAddress, data: aragonAgentContract.interface.encodeFunctionData('forward', [votingData]), }; diff --git a/scripts/index.ts b/scripts/index.ts index 2f73287..c51dad3 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -1,3 +1,4 @@ +export * from './agent-or-direct'; export * from './agent'; export * from './app'; export * from './lido'; diff --git a/scripts/voting.ts b/scripts/voting.ts index b91cdac..fd203c8 100644 --- a/scripts/voting.ts +++ b/scripts/voting.ts @@ -10,3 +10,13 @@ export const votingForward = (votingData: string) => { const encoded = encodeCallScript([call]); return [encoded, call] as const; }; + +export const votingNewVote = (votingData: string, votingDesc: string = '') => { + const call = { + to: votingAddress, + data: votingContract.interface.encodeFunctionData('newVote(bytes, string)', [votingData, votingDesc]), + }; + + const encoded = encodeCallScript([call]); + return [encoded, call] as const; +}; diff --git a/utils/authorized-call.ts b/utils/authorized-call.ts index 9d5ebe7..0505fef 100644 --- a/utils/authorized-call.ts +++ b/utils/authorized-call.ts @@ -1,11 +1,13 @@ -import { AbstractSigner, Contract } from 'ethers'; +import { Contract } from 'ethers'; import { votingForward } from '@scripts'; +import { green } from 'chalk'; import { aragonAgentAddress, votingAddress } from '@contracts'; import { encodeCallScript } from './scripts'; import { forwardVoteFromTm } from './voting'; import { contractCallTxWithConfirm } from './call-tx'; import { agentForward } from 'scripts/agent'; import { printTx } from './print-tx'; +import { getProvider, getSignerAddress } from './contract'; export const authorizedCall = async (contract: Contract, method: string, args: unknown[] = []) => { printTx(contract, method, args); @@ -39,63 +41,54 @@ export const authorizedCall = async (contract: Contract, method: string, args: u }; export const authorizedCallEOA = async (contract: Contract, method: string, args: unknown[] = []) => { - if (!(contract.runner instanceof AbstractSigner)) { - throw new Error('Runner is not a signer'); - } - - const signer = contract.runner; - const signerAddress = await signer.getAddress(); + const signerAddress = await getSignerAddress(contract); await contract[method].staticCall(...args, { from: signerAddress }); - console.log('direct call passed successfully'); + printSuccess('EOA'); await contractCallTxWithConfirm(contract, method, args); return true; }; export const authorizedCallVoting = async (contract: Contract, method: string, args: unknown[] = []) => { - const provider = contract.runner?.provider; - - if (!provider) { - throw new Error('Provider is not set'); - } - - const contractWithoutSigner = contract.connect(provider) as Contract; - await contractWithoutSigner[method].staticCall(...args, { from: votingAddress }); - console.log('call from voting passed successfully'); - - const call = { - to: await contract.getAddress(), - data: contract.interface.encodeFunctionData(method, args), - }; - - const encoded = encodeCallScript([call]); + authorizedCallTest(contract, method, args, votingAddress); + printSuccess('voting'); + const encoded = await encode(contract, method, args); const [votingCalldata] = votingForward(encoded); await forwardVoteFromTm(votingCalldata); + return true; }; export const authorizedCallAgent = async (contract: Contract, method: string, args: unknown[] = []) => { - const provider = contract.runner?.provider; + authorizedCallTest(contract, method, args, aragonAgentAddress); + printSuccess('agent'); - if (!provider) { - throw new Error('Provider is not set'); - } + const encoded = await encode(contract, method, args); + const [agentCalldata] = agentForward(encoded); + const [votingCalldata] = votingForward(agentCalldata); + await forwardVoteFromTm(votingCalldata); + return true; +}; + +export const authorizedCallTest = async (contract: Contract, method: string, args: unknown[] = [], from: string) => { + const provider = getProvider(contract); const contractWithoutSigner = contract.connect(provider) as Contract; - await contractWithoutSigner[method].staticCall(...args, { from: aragonAgentAddress }); - console.log('call from agent voting passed successfully'); + await contractWithoutSigner[method].staticCall(...args, { from }); + return true; +}; +const printSuccess = (from: string) => { + console.log(green(`\ncall from ${from} passed successfully`)); +}; + +const encode = async (contract: Contract, method: string, args: unknown[] = []) => { const call = { to: await contract.getAddress(), data: contract.interface.encodeFunctionData(method, args), }; - const encoded = encodeCallScript([call]); - - const [agentCalldata] = agentForward(encoded); - const [votingCalldata] = votingForward(agentCalldata); - await forwardVoteFromTm(votingCalldata); - return true; + return encodeCallScript([call]); }; diff --git a/utils/call-tx.ts b/utils/call-tx.ts index c518f86..ebbd63a 100644 --- a/utils/call-tx.ts +++ b/utils/call-tx.ts @@ -52,7 +52,7 @@ export const contractCallTx = async (contract: Contract, method: string, args: u data: log.data, topics: log.topics as string[], }); - console.log(parsedLog); + console.dir(parsedLog, { depth: null }); }); } catch (error) { console.log('failed to parse logs'); diff --git a/utils/contract.ts b/utils/contract.ts new file mode 100644 index 0000000..b878491 --- /dev/null +++ b/utils/contract.ts @@ -0,0 +1,25 @@ +import { AbstractSigner, Contract, Provider } from 'ethers'; + +export const getProvider = (contract: Contract): Provider => { + const provider = contract.runner?.provider; + + if (!provider) { + throw new Error('Provider is not set'); + } + + return provider; +}; + +export const getSigner = (contract: Contract): AbstractSigner => { + if (!(contract.runner instanceof AbstractSigner)) { + throw new Error('Runner is not a signer'); + } + + return contract.runner; +}; + +export const getSignerAddress = async (contract: Contract): Promise => { + const signer = getSigner(contract); + + return await signer.getAddress(); +}; diff --git a/utils/index.ts b/utils/index.ts index 613a27d..abcafdd 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -3,10 +3,13 @@ export * from './authorized-call'; export * from './block'; export * from './call-tx'; export * from './compare-calls'; +export * from './contract'; export * from './csv'; export * from './format-date'; export * from './get-value'; export * from './modules'; +export * from './parse-method-call'; +export * from './print-tx'; export * from './role-hash'; export * from './scripts'; export * from './sleep'; diff --git a/utils/parse-method-call.ts b/utils/parse-method-call.ts new file mode 100644 index 0000000..ad41be7 --- /dev/null +++ b/utils/parse-method-call.ts @@ -0,0 +1,37 @@ +import { provider } from '@providers'; +import { Contract, FunctionFragment } from 'ethers'; +import stringArgv from 'string-argv'; + +export type ParsedMethodCall = { + address: string; + method: string; + methodName: string; + args: string[]; + contract: Contract; +}; + +export const parseMethodCall = (methodCall: string) => { + const [address, method, ...args] = stringArgv(methodCall); + + return { address, method, args }; +}; + +export const parseMethodCallToContract = (methodCall: string): ParsedMethodCall => { + const [address, method, ...args] = stringArgv(methodCall.trim()); + + if (!method) { + throw new Error(`Method name is empty`); + } + + const abi = [`function ${method}`]; + const contract = new Contract(address, abi, provider); + const fragment = contract.interface.fragments[0] as FunctionFragment; + + if (!fragment?.name) { + throw new Error(`Could not parse function signature`); + } + + const methodName = fragment.name; + + return { address, method, methodName, args, contract }; +}; diff --git a/utils/print-tx.ts b/utils/print-tx.ts index 58661c0..b273c78 100644 --- a/utils/print-tx.ts +++ b/utils/print-tx.ts @@ -1,23 +1,14 @@ import chalk from 'chalk'; -import { AbstractSigner, Contract } from 'ethers'; +import { Contract } from 'ethers'; import { stringify } from './stringify'; +import { getProvider, getSignerAddress } from './contract'; const title = chalk.gray; const value = chalk.blue.bold; export const printTx = async (contract: Contract, method: string, args: unknown[] = []) => { - const provider = contract.runner?.provider; - - if (!provider) { - throw new Error('Provider is not set'); - } - - if (!(contract.runner instanceof AbstractSigner)) { - throw new Error('Runner is not a signer'); - } - - const signer = contract.runner; - const from = await signer.getAddress(); + const provider = getProvider(contract); + const from = await getSignerAddress(contract); const network = await provider.getNetwork(); const to = await contract.getAddress(); diff --git a/utils/voting.ts b/utils/voting.ts index fd15604..7877ca7 100644 --- a/utils/voting.ts +++ b/utils/voting.ts @@ -1,6 +1,7 @@ import { tmContract, votingContract } from '@contracts'; import { sleep } from './sleep'; import { contractCallTx, contractCallTxWithConfirm } from './call-tx'; +import { getSignerAddress } from './contract'; export const forwardVoteFromTm = async (votingCalldata: string) => { const tx = await contractCallTxWithConfirm(tmContract, 'forward', [votingCalldata]); @@ -57,7 +58,19 @@ export const waitForEnd = async (voteId: number) => { const vote = await votingContract.getVote(voteId); if (vote.open == true) { - console.log('vote checked, still active'); + console.log('waiting for the vote to finish, still active'); await waitForEnd(voteId); } }; + +export const checkTmCanForward = async () => { + const signerAddress = await getSignerAddress(tmContract); + const canForward = await tmContract.canForward(signerAddress, '0x'); + + if (!canForward) { + console.warn('tm can not forward, check your LDO balance'); + return false; + } + + return true; +}; diff --git a/yarn.lock b/yarn.lock index f6fd0ba..bb6cda8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1842,6 +1842,11 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" +string-argv@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"