Skip to content

Commit

Permalink
Merge pull request #900 from fei-protocol/auto-register-metadata
Browse files Browse the repository at this point in the history
Auto generate metadata for TC proposals
  • Loading branch information
thomas-waite authored Jun 28, 2022
2 parents 7c03ebd + 3d67709 commit 3c0daae
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 73 deletions.
2 changes: 1 addition & 1 deletion block.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
15018600
15039959
5 changes: 4 additions & 1 deletion proposals/description/fip_x.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ const fip_x: ProposalDescription = {
description: ''
}
],
description: 'fip_x will change the game!'
description: `
[TITLE] /n/n
[BODY OF PROPOSAL] \n\n
`
};

export default fip_x;
34 changes: 1 addition & 33 deletions protocol-configuration/proposalsConfig.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,5 @@
import { ProposalCategory, TemplatedProposalsConfigMap } from '@custom-types/types';

import tip_117 from '@proposals/description/tip_117';
import pod_exec_v2 from '@proposals/description/pod_exec_v2';
import tip_115 from '@proposals/description/tip_115';

const proposals: TemplatedProposalsConfigMap = {
tip_117: {
deploy: false, // deploy flag for whether to run deploy action during e2e tests or use mainnet state
totalValue: 0, // amount of ETH to send to DAO execution
proposal: tip_117, // full proposal file, imported from '@proposals/description/fip_xx.ts'
proposalId: '',
affectedContractSignoff: [],
deprecatedContractSignoff: [],
category: ProposalCategory.DAO
},
tip_115: {
deploy: false, // deploy flag for whether to run deploy action during e2e tests or use mainnet state
totalValue: 0, // amount of ETH to send to DAO execution
proposal: tip_115, // full proposal file, imported from '@proposals/description/fip_xx.ts'
proposalId: '',
affectedContractSignoff: [],
deprecatedContractSignoff: [],
category: ProposalCategory.TC
},
pod_exec_v2: {
deploy: false, // deploy flag for whether to run deploy action during e2e tests or use mainnet state
totalValue: 0, // amount of ETH to send to DAO execution
proposal: pod_exec_v2, // full proposal file, imported from '@proposals/description/fip_xx.ts'
proposalId: '',
affectedContractSignoff: [],
deprecatedContractSignoff: [],
category: ProposalCategory.TC
}
};
const proposals: TemplatedProposalsConfigMap = {};

export default proposals;
65 changes: 49 additions & 16 deletions scripts/utils/constructProposalCalldata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { ethers } from 'hardhat';
import { Interface } from '@ethersproject/abi';
import { utils } from 'ethers';
import { getAllContractAddresses, getAllContracts } from '@test/integration/setup/loadContracts';
import { ProposalCategory, ProposalDescription, TemplatedProposalDescription } from '@custom-types/types';
import { ProposalCategory, TemplatedProposalDescription } from '@custom-types/types';
import proposals from '@protocol/proposalsConfig';
import { TRIBAL_COUNCIL_POD_ID } from '@protocol/optimisticGovernance';
import { abi as TimelockControllerABI } from '../../artifacts/@openzeppelin/contracts/governance/TimelockController.sol/TimelockController.json';
import { abi as FeiDAOABI } from '../../artifacts/contracts/dao/governor/FeiDAO.sol/FeiDAO.json';
import { abi as MetadataRegistryABI } from '../../artifacts/contracts/pods/GovernanceMetadataRegistry.sol/GovernanceMetadataRegistry.json';

type ExtendedAlphaProposal = {
targets: string[];
Expand All @@ -15,6 +19,11 @@ type ExtendedAlphaProposal = {
description: string;
};

export interface PodConfig {
id: number;
timelockAddress: string;
}

/**
* Take in a hardhat proposal object and output the proposal calldatas
* See `proposals/utils/getProposalCalldata.js` on how to construct the proposal calldata
Expand All @@ -29,24 +38,26 @@ export async function constructProposalCalldata(proposalName: string): Promise<s

console.log(proposals[proposalName].category);
if (proposals[proposalName].category === ProposalCategory.TC) {
return getTimelockCalldata(proposal, proposalInfo, contractAddresses.tribalCouncilTimelock);
const podConfig: PodConfig = {
id: TRIBAL_COUNCIL_POD_ID,
timelockAddress: contractAddresses.tribalCouncilTimelock
};
return getPodCalldata(proposal, proposalInfo, podConfig);
}

return getDAOCalldata(proposal);
}

function getDAOCalldata(proposal: ExtendedAlphaProposal): string {
const proposeFuncFrag = new Interface([
'function propose(address[] memory targets,uint256[] memory values,bytes[] memory calldatas,string memory description) public returns (uint256)'
]);
const feiDAOInterface = new Interface(FeiDAOABI);

const combinedCalldatas = [];
for (let i = 0; i < proposal.targets.length; i++) {
const sighash = utils.id(proposal.signatures[i]).slice(0, 10);
combinedCalldatas.push(`${sighash}${proposal.calldatas[i].slice(2)}`);
}

const calldata = proposeFuncFrag.encodeFunctionData('propose', [
const calldata = feiDAOInterface.encodeFunctionData('propose', [
proposal.targets,
proposal.values,
combinedCalldatas,
Expand All @@ -56,15 +67,13 @@ function getDAOCalldata(proposal: ExtendedAlphaProposal): string {
return calldata;
}

function getTimelockCalldata(
function getPodCalldata(
proposal: ExtendedAlphaProposal,
proposalInfo: TemplatedProposalDescription,
timelockAddress: string
podConfig: PodConfig
): string {
const proposeFuncFrag = new Interface([
'function scheduleBatch(address[] calldata targets,uint256[] calldata values,bytes[] calldata data,bytes32 predecessor,bytes32 salt,uint256 delay) public',
'function executeBatch(address[] calldata targets,uint256[] calldata values,bytes[] calldata data,bytes32 predecessor,bytes32 salt) public'
]);
const timelockControllerInterface = new Interface(TimelockControllerABI);
const metadataRegistryInterface = new Interface(MetadataRegistryABI);

const combinedCalldatas = [];
for (let i = 0; i < proposal.targets.length; i++) {
Expand All @@ -75,7 +84,8 @@ function getTimelockCalldata(
const salt = ethers.utils.id(proposalInfo.title);
const predecessor = ethers.constants.HashZero;

const calldata = proposeFuncFrag.encodeFunctionData('scheduleBatch', [
// Schedule transaction calldata
const calldata = timelockControllerInterface.encodeFunctionData('scheduleBatch', [
proposal.targets,
proposal.values,
combinedCalldatas,
Expand All @@ -84,27 +94,50 @@ function getTimelockCalldata(
345600
]);

const executeCalldata = proposeFuncFrag.encodeFunctionData('executeBatch', [
// Execute via timelock calldata
const executeCalldata = timelockControllerInterface.encodeFunctionData('executeBatch', [
proposal.targets,
proposal.values,
combinedCalldatas,
predecessor,
salt
]);

// Register metadata calldata
const proposalId = computeBatchProposalId(proposal.targets, proposal.values, combinedCalldatas, predecessor, salt);
const registerMetadataCalldata = metadataRegistryInterface.encodeFunctionData('registerProposal', [
podConfig.id,
proposalId,
proposalInfo.description
]);

// Pod execute calldata
const podExecutorFunctionFragment = new Interface([
'function executeBatch(address timelock,address[] calldata targets,uint256[] calldata values,bytes[] calldata payloads,bytes32 predecessor,bytes32 salt) public'
]);

const podExecuteCalldata = podExecutorFunctionFragment.encodeFunctionData('executeBatch', [
timelockAddress,
podConfig.timelockAddress,
proposal.targets,
proposal.values,
combinedCalldatas,
predecessor,
salt
]);

return `Calldata: ${calldata}\nExecute Calldata: ${executeCalldata}\nPod Executor Calldata: ${podExecuteCalldata}`;
return `Calldata: ${calldata}\nMetadata Calldata: ${registerMetadataCalldata}\nExecute Calldata: ${executeCalldata}\nPod Executor Calldata: ${podExecuteCalldata}`;
}

export function computeBatchProposalId(
targets: string[],
values: BigNumber[],
payloads: string[],
predecessor: string,
salt: string
): string {
const dataToHash = ethers.utils.defaultAbiCoder.encode(
['address[]', 'uint256[]', 'bytes[]', 'bytes32', 'bytes32'],
[targets, values.map((x) => x.toString()), payloads, predecessor, salt]
);
return ethers.utils.keccak256(dataToHash);
}
46 changes: 26 additions & 20 deletions scripts/utils/simulateTimelockProposal.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import { ethers } from 'hardhat';
import {
MainnetContracts,
NamedAddresses,
ProposalDescription,
TemplatedProposalDescription
} from '@custom-types/types';
import { MainnetContracts, NamedAddresses, TemplatedProposalDescription } from '@custom-types/types';
import { OptimisticTimelock } from '@custom-types/contracts';
import { getImpersonatedSigner, time } from '@test/helpers';
import { Contract } from '@ethersproject/contracts';
import { forceEth } from '@test/integration/setup/utils';

export async function simulateOAProposal(
proposalInfo: TemplatedProposalDescription,
contracts: MainnetContracts,
contractAddresses: NamedAddresses,
logging = false
) {
const timelockOA = contracts.optimisticTimelock as OptimisticTimelock;
const multisigAddressOA = contractAddresses.optimisticMultisig as string;
await simulateTimelockProposal(timelockOA, multisigAddressOA, proposalInfo, contracts, contractAddresses, logging);
}
import { TRIBAL_COUNCIL_POD_ID } from '@protocol/optimisticGovernance';
import { PodConfig } from './constructProposalCalldata';

export async function simulateTCProposal(
proposalInfo: TemplatedProposalDescription,
Expand All @@ -29,16 +15,29 @@ export async function simulateTCProposal(
) {
const timelockTC = contracts.tribalCouncilTimelock as OptimisticTimelock;
const multisigAddressTC = contractAddresses.tribalCouncilSafe as string;
await simulateTimelockProposal(timelockTC, multisigAddressTC, proposalInfo, contracts, contractAddresses, logging);
const podConfig: PodConfig = {
id: TRIBAL_COUNCIL_POD_ID,
timelockAddress: contractAddresses.tribalCouncilTimelock
};
await simulatePodProposal(
timelockTC,
multisigAddressTC,
proposalInfo,
contracts,
contractAddresses,
logging,
podConfig
);
}

export async function simulateTimelockProposal(
export async function simulatePodProposal(
timelock: OptimisticTimelock,
multisigAddress: string,
proposalInfo: TemplatedProposalDescription,
contracts: MainnetContracts,
contractAddresses: NamedAddresses,
logging = false
logging = false,
podConfig: PodConfig
) {
await forceEth(multisigAddress);
const signer = await getImpersonatedSigner(multisigAddress);
Expand Down Expand Up @@ -88,6 +87,13 @@ export async function simulateTimelockProposal(
if (!proposalId || !(await timelock.isOperation(proposalId))) {
const schedule = await timelock.connect(signer).scheduleBatch(targets, values, datas, predecessor, salt, delay);
console.log('Calldata:', schedule.data);

// Register the pod's metadata
console.log(`Registering proposal ${proposalId} of pod ${podConfig.id}`);
const registerTx = await contracts.governanceMetadataRegistry
.connect(signer)
.registerProposal(podConfig.id, proposalId, proposalInfo.description);
console.log('Metadata tx: ', registerTx.data);
} else {
console.log('Already scheduled proposal');
}
Expand Down
2 changes: 0 additions & 2 deletions test/integration/setup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
ContractAccessRights,
TestCoordinator,
ContractsAndAddresses,
ProposalConfig,
TemplatedProposalConfig,
namedContractsToNamedAddresses,
NamedAddresses,
Expand All @@ -21,7 +20,6 @@ import { sudo } from '@scripts/utils/sudo';
import constructProposal from '@scripts/utils/constructProposal';
import '@nomiclabs/hardhat-ethers';
import { resetFork } from '@test/helpers';
import { simulateOAProposal } from '@scripts/utils/simulateTimelockProposal';
import { simulateTCProposal } from '@scripts/utils/simulateTimelockProposal';
import { simulateDEBUGProposal } from '@scripts/utils/simulateDEBUGProposal';
import { forceEth } from '@test/integration/setup/utils';
Expand Down
36 changes: 36 additions & 0 deletions test/unit/scriptUtils/computeBatchProposalId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { computeBatchProposalId } from '@scripts/utils/constructProposalCalldata';
import { expect } from 'chai';
import { BigNumber } from 'ethers';

const toBN = BigNumber.from;

describe('Compute proposal ID off-chain', () => {
it('should compute proposalId for a batched proposal', () => {
// Fixture is a real-transaction submitted on-chain
const expectedProposalId = '0x81c419dbb3c44645493c214eee0ceaf273ba870d1e7e1c48422e2762f60e0db4';
const targets = [
'0x02435948F84d7465FB71dE45ABa6098Fc6eC2993',
'0x02435948F84d7465FB71dE45ABa6098Fc6eC2993',
'0x02435948F84d7465FB71dE45ABa6098Fc6eC2993',
'0xF7991f4698ffb6716982aec7F78964Dd731C4A54',
'0xFF6f59333cfD8f4Ebc14aD0a0E181a83e655d257',
'0xFF6f59333cfD8f4Ebc14aD0a0E181a83e655d257',
'0x98E5F5706897074a4664DD3a32eB80242d6E694B'
];
const values = [toBN(0), toBN(0), toBN(0), toBN(0), toBN(0), toBN(0), toBN(0)];
const payloads = [
'0x8320357d00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f7991f4698ffb6716982aec7f78964dd731c4a54',
'0xfe68d76e0000000000000000000000005b86887e171bae0c2c826e87e34df8d558c079b9000000000000000000000000f7991f4698ffb6716982aec7f78964dd731c4a5400000000000000000000000000000000000000000000043c33c193756480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
'0xfe68d76e000000000000000000000000e0f73b8d76d2ad33492f995af218b03564b8ce20000000000000000000000000f7991f4698ffb6716982aec7f78964dd731c4a54000000000000000000000000000000000000000000027b46536c66c8e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
'0x8119c065',
'0xdea02892000000000000000000000000df9ff5c077d9f3427ade67ac2d27a864be6f3187',
'0xdea02892000000000000000000000000f24401f6992faeacbc5d6c6991db15b5f8364a1b',
'0x5d841af5000000000000000000000000000000000000000000000000000000000000003c'
];
const predecessor = '0x0000000000000000000000000000000000000000000000000000000000000000';
const salt = '0x0d0c2b4e41c5fdf9375f2b1601caf0af18e81d6257ea3abb3b72a989fc3698da';

const proposalId = computeBatchProposalId(targets, values, payloads, predecessor, salt);
expect(proposalId).to.be.equal(expectedProposalId);
});
});
3 changes: 3 additions & 0 deletions types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
EthCompoundPCVDeposit,
Fei,
FeiDAO,
GovernanceMetadataRegistry,
GovernorAlpha,
IAaveIncentivesController,
IERC20,
Expand Down Expand Up @@ -273,6 +274,7 @@ export interface MainnetContracts {
rewardsDistributorAdmin: RewardsDistributorAdmin;
restrictedPermissions: RestrictedPermissions;
tribalCouncilTimelock: TimelockController;
governanceMetadataRegistry: GovernanceMetadataRegistry;
}

export interface MainnetContractAddresses {
Expand Down Expand Up @@ -308,6 +310,7 @@ export interface MainnetContractAddresses {
rariRewardsDistributorDelegator: string;
restrictedPermissions: string;
tribalCouncilTimelock: string;
governanceMetadataRegistry: string;
}

export type ContractAccessRights = {
Expand Down

0 comments on commit 3c0daae

Please sign in to comment.