Skip to content

Commit

Permalink
feat: Add resolveProposal script to retrieve all possible UMA bonds (U…
Browse files Browse the repository at this point in the history
…MAprotocol#3821)

* feat: Add manual resolveProposal script

* make script simpler

* Update resolveProposal.js

* Update README.md
  • Loading branch information
nicholaspai authored Feb 7, 2022
1 parent 5cd7e7b commit 4b2ff2f
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 39 deletions.
17 changes: 17 additions & 0 deletions packages/financial-templates-lib/src/helpers/multicall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { MulticallWeb3 } from "@uma/contracts-node";
import { TransactionDataDecoder } from "./AbiUtils";
import assert from "assert";
import type Web3 from "web3";
import type { TransactionReceipt } from "web3-core";

// Decode `returnData` into Javascript type using known contract ABI informtaion
// from the `callData` originally used to produce `returnData`.
Expand Down Expand Up @@ -42,6 +43,22 @@ export const aggregateTransactionsAndCall = async (
return returnData.map((data, i) => _decodeOutput(transactions[i].callData, data, web3));
};

export const aggregateTransactionsAndSend = async (
multicallAddress: string,
web3: Web3,
transactions: Transaction[],
txnConfigObj: any
): Promise<TransactionReceipt> => {
const multicallContract = (new web3.eth.Contract(getAbi("Multicall"), multicallAddress) as unknown) as MulticallWeb3;
for (let i = 0; i < transactions.length; i++) {
assert(
transactions[i].target && transactions[i].callData,
"transaction expected in form {target: address, callData: bytes}"
);
}
return await multicallContract.methods.aggregate((transactions as unknown) as [string, string][]).send(txnConfigObj);
};

export const multicallAddressMap: { [network: string]: { multicall: string } } = {
mainnet: { multicall: "0xeefba1e63905ef1d7acba5a8513c70307c1ce441" },
kovan: { multicall: "0x2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a" },
Expand Down
11 changes: 10 additions & 1 deletion packages/scripts/src/admin-proposals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ HARDHAT_CHAIN_ID=1 yarn hardhat node --fork https://mainnet.infura.io/v3/YOUR-IN

Be sure to set `NODE_URL_1=http://localhost:9545` in your environment if you want to continue running scripts against the forked mainnet network.

2. Request to impersonate accounts we'll need to propose and vote on admin proposals:
2. Request to impersonate accounts we'll need to propose and vote on admin proposals. This script will also mint the
wallet enough UMA required to submit a new governance proposal.

```sh
./packages/scripts/setupFork.sh
Expand Down Expand Up @@ -62,6 +63,14 @@ node ./packages/scripts/src/admin-proposals/collateral.js \
--network mainnet-fork
```

6. Retrieve bond staked for submitting proposal:

```
node ./packages/scripts/src/admin-proposals/resolveProposal.js \
--sender 0x2bAaA41d155ad8a4126184950B31F50A1513cE25 \
--network mainnet-fork
```

## Running on a public network in production mode

For production, simply run the script with a production network passed to the `--network` flag (along with other params like --keys) like so: `node ... --network mainnet_gckms --keys deployer`.
103 changes: 103 additions & 0 deletions packages/scripts/src/admin-proposals/resolveProposal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Description:
// - Resolve all possible proposals for a specific sender.

// Usage:
// NODE_URL_1=http://localhost:9545 \
// node ./packages/scripts/src/admin-proposals/resolveProposal.js \
// --network mainnet_mnemonic \
// --sender <ADDRESS>

require("dotenv").config();
const { getWeb3ByChainId } = require("@uma/common");
const { setupGasEstimator } = require("./utils");
const {
aggregateTransactionsAndSend,
multicallAddressMap,
aggregateTransactionsAndCall,
} = require("@uma/financial-templates-lib");
const { _getContractAddressByName } = require("../utils");

const hre = require("hardhat");
const { getContract } = hre;
const Proposer = getContract("Proposer");
const Governor = getContract("Governor");
const argv = require("minimist")(process.argv.slice(), {
string: [
// proposer address to resolve proposals for
"sender",
],
});

async function run() {
const web3 = getWeb3ByChainId(1);
const accounts = await web3.eth.getAccounts();
const sender = argv.sender;

// Initialize Eth contracts by grabbing deployed addresses from networks/1.json file.
const gasEstimator = await setupGasEstimator();

const proposer = new web3.eth.Contract(Proposer.abi, await _getContractAddressByName("Proposer", 1));
const governor = new web3.eth.Contract(Governor.abi, await _getContractAddressByName("Governor", 1));

// Query the state of all possible bonded proposals and determine which ones can be resolved. If the `lockedBond`
// property of the `bondedProposal` is 0 then the proposal cannot be resolved.
const totalProposals = Number(await governor.methods.numProposals().call());
const bondedProposalTransactions = [...Array(totalProposals).keys()].map((i) => {
return { target: proposer.options.address, callData: proposer.methods.bondedProposals(i).encodeABI() };
});

console.group(`\n📢 There are a total of ${totalProposals.length} possible resolvable proposals`);

// Read bonded proposal state in a single batched web3 call:
console.log("- Reading bondedProposals() state for all ids...");
const bondedProposals = await aggregateTransactionsAndCall(
multicallAddressMap.mainnet.multicall,
web3,
bondedProposalTransactions
);

const proposalToResolve = [];
for (let i = 0; i < bondedProposals.length; i++) {
if (web3.utils.toBN(bondedProposals[i].lockedBond.toString()).gt(web3.utils.toBN("0"))) {
console.log(
`- Proposal #${i} has a locked bond of ${bondedProposals[i].lockedBond.toString()} UMA, proposer was @ ${
bondedProposals[i].sender
}`
);
if (bondedProposals[i].sender === sender) {
proposalToResolve.push(i);
} else {
console.log(`Skipping proposal #${i}, only resolving bonded proposals where sender is ${sender}`);
}
}
}

if (proposalToResolve.length === 0) {
console.groupEnd("No proposals to resolve.");
} else {
console.log(`- Resolving ${proposalToResolve.length} proposals: ${proposalToResolve}`);
const resolveProposalTransactions = proposalToResolve.map((i) => {
return { target: proposer.options.address, callData: proposer.methods.resolveProposal(i).encodeABI() };
});
const txn = await aggregateTransactionsAndSend(
multicallAddressMap.mainnet.multicall,
web3,
resolveProposalTransactions,
{ from: accounts[0], ...gasEstimator.getCurrentFastPrice() }
);
console.groupEnd("Transaction: ", txn?.transactionHash);
}
}

function main() {
const startTime = Date.now();
run()
.catch((err) => {
console.error(err);
})
.finally(() => {
const timeElapsed = Date.now() - startTime;
console.log(`Done in ${(timeElapsed / 1000).toFixed(2)}s`);
});
}
main();
39 changes: 1 addition & 38 deletions packages/scripts/src/admin-proposals/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const hre = require("hardhat");
const { getContract } = hre;
const { getWeb3ByChainId, interfaceName, MAX_UINT_VAL } = require("@uma/common");
const { _getContractAddressByName } = require("../utils");
const { GasEstimator, aggregateTransactionsAndCall, multicallAddressMap } = require("@uma/financial-templates-lib");
const { GasEstimator } = require("@uma/financial-templates-lib");
const winston = require("winston");
const Web3 = require("Web3");
const { fromWei, toBN } = Web3.utils;
Expand Down Expand Up @@ -254,8 +254,6 @@ const verifyGovernanceRootTunnelMessage = async (targetAddress, message, governo
};

const proposeAdminTransactions = async (web3, adminProposalTransactions, caller, gasPriceObj) => {
await resolveProposals(web3, caller);

const proposer = new web3.eth.Contract(Proposer.abi, await _getContractAddressByName("Proposer", 1));
console.group(`\n📨 Sending to proposer @ ${proposer.options.address}`);
console.log(`- Admin proposal contains ${adminProposalTransactions.length} transactions`);
Expand Down Expand Up @@ -305,41 +303,6 @@ const proposeAdminTransactions = async (web3, adminProposalTransactions, caller,
console.groupEnd();
};

const resolveProposals = async (web3, caller, proposalsToLookback = 10) => {
const proposer = new web3.eth.Contract(Proposer.abi, await _getContractAddressByName("Proposer", 1));
const governor = new web3.eth.Contract(Governor.abi, await _getContractAddressByName("Governor", 1));

console.group(`\nResolving the latest ${proposalsToLookback} proposals`);
const totalProposals = Number(await governor.methods.numProposals().call());
const bondedProposalTransactions = [...Array(totalProposals).keys()].map((i) => {
return { target: proposer.options.address, callData: proposer.methods.bondedProposals(i).encodeABI() };
});
const bondedProposals = await aggregateTransactionsAndCall(
multicallAddressMap.mainnet.multicall,
web3,
bondedProposalTransactions
);
for (let i = bondedProposals.length - 1; i >= bondedProposals.length - 1 - proposalsToLookback; i--) {
const bondedProposal = bondedProposals[i];
if (bondedProposal.sender === caller && toBN(bondedProposal.lockedBond.toString()).gt(toBN(0))) {
console.log(
`- Proposal #${i} has a locked bond of ${bondedProposal.lockedBond.toString()} UMA, proposer was @ ${
bondedProposal.sender
}`
);
try {
const txn = await proposer.methods.resolveProposal(i).send({ from: caller });
console.log("- Transaction: ", txn?.transactionHash);
} catch (err) {
console.log("- Resolution failed, has price been resolved for admin proposal?", err);
continue;
}
}
}
console.log("- No more proposals to resolve");
console.groupEnd();
};

module.exports = {
L2_ADMIN_NETWORK_NAMES,
L2_ADMIN_NETWORKS,
Expand Down

0 comments on commit 4b2ff2f

Please sign in to comment.