1
0
forked from mico/idle_moloch
idle_moloch/lib/Baal/test/utils/baal.ts
2024-11-01 11:55:27 +01:00

372 lines
11 KiB
TypeScript

import { expect } from 'chai';
import { BigNumber, BigNumberish, Contract, ContractTransaction } from 'ethers';
import { ethers } from 'hardhat';
import { Baal, BaalSummoner, MultiSend, Poster } from '../../src/types';
import { encodeMultiAction } from '../../src/util';
import { moveForwardPeriods } from './evm';
import { getSaltNonce } from './safe';
export type DAOSettings = {
PROPOSAL_OFFERING: any;
GRACE_PERIOD_IN_SECONDS: any;
VOTING_PERIOD_IN_SECONDS: any;
QUORUM_PERCENT: any;
SPONSOR_THRESHOLD: any;
MIN_RETENTION_PERCENT: any;
MIN_STAKING_PERCENT: any;
TOKEN_NAME: any;
TOKEN_SYMBOL: any;
};
export const abiCoder = ethers.utils.defaultAbiCoder;
export const revertMessages = {
molochAlreadyInitialized: "Initializable: contract is already initialized",
molochConstructorSharesCannotBe0: "shares cannot be 0",
molochConstructorVotingPeriodCannotBe0: "votingPeriod cannot be 0",
submitProposalExpired: "expired",
submitProposalOffering: "Baal requires an offering",
submitProposalVotingPeriod: "!votingPeriod",
submitProposalArrays: "!array parity",
submitProposalArrayMax: "array max",
submitProposalFlag: "!flag",
sponsorProposalExpired: "expired",
sponsorProposalSponsor: "!sponsor",
sponsorProposalExists: "!exist",
sponsorProposalSponsored: "sponsored",
submitVoteNotSponsored: "!sponsored",
submitVoteTimeEnded: "ended",
submitVoteVoted: "voted",
submitVoteMember: "!member",
submitVoteWithSigTimeEnded: "ended",
submitVoteWithSigVoted: "voted",
submitVoteWithSigMember: "!member",
proposalMisnumbered: "!exist",
unsetGuildTokensLastToken:
"reverted with panic code 0x32 (Array accessed at an out-of-bounds or negative index)",
sharesTransferPaused: "shares: !transferable",
sharesInsufficientBalance:
"ERC20: transfer amount exceeds balance",
// -----
lootAlreadyInitialized: "Initializable: contract is already initialized",
molochSetupSharesNoShares: "shares != 0",
proposalNotSponsored: "!sponsor",
sponsorProposalNotSubmitted: "!submitted",
submitVoteNotVoting: "!voting",
processProposalNotReady: "!ready",
ragequitUnordered: "!order",
// unsetGuildTokensLastToken: 'reverted with panic code 0x32 (Array accessed at an out-of-bounds or negative index)',
sharesInsufficientApproval: "ERC20: insufficient allowance", // Error: Transaction reverted without a reason string
lootTransferPaused: "loot: !transferable",
lootInsufficientBalance:
"ERC20: transfer amount exceeds balance",
// lootInsufficientApproval: 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)',
lootInsufficientApproval: "ERC20: insufficient allowance", // Error: Transaction reverted without a reason string
mintSharesArrayParity: "!array parity",
burnSharesArrayParity: "!array parity",
burnSharesInsufficientShares: "ERC20: burn amount exceeds balance",
mintLootArrayParity: "!array parity",
burnLootArrayParity: "!array parity",
burnLootInsufficientShares:
"ERC20: burn amount exceeds balance",
cancelProposalNotVoting: "!voting",
cancelProposalNotCancellable: "!cancellable",
baalOrAdmin: "!baal & !admin",
baalOrManager: "!baal & !manager",
baalOrGovernor: "!baal & !governor",
permitNotAuthorized: "!authorized",
permitExpired: "expired",
notEnoughGas: "not enough gas",
baalGasToHigh: "baalGas to high",
OwnableCallerIsNotTheOwner: "Ownable: caller is not the owner",
};
export type ProposalType = {
flag: BigNumberish;
account?: `0x${string}`;
data: string;
details: string;
expiration: BigNumberish;
baalGas: BigNumberish;
};
export const PROPOSAL_STATES = {
UNBORN: 0,
SUBMITTED: 1,
VOTING: 2,
CANCELLED: 3,
GRACE: 4,
READY: 5,
PROCESSED: 6,
DEEFEATED: 7,
};
export enum SHAMAN_PERMISSIONS {
NONE,
ADMIN,
MANAGER,
ADMIN_MANAGER,
GOVERNANCE,
ADMIN_GOVERNANCE,
MANAGER_GOVERNANCE,
ALL,
};
export const defaultDAOSettings: DAOSettings = {
GRACE_PERIOD_IN_SECONDS: 43200,
VOTING_PERIOD_IN_SECONDS: 432000,
PROPOSAL_OFFERING: 0,
SPONSOR_THRESHOLD: 1,
MIN_RETENTION_PERCENT: 0,
MIN_STAKING_PERCENT: 0,
QUORUM_PERCENT: 0,
TOKEN_NAME: "wrapped ETH",
TOKEN_SYMBOL: "WETH",
};
export const defaultMetadataConfig = {
CONTENT: '{"name":"test"}',
TAG: "daohaus.summoner.daoProfile",
};
export const defaultProposalSettings = {
DETAILS: 'all hail baal',
EXPIRATION: 0,
BAAL_GAS: 0,
};
export type SummonSetup = {
loot: number;
lootPaused: boolean;
shamanPermissions: SHAMAN_PERMISSIONS;
shares: number;
sharesPaused: boolean;
};
export const defaultSummonSetup: SummonSetup = {
loot: 500,
lootPaused: false,
shamanPermissions: SHAMAN_PERMISSIONS.ALL,
shares: 100,
sharesPaused: false,
};
export type NewBaalParams = {
baalSummoner: Contract;
baalSingleton: Baal;
poster: Poster;
config: DAOSettings;
adminConfig: [boolean, boolean];
shamans: [string[], number[]];
shares: [string[], number[]];
loots: [string[], number[]];
safeAddress?: `0x${string}`;
forwarderAddress?: `0x${string}`;
lootAddress?: `0x${string}`;
sharesAddress?: `0x${string}`;
saltNonceOverride?: string;
}
export type NewBaalAddresses = {
baal: string;
loot: string;
shares: string;
safe: string;
}
export const getNewBaalAddresses = async (tx: ContractTransaction): Promise<NewBaalAddresses> => {
const receipt = await ethers.provider.getTransactionReceipt(tx.hash);
// console.log({logs: receipt.logs})
let baalSummonAbi = [
"event SummonBaal(address indexed baal, address indexed loot, address indexed shares, address safe, address forwarder, uint256 existingAddrs)",
];
let iface = new ethers.utils.Interface(baalSummonAbi);
let log = iface.parseLog(receipt.logs[receipt.logs.length - 1]);
const { baal, loot, shares, safe } = log.args;
return { baal, loot, shares, safe };
};
export const getBaalParams = async function (
baal: Baal,
poster: Poster,
config: DAOSettings,
adminConfig: [boolean, boolean],
shamans: [string[], number[]],
shares: [string[], number[]],
loots: [string[], number[]],
safeAddress: string = ethers.constants.AddressZero,
forwarderAddress: string = ethers.constants.AddressZero,
lootAddress: string = ethers.constants.AddressZero,
sharesAddress: string = ethers.constants.AddressZero,
) {
const governanceConfig = abiCoder.encode(
["uint32", "uint32", "uint256", "uint256", "uint256", "uint256"],
[
config.VOTING_PERIOD_IN_SECONDS,
config.GRACE_PERIOD_IN_SECONDS,
config.PROPOSAL_OFFERING,
config.QUORUM_PERCENT,
config.SPONSOR_THRESHOLD,
config.MIN_RETENTION_PERCENT,
]
);
const setAdminConfig = await baal.interface.encodeFunctionData(
"setAdminConfig",
adminConfig
);
const setGovernanceConfig = await baal.interface.encodeFunctionData(
"setGovernanceConfig",
[governanceConfig]
);
const setShaman = await baal.interface.encodeFunctionData(
"setShamans",
shamans
);
const mintShares = await baal.interface.encodeFunctionData(
"mintShares",
shares
);
const mintLoot = await baal.interface.encodeFunctionData("mintLoot", loots);
const postMetaData = await poster.interface.encodeFunctionData("post", [
defaultMetadataConfig.CONTENT,
defaultMetadataConfig.TAG,
]);
const posterFromBaal = await baal.interface.encodeFunctionData(
"executeAsBaal",
[poster.address, 0, postMetaData]
);
const initalizationActions = [
setAdminConfig,
setGovernanceConfig,
setShaman,
mintShares,
mintLoot,
posterFromBaal,
];
return {
initParams: abiCoder.encode(
["string", "string", "address", "address", "address", "address"],
[
config.TOKEN_NAME,
config.TOKEN_SYMBOL,
safeAddress,
forwarderAddress,
lootAddress,
sharesAddress,
],
),
initalizationActions,
};
};
// export const setupBaal = async (params: NewBaalParams) => {
export const setupBaal = async ({
baalSummoner,
baalSingleton,
poster,
config,
adminConfig,
shamans,
shares,
loots,
safeAddress,
forwarderAddress,
lootAddress,
sharesAddress,
saltNonceOverride
}: NewBaalParams) => {
const saltNonce = saltNonceOverride || getSaltNonce();
const encodedInitParams = await getBaalParams(
baalSingleton,
poster,
config,
adminConfig,
shamans,
shares,
loots,
safeAddress,
forwarderAddress,
lootAddress,
sharesAddress,
);
const tx = await (baalSummoner as BaalSummoner).summonBaal(
encodedInitParams.initParams,
encodedInitParams.initalizationActions,
saltNonce,
);
return await getNewBaalAddresses(tx);
};
export type ProposalParams = {
baal: Baal;
encodedAction: string;
proposal: ProposalType;
proposalId?: BigNumberish;
daoSettings?: DAOSettings;
extraSeconds?: number;
};
export const submitAndProcessProposal = async ({
baal,
encodedAction,
proposal,
proposalId,
daoSettings = defaultDAOSettings,
extraSeconds = 2,
}: ProposalParams) => {
await baal.submitProposal(encodedAction, proposal.expiration, proposal.baalGas, ethers.utils.id(proposal.details));
const id = proposalId ? proposalId : await baal.proposalCount();
await baal.submitVote(id, true);
await moveForwardPeriods(daoSettings.VOTING_PERIOD_IN_SECONDS, extraSeconds);
return await baal.processProposal(id, encodedAction);
};
export const setShamanProposal = async (
baal: Baal,
multisend: MultiSend,
shamanAddress: string,
permission: BigNumberish,
daoSettings = defaultDAOSettings,
extraSeconds = 2,
) => {
const setShaman = baal.interface.encodeFunctionData('setShamans', [
[shamanAddress],
[permission],
]);
const setShamanAction = encodeMultiAction(
multisend,
[setShaman],
[baal.address],
[BigNumber.from(0)],
[0]
);
// ----
await baal.submitProposal(setShamanAction, 0, 0, '');
const proposalId = await baal.proposalCount();
await baal.submitVote(proposalId, true);
await moveForwardPeriods(daoSettings.VOTING_PERIOD_IN_SECONDS, extraSeconds);
await baal.processProposal(proposalId, setShamanAction);
return proposalId;
};
export const verifyProposal = (prop1: any, prop2: any, overrides?: any) => {
for (let key in prop1) {
if (Number.isInteger(+key)) {
continue;
}
if (overrides && key in overrides) {
// console.log('override', key)
expect(prop1[key]).to.equal(overrides[key]);
} else {
// console.log('check', key)
expect(prop1[key]).to.equal(prop2[key]);
}
}
};