idle_moloch/lib/Baal/test/BaalSafe.test.ts
2024-11-01 11:55:27 +01:00

3672 lines
139 KiB
TypeScript

import { expect } from 'chai';
import { BigNumber } from 'ethers';
import { deployments, ethers, getChainId, getNamedAccounts, getUnnamedAccounts } from 'hardhat';
import { calculateProxyAddress } from "@gnosis.pm/zodiac";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import {
abiCoder,
defaultDAOSettings,
defaultSummonSetup,
ProposalType,
PROPOSAL_STATES,
revertMessages,
setupBaal,
SHAMAN_PERMISSIONS,
verifyProposal,
} from './utils/baal';
import { blockTime, moveForwardPeriods } from './utils/evm';
import { baalSetup, mockBaalLessSharesSetup, ProposalHelpers, Signer } from './utils/fixtures';
import { calculateSafeProxyAddress, getSaltNonce } from './utils/safe';
import signDelegation from "../src/signDelegation";
import signVote from "../src/signVote";
import { sharesRevertMessages } from './utils/token';
import {
Baal,
BaalSummoner,
GnosisSafe,
GnosisSafeProxyFactory,
Loot,
ModuleProxyFactory,
MultiSend,
Poster,
Shares,
TestAvatar,
TestERC20,
} from '../src/types';
import { encodeMultiAction, hashOperation } from '../src/util';
const deploymentConfig = {
...defaultDAOSettings,
TOKEN_NAME: "Baal Shares",
TOKEN_SYMBOL: "BAAL",
};
describe("Baal contract", function () {
let baal: Baal;
let baalSummoner: BaalSummoner;
let lootToken: Loot;
let sharesToken: Shares;
let weth: TestERC20;
let dai: TestERC20;
let multisend: MultiSend;
let forwarder: `0x${string}`;
let gnosisSafe: GnosisSafe;
let chainId: number;
let proposal: ProposalType;
let users: {
[key: string]: Signer;
};
const yes = true;
const no = false;
let proposalHelpers: ProposalHelpers;
beforeEach(async function () {
forwarder = '0x0000000000000000000000000000000000000420';
const {
Baal,
Loot,
Shares,
BaalSummoner,
GnosisSafe,
MultiSend,
WETH,
DAI,
signers,
helpers,
} = await baalSetup({
daoSettings: deploymentConfig,
forwarderAddress: forwarder,
});
baal = Baal;
lootToken = Loot;
sharesToken = Shares;
baalSummoner = BaalSummoner;
gnosisSafe = GnosisSafe;
multisend = MultiSend;
weth = WETH;
dai = DAI;
users = signers;
proposalHelpers = helpers;
chainId = Number(await getChainId());
const selfTransferAction = encodeMultiAction(
multisend,
["0x"],
[gnosisSafe.address],
[BigNumber.from(0)],
[0]
);
proposal = {
flag: 0,
data: selfTransferAction,
details: "all hail baal",
expiration: 0,
baalGas: 0,
};
});
describe("constructor", function () {
it("verify deployment parameters", async () => {
const now = await blockTime();
const gracePeriod = await baal.gracePeriod();
expect(gracePeriod).to.equal(deploymentConfig.GRACE_PERIOD_IN_SECONDS);
const votingPeriod = await baal.votingPeriod();
expect(votingPeriod).to.equal(deploymentConfig.VOTING_PERIOD_IN_SECONDS);
const proposalOffering = await baal.proposalOffering();
expect(proposalOffering).to.equal(deploymentConfig.PROPOSAL_OFFERING);
expect(await sharesToken.paused()).to.equal(false);
expect(await lootToken.paused()).to.equal(false);
const shamans = await baal.shamans(users.shaman.address);
expect(shamans).to.be.equal(7);
const summonerLoot = await lootToken.balanceOf(users.summoner.address);
expect(summonerLoot).to.equal(defaultSummonSetup.loot);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
expect(summonerVotes).to.equal(users.summoner.sharesInitial);
const summonerSelfDelegates = await sharesToken.delegates(
users.summoner.address
);
expect(summonerSelfDelegates).to.equal(users.summoner.address);
const summonerShares = await sharesToken.balanceOf(users.summoner.address);
expect(summonerShares).to.equal(users.summoner.sharesInitial);
const totalLoot = await baal.totalLoot();
expect(totalLoot).to.equal(defaultSummonSetup.loot * 2);
const trustedForwarder = await baal.trustedForwarder();
expect(trustedForwarder).to.equal(forwarder);
});
});
describe("token ownership", function () {
it("can not transfer ownership when not owner", async () => {
expect(await lootToken.owner()).to.equal(baal.address);
await expect(lootToken.transferOwnership(users.summoner.address)).to.be.revertedWith(
revertMessages.OwnableCallerIsNotTheOwner
);
});
it("can not be upgraded when not owner", async () => {
expect(await lootToken.owner()).to.equal(baal.address);
await expect(lootToken.upgradeTo(sharesToken.address)).to.be.revertedWith(
revertMessages.OwnableCallerIsNotTheOwner
);
});
it("can renounce loot token ownership", async () => {
expect(await lootToken.owner()).to.equal(baal.address);
const renounceAction = lootToken.interface.encodeFunctionData(
"renounceOwnership"
);
const renounceFromBaal = baal.interface.encodeFunctionData(
"executeAsBaal",
[lootToken.address, 0, renounceAction]
);
const encodedAction = encodeMultiAction(
multisend,
[renounceFromBaal],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(
proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1
})
)
.to.emit(baal, "ProcessProposal")
.withArgs(1, true, false);
expect(await lootToken.owner()).to.equal(ethers.constants.AddressZero);
});
it("can renounce shares token ownership", async () => {
expect(await sharesToken.owner()).to.equal(baal.address);
const renounceAction = sharesToken.interface.encodeFunctionData(
"renounceOwnership"
);
const renounceFromBaal = baal.interface.encodeFunctionData(
"executeAsBaal",
[sharesToken.address, 0, renounceAction]
);
const encodedAction = encodeMultiAction(
multisend,
[renounceFromBaal],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(
proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1
})
)
.to.emit(baal, "ProcessProposal")
.withArgs(1, true, false);
expect(await sharesToken.owner()).to.equal(ethers.constants.AddressZero);
});
it("can change loot token ownership to avatar", async () => {
expect(await lootToken.owner()).to.equal(baal.address);
const transferOwnershipAction = await lootToken.interface.encodeFunctionData(
"transferOwnership",
[gnosisSafe.address]
);
const transferOwnershipFromBaal = await baal.interface.encodeFunctionData(
"executeAsBaal",
[lootToken.address, 0, transferOwnershipAction]
);
const encodedAction = encodeMultiAction(
multisend,
[transferOwnershipFromBaal],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(
proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1
})
)
.to.emit(baal, "ProcessProposal")
.withArgs(1, true, false);
expect(await lootToken.owner()).to.equal(gnosisSafe.address);
});
it("can change shares token ownership to avatar", async () => {
expect(await sharesToken.owner()).to.equal(baal.address);
const transferOwnershipAction = await sharesToken.interface.encodeFunctionData(
"transferOwnership",
[gnosisSafe.address]
);
const transferOwnershipFromBaal = await baal.interface.encodeFunctionData(
"executeAsBaal",
[sharesToken.address, 0, transferOwnershipAction]
);
const encodedAction = encodeMultiAction(
multisend,
[transferOwnershipFromBaal],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(
proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1
})
)
.to.emit(baal, "ProcessProposal")
.withArgs(1, true, false);
expect(await sharesToken.owner()).to.equal(gnosisSafe.address);
});
it("can eject and upgrade token with eoa", async () => {
// upgrade token contracts to remove baal deps
// call from safe
// remove baal module
// owner should be baal
expect(await sharesToken.owner()).to.equal(baal.address);
const transferOwnershipAction = await sharesToken.interface.encodeFunctionData(
"transferOwnership",
[users.summoner.address]
);
const transferOwnershipFromBaal = await baal.interface.encodeFunctionData(
"executeAsBaal",
[sharesToken.address, 0, transferOwnershipAction]
);
const encodedAction = encodeMultiAction(
multisend,
[transferOwnershipFromBaal],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(
proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1
})
)
.to.emit(baal, "ProcessProposal")
.withArgs(1, true, false);
expect(await sharesToken.owner()).to.equal(users.summoner.address);
// Upgrade Token
const { BaalLessShares } = await mockBaalLessSharesSetup();
const baalLessSharesSingleton = BaalLessShares;
expect(await baalLessSharesSingleton.version()).to.equal(0);
await users.summoner.shares?.upgradeToAndCall(
baalLessSharesSingleton.address,
baalLessSharesSingleton.interface.encodeFunctionData("setUp", [
2
]));
// after upgrade token should have same balances
// after upgrade token should have a version
expect(
await sharesToken.balanceOf(users.summoner.address)
).to.equal(users.summoner.sharesInitial);
const newTokenInterface = baalLessSharesSingleton.attach(sharesToken.address);
expect(await newTokenInterface.version()).to.equal(2);
expect(await newTokenInterface.baal()).to.equal(ethers.constants.AddressZero);
// new owner should be able to mint
await users.summoner.shares?.mint(users.summoner.address, 100);
expect(
await sharesToken.balanceOf(users.summoner.address)
).to.equal(users.summoner.sharesInitial + 100);
});
});
describe("shaman actions - permission level 7 (full)", function () {
const amountToMint = 69;
let currentTotalLoot: number;
let currentTotalShares: number;
this.beforeEach(async function() {
currentTotalLoot = users.summoner.lootInitial + users.applicant.lootInitial;
currentTotalShares = users.summoner.sharesInitial + users.applicant.sharesInitial;
});
it("setAdminConfig", async () => {
await users.shaman.baal?.setAdminConfig(true, true);
expect(await sharesToken.paused()).to.equal(true);
expect(await lootToken.paused()).to.equal(true);
});
it("mint shares - recipient has shares", async () => {
await users.shaman.baal?.mintShares([users.summoner.address], [amountToMint]);
expect(
await sharesToken.balanceOf(users.summoner.address)
).to.equal(users.summoner.sharesInitial + amountToMint);
const votes = await sharesToken.getVotes(users.summoner.address);
expect(votes).to.equal(users.summoner.sharesInitial + amountToMint);
const totalShares = await baal.totalShares();
expect(totalShares).to.equal(
users.summoner.sharesInitial + amountToMint
+ users.applicant.sharesInitial
);
});
it("mint shares - new recipient", async () => {
await users.shaman.baal?.mintShares([users.shaman.address], [amountToMint]);
await blockTime();
expect(await sharesToken.balanceOf(users.shaman.address)).to.equal(amountToMint);
const votes = await sharesToken.getVotes(users.shaman.address);
expect(votes).to.equal(amountToMint);
const shamanDelegate = await sharesToken.delegates(users.shaman.address);
expect(shamanDelegate).to.equal(users.shaman.address);
});
it("mint shares - recipient has delegate - new shares are also delegated", async () => {
await users.summoner.shares?.delegate(users.shaman.address);
// await blockTime();
await users.shaman.baal?.mintShares([users.summoner.address], [amountToMint]);
expect(
await sharesToken.balanceOf(users.summoner.address)
).to.equal(users.summoner.sharesInitial + amountToMint);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
expect(summonerVotes).to.equal(0);
const shamanVotes = await sharesToken.getVotes(users.shaman.address);
expect(shamanVotes).to.equal(users.summoner.sharesInitial + amountToMint);
const summonerDelegate = await sharesToken.delegates(users.summoner.address);
expect(summonerDelegate).to.equal(users.shaman.address);
});
it("mint shares - zero mint amount - no votes", async () => {
await users.shaman.baal?.mintShares([users.shaman.address], [0]);
// await blockTime();
expect(await sharesToken.balanceOf(users.shaman.address)).to.equal(0);
const votes = await sharesToken.getVotes(users.shaman.address);
expect(votes).to.equal(0);
const totalShares = await sharesToken.totalSupply();
expect(totalShares).to.equal(currentTotalShares);
const shamanVotes = await sharesToken.getVotes(users.shaman.address);
expect(shamanVotes).to.equal(0);
const shamanDelegate = await sharesToken.delegates(users.shaman.address);
expect(shamanDelegate).to.equal(ethers.constants.AddressZero);
});
it("mint shares - require fail - array parity", async () => {
await expect(
users.shaman.baal?.mintShares([users.summoner.address], [amountToMint, amountToMint])
).to.be.revertedWith(revertMessages.mintSharesArrayParity);
});
it("burn shares", async () => {
await users.shaman.baal?.burnShares([users.summoner.address], [amountToMint]);
expect(
await sharesToken.balanceOf(users.summoner.address)
).to.equal(users.summoner.sharesInitial - amountToMint);
});
it("burn shares - require fail - array parity", async () => {
await expect(
users.shaman.baal?.burnShares([users.summoner.address], [amountToMint, amountToMint])
).to.be.revertedWith(revertMessages.burnSharesArrayParity);
});
it("burn shares - require fail - insufficent shares", async () => {
await expect(
users.shaman.baal?.burnShares([users.summoner.address], [users.summoner.sharesInitial + 1])
).to.be.revertedWith(revertMessages.burnSharesInsufficientShares);
});
it("mint loot", async () => {
await users.shaman.baal?.mintLoot([users.summoner.address], [amountToMint]);
expect(
await lootToken.balanceOf(users.summoner.address)
).to.equal(defaultSummonSetup.loot + amountToMint);
expect(await baal.totalLoot()).to.equal(currentTotalLoot + amountToMint);
});
it("mint loot - require fail - array parity", async () => {
await expect(
users.shaman.baal?.mintLoot([users.summoner.address], [amountToMint, amountToMint])
).to.be.revertedWith(revertMessages.mintSharesArrayParity);
});
it("burn loot", async () => {
await users.shaman.baal?.burnLoot([users.summoner.address], [amountToMint]);
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(defaultSummonSetup.loot - amountToMint);
expect(await baal.totalLoot()).to.equal(currentTotalLoot - amountToMint);
});
it("burn loot - require fail - array parity", async () => {
await expect(
users.shaman.baal?.burnLoot([users.summoner.address], [amountToMint, amountToMint])
).to.be.revertedWith(revertMessages.burnLootArrayParity);
});
it("burn loot - require fail - insufficent shares", async () => {
await expect(
users.shaman.baal?.burnLoot([users.summoner.address], [defaultSummonSetup.loot + 1])
).to.be.revertedWith(revertMessages.burnLootInsufficientShares);
});
it("set trusted forwarder", async () => {
const newForwarderAddress = '0x0000000000000000000000000000000000000421';
await users.shaman.baal?.setTrustedForwarder(newForwarderAddress);
expect(await baal.trustedForwarder()).to.equal(newForwarderAddress);
});
it("have shaman mint and burn _delegated_ shares", async () => {
const minting = 100;
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(users.applicant.sharesInitial);
// mint shares for a separate member than the summoner
await users.shaman.baal?.mintShares([users.applicant.address], [minting]);
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(users.applicant.sharesInitial + minting);
expect(await sharesToken.delegates(users.applicant.address)).to.equal(
users.applicant.address
);
expect(await sharesToken.getVotes(users.applicant.address)).to.equal(users.applicant.sharesInitial + minting);
expect(await sharesToken.getVotes(users.summoner.address)).to.equal(users.summoner.sharesInitial);
// delegate shares from applicant to the summoner
// const baalAsApplicant = sharesToken.connect(applicant);
await expect(users.applicant.shares?.delegate(users.summoner.address))
.to.emit(sharesToken, 'DelegateChanged')
.withArgs(users.applicant.address, users.applicant.address, users.summoner.address)
.to.emit(sharesToken, 'DelegateVotesChanged')
.withArgs(
users.summoner.address, users.summoner.sharesInitial,
users.summoner.sharesInitial + users.applicant.sharesInitial + minting
);
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(users.applicant.sharesInitial + minting);
expect(await sharesToken.delegates(users.applicant.address)).to.equal(
users.summoner.address
);
expect(await sharesToken.getVotes(users.applicant.address)).to.equal(0);
expect(await sharesToken.getVotes(users.summoner.address)).to.equal(
users.summoner.sharesInitial + users.applicant.sharesInitial + minting
);
// mint shares for the delegator
await expect(users.shaman.baal?.mintShares([users.applicant.address], [minting]))
.to.emit(sharesToken, 'DelegateVotesChanged')
.withArgs(
users.summoner.address,
users.summoner.sharesInitial + users.applicant.sharesInitial + minting,
users.summoner.sharesInitial + users.applicant.sharesInitial + 2 * minting
);
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(
users.applicant.sharesInitial + 2 * minting
);
expect(await sharesToken.delegates(users.applicant.address)).to.equal(
users.summoner.address
);
expect(await sharesToken.getVotes(users.applicant.address)).to.equal(0);
expect(await sharesToken.getVotes(users.summoner.address)).to.equal(
users.summoner.sharesInitial + users.applicant.sharesInitial + 2 * minting
);
// burn shares for the delegator
await users.shaman.baal?.burnShares([users.applicant.address], [minting]);
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(
defaultSummonSetup.shares + minting
);
expect(await sharesToken.delegates(users.applicant.address)).to.equal(
users.summoner.address
);
expect(await sharesToken.getVotes(users.applicant.address)).to.equal(
defaultSummonSetup.shares - minting
);
expect(await sharesToken.getVotes(users.summoner.address)).to.equal(
users.summoner.sharesInitial + users.applicant.sharesInitial + minting
);
});
it("setGovernanceConfig", async () => {
const governanceConfig = abiCoder.encode(
["uint32", "uint32", "uint256", "uint256", "uint256", "uint256"],
[10, 20, 50, 1, 2, 3]
);
await users.shaman.baal?.setGovernanceConfig(governanceConfig);
const voting = await baal.votingPeriod();
const grace = await baal.gracePeriod();
const offering = await baal.proposalOffering();
const quorum = await baal.quorumPercent();
const sponsorThreshold = await baal.sponsorThreshold();
const minRetentionPercent = await baal.minRetentionPercent();
expect(voting).to.be.equal(10);
expect(grace).to.be.equal(20);
expect(offering).to.be.equal(50);
expect(quorum).to.be.equal(1);
expect(sponsorThreshold).to.be.equal(2);
expect(minRetentionPercent).to.equal(3);
});
it("setGovernanceConfig - doesnt set voting/grace if =0", async () => {
const governanceConfig = abiCoder.encode(
["uint32", "uint32", "uint256", "uint256", "uint256", "uint256"],
[0, 0, 50, 1, 2, 3]
);
await users.shaman.baal?.setGovernanceConfig(governanceConfig);
const voting = await baal.votingPeriod();
const grace = await baal.gracePeriod();
const offering = await baal.proposalOffering();
const quorum = await baal.quorumPercent();
const sponsorThreshold = await baal.sponsorThreshold();
const minRetentionPercent = await baal.minRetentionPercent();
expect(voting).to.be.equal(deploymentConfig.VOTING_PERIOD_IN_SECONDS);
expect(grace).to.be.equal(deploymentConfig.GRACE_PERIOD_IN_SECONDS);
expect(offering).to.be.equal(50);
expect(quorum).to.be.equal(1);
expect(sponsorThreshold).to.be.equal(2);
expect(minRetentionPercent).to.equal(3);
});
it("cancelProposal - happy case - as gov shaman", async () => {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.shaman.baal?.cancelProposal(1); // cancel as gov shaman
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.CANCELLED);
});
it("cancelProposal - happy case - as proposal sponsor", async () => {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.cancelProposal(1); // cancel as sponsor
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.CANCELLED);
});
// TODO: get prior votes is 100 and threshold is 1
it("cancelProposal - happy case - after undelegation", async () => {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
// transfer all shares/votes to shaman
await users.summoner.shares?.transfer(
users.shaman.address,
users.summoner.sharesInitial
);
await users.applicant.baal?.cancelProposal(1); // cancel as rando
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.CANCELLED);
});
it("cancelProposal - require fail - not cancellable by rando", async () => {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
expect(users.applicant.baal?.cancelProposal(1)).to.be.revertedWith(
revertMessages.cancelProposalNotCancellable
);
});
it("cancelProposal - require fail - !voting (submitted)", async () => {
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.SUBMITTED);
await expect(baal.cancelProposal(1)).to.be.revertedWith(
revertMessages.cancelProposalNotVoting
);
});
it("cancelProposal - require fail - !voting (grace)", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
// add 1 extra second to push us into grace period
await moveForwardPeriods(
defaultDAOSettings.VOTING_PERIOD_IN_SECONDS,
1,
1
);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.GRACE);
await expect(baal.cancelProposal(1)).to.be.revertedWith(
revertMessages.cancelProposalNotVoting
);
});
it("cancelProposal - require fail - !voting (defeated)", async () => {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.DEEFEATED);
await expect(baal.cancelProposal(1)).to.be.revertedWith(
revertMessages.cancelProposalNotVoting
);
});
it("cancelProposal - require fail - !voting (cancelled)", async () => {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.cancelProposal(1);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.CANCELLED);
await expect(baal.cancelProposal(1)).to.be.revertedWith(
revertMessages.cancelProposalNotVoting
);
});
it("cancelProposal - require fail - !voting (ready)", async () => {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.READY);
await expect(baal.cancelProposal(1)).to.be.revertedWith(
revertMessages.cancelProposalNotVoting
);
});
it("cancelProposal - require fail - !voting (processed)", async () => {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.PROCESSED);
await expect(baal.cancelProposal(1)).to.be.revertedWith(
revertMessages.cancelProposalNotVoting
);
});
});
describe("shaman permissions: 0-6", function () {
const governanceConfig = abiCoder.encode(
["uint32", "uint32", "uint256", "uint256", "uint256", "uint256"],
[10, 20, 50, 1, 2, 3]
);
const newForwarderAddress = '0x0000000000000000000000000000000000000421';
beforeEach(async function () {
const shamanAddresses = [
users.shaman.address,
users.s1.address,
users.s2.address,
users.s3.address,
users.s4.address,
users.s5.address,
users.s6.address,
];
const permissions = [0, 1, 2, 3, 4, 5, 6];
const setShaman = baal.interface.encodeFunctionData("setShamans", [
shamanAddresses,
permissions,
]);
const setShamanAction = encodeMultiAction(
multisend,
[setShaman],
[baal.address],
[BigNumber.from(0)],
[0]
);
proposal.data = setShamanAction;
proposal.details = 'Shaman Proposal';
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
const shamanPermission = await baal.shamans(users.shaman.address);
expect(shamanPermission).to.equal(0);
});
it("permission = 0 - all actions fail", async () => {
// admin
await expect(users.shaman.baal?.setAdminConfig(true, true)).to.be.revertedWith(
revertMessages.baalOrAdmin
);
// manager
await expect(
users.shaman.baal?.mintShares([users.shaman.address], [69])
).to.be.revertedWith(revertMessages.baalOrManager);
await expect(
users.shaman.baal?.burnShares([users.shaman.address], [69])
).to.be.revertedWith(revertMessages.baalOrManager);
await expect(
users.shaman.baal?.mintLoot([users.shaman.address], [69])
).to.be.revertedWith(revertMessages.baalOrManager);
await expect(
users.shaman.baal?.burnLoot([users.shaman.address], [69])
).to.be.revertedWith(revertMessages.baalOrManager);
// governor
await expect(
users.shaman.baal?.setGovernanceConfig(governanceConfig)
).to.be.revertedWith(revertMessages.baalOrGovernor);
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await expect(users.shaman.baal?.cancelProposal(2)).to.be.revertedWith(
revertMessages.cancelProposalNotCancellable
);
await expect(users.shaman.baal?.setTrustedForwarder(forwarder)).to.be.revertedWith(
revertMessages.baalOrGovernor
);
});
it("permission = 1 - admin actions succeed", async () => {
// admin - success
await users.s1.baal?.setAdminConfig(true, true);
expect(await sharesToken.paused()).to.equal(true);
expect(await lootToken.paused()).to.equal(true);
// manager - fail
expect(users.s1.baal?.mintShares([users.s1.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
expect(users.s1.baal?.burnShares([users.s1.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
expect(users.s1.baal?.mintLoot([users.s1.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
expect(users.s1.baal?.burnLoot([users.s1.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
// governor - fail
expect(users.s1.baal?.setGovernanceConfig(governanceConfig)).to.be.revertedWith(
revertMessages.baalOrGovernor
);
expect(users.s1.baal?.setTrustedForwarder(forwarder)).to.be.revertedWith(
revertMessages.baalOrGovernor
);
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
expect(users.s1.baal?.cancelProposal(2)).to.be.revertedWith(
revertMessages.cancelProposalNotCancellable
);
});
it("permission = 2 - manager actions succeed", async () => {
// admin - fail
expect(users.s2.baal?.setAdminConfig(true, true)).to.be.revertedWith(
revertMessages.baalOrAdmin
);
// manager - success
await users.s2.baal?.mintShares([users.s2.address], [69]);
expect(await sharesToken.balanceOf(users.s2.address)).to.equal(69);
await users.s2.baal?.burnShares([users.s2.address], [69]);
expect(await sharesToken.balanceOf(users.s2.address)).to.equal(0);
await users.s2.baal?.mintLoot([users.s2.address], [69]);
expect(await lootToken.balanceOf(users.s2.address)).to.equal(69);
await users.s2.baal?.burnLoot([users.s2.address], [69]);
expect(await lootToken.balanceOf(users.s2.address)).to.equal(0);
// cleanup - mint summoner shares so they can submit/sponsor
await users.s2.baal?.mintShares([users.summoner.address], [100]);
// governor - fail
expect(users.s2.baal?.setGovernanceConfig(governanceConfig)).to.be.revertedWith(
revertMessages.baalOrGovernor
);
await expect(users.s2.baal?.setTrustedForwarder(forwarder)).to.be.revertedWith(
revertMessages.baalOrGovernor
);
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
expect(users.s2.baal?.cancelProposal(2)).to.be.revertedWith(
revertMessages.cancelProposalNotCancellable
);
});
it("permission = 3 - admin + manager actions succeed", async () => {
// admin - success
await users.s3.baal?.setAdminConfig(true, true);
expect(await sharesToken.paused()).to.equal(true);
expect(await lootToken.paused()).to.equal(true);
// manager - success
await users.s3.baal?.mintShares([users.s3.address], [69]);
expect(await sharesToken.balanceOf(users.s3.address)).to.equal(69);
await users.s3.baal?.burnShares([users.s3.address], [69]);
expect(await sharesToken.balanceOf(users.s3.address)).to.equal(0);
await users.s3.baal?.mintLoot([users.s3.address], [69]);
expect(await lootToken.balanceOf(users.s3.address)).to.equal(69);
await users.s3.baal?.burnLoot([users.s3.address], [69]);
expect(await lootToken.balanceOf(users.s3.address)).to.equal(0);
// cleanup - mint summoner shares so they can submit/sponsor
await users.s3.baal?.mintShares([users.summoner.address], [100]);
// governor - fail
expect(users.s3.baal?.setGovernanceConfig(governanceConfig)).to.be.revertedWith(
revertMessages.baalOrGovernor
);
expect(users.s3.baal?.setTrustedForwarder(forwarder)).to.be.revertedWith(
revertMessages.baalOrGovernor
);
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
expect(users.s3.baal?.cancelProposal(2)).to.be.revertedWith(
revertMessages.cancelProposalNotCancellable
);
});
it("permission = 4 - governor actions succeed", async () => {
// admin - fail
await expect(users.s4.baal?.setAdminConfig(true, true)).to.be.revertedWith(
revertMessages.baalOrAdmin
);
// manager - fail
await expect(users.s4.baal?.mintShares([users.s4.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
await expect(users.s4.baal?.burnShares([users.s4.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
await expect(users.s4.baal?.mintLoot([users.s4.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
await expect(users.s4.baal?.burnLoot([users.s4.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
// governor - succeed
await users.s4.baal?.setGovernanceConfig(governanceConfig);
const voting = await baal.votingPeriod();
const grace = await baal.gracePeriod();
const offering = await baal.proposalOffering();
const quorum = await baal.quorumPercent();
const sponsorThreshold = await baal.sponsorThreshold();
expect(voting).to.be.equal(10);
expect(grace).to.be.equal(20);
expect(offering).to.be.equal(50);
expect(quorum).to.be.equal(1);
expect(sponsorThreshold).to.be.equal(2);
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.s4.baal?.cancelProposal(2);
const state = await baal.state(2);
expect(state).to.equal(PROPOSAL_STATES.CANCELLED);
await users.s4.baal?.setTrustedForwarder(newForwarderAddress);
expect(await users.s4.baal?.trustedForwarder()).to.equal(newForwarderAddress);
});
it("permission = 5 - admin + governor actions succeed", async () => {
// admin - success
await users.s5.baal?.setAdminConfig(true, true);
expect(await sharesToken.paused()).to.equal(true);
expect(await lootToken.paused()).to.equal(true);
// manager - fail
expect(users.s5.baal?.mintShares([users.s5.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
expect(users.s5.baal?.burnShares([users.s5.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
expect(users.s5.baal?.mintLoot([users.s5.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
expect(users.s5.baal?.burnLoot([users.s5.address], [69])).to.be.revertedWith(
revertMessages.baalOrManager
);
// governor - succeed
await users.s5.baal?.setGovernanceConfig(governanceConfig);
const voting = await baal.votingPeriod();
const grace = await baal.gracePeriod();
const offering = await baal.proposalOffering();
const quorum = await baal.quorumPercent();
const sponsorThreshold = await baal.sponsorThreshold();
expect(voting).to.be.equal(10);
expect(grace).to.be.equal(20);
expect(offering).to.be.equal(50);
expect(quorum).to.be.equal(1);
expect(sponsorThreshold).to.be.equal(2);
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.s5.baal?.cancelProposal(2);
const state = await baal.state(2);
expect(state).to.equal(PROPOSAL_STATES.CANCELLED);
await users.s5.baal?.setTrustedForwarder(newForwarderAddress);
expect(await users.s5.baal?.trustedForwarder()).to.equal(newForwarderAddress);
});
it("permission = 6 - manager + governor actions succeed", async () => {
// admin - fail
expect(users.s6.baal?.setAdminConfig(true, true)).to.be.revertedWith(
revertMessages.baalOrAdmin
);
// manager - success
await users.s6.baal?.mintShares([users.s6.address], [69]);
expect(await sharesToken.balanceOf(users.s6.address)).to.equal(69);
await users.s6.baal?.burnShares([users.s6.address], [69]);
expect(await sharesToken.balanceOf(users.s6.address)).to.equal(0);
await users.s6.baal?.mintLoot([users.s6.address], [69]);
expect(await lootToken.balanceOf(users.s6.address)).to.equal(69);
await users.s6.baal?.burnLoot([users.s6.address], [69]);
expect(await lootToken.balanceOf(users.s6.address)).to.equal(0);
// cleanup - mint summoner shares so they can submit/sponsor
await users.s6.baal?.mintShares([users.summoner.address], [100]);
// governor - succeed
await users.s6.baal?.setGovernanceConfig(governanceConfig);
const voting = await baal.votingPeriod();
const grace = await baal.gracePeriod();
const offering = await baal.proposalOffering();
const quorum = await baal.quorumPercent();
const sponsorThreshold = await baal.sponsorThreshold();
expect(voting).to.be.equal(10);
expect(grace).to.be.equal(20);
expect(offering).to.be.equal(50);
expect(quorum).to.be.equal(1);
expect(sponsorThreshold).to.be.equal(2);
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.s6.baal?.cancelProposal(2);
const state = await baal.state(2);
expect(state).to.equal(PROPOSAL_STATES.CANCELLED);
await users.s6.baal?.setTrustedForwarder(newForwarderAddress);
expect(await users.s6.baal?.trustedForwarder()).to.equal(newForwarderAddress);
});
});
describe("shaman locks", function () {
it("lockAdmin", async function () {
const lockAdmin = baal.interface.encodeFunctionData("lockAdmin");
const lockAdminAction = encodeMultiAction(
multisend,
[lockAdmin],
[baal.address],
[BigNumber.from(0)],
[0]
);
proposal.data = lockAdminAction;
proposal.details = 'Shaman Locks';
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
expect(await baal.adminLock()).to.equal(true);
});
it("lockManager", async () => {
const lockManager = baal.interface.encodeFunctionData(
"lockManager"
);
const lockManagerAction = encodeMultiAction(
multisend,
[lockManager],
[baal.address],
[BigNumber.from(0)],
[0]
);
proposal.data = lockManagerAction;
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
expect(await baal.managerLock()).to.equal(true);
});
it("lockGovernor", async () => {
const lockGovernor = await baal.interface.encodeFunctionData(
"lockGovernor"
);
const lockGovernorAction = encodeMultiAction(
multisend,
[lockGovernor],
[baal.address],
[BigNumber.from(0)],
[0]
);
proposal.data = lockGovernorAction;
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
expect(await baal.governorLock()).to.equal(true);
});
});
describe("setShamans - adminLock (1, 3, 5, 7)", function () {
beforeEach(async function () {
const lockAdmin = baal.interface.encodeFunctionData("lockAdmin");
const lockAdminAction = encodeMultiAction(
multisend,
[lockAdmin],
[baal.address],
[BigNumber.from(0)],
[0]
);
proposal.data = lockAdminAction;
proposal.details = 'Admin Locks';
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
expect(await baal.adminLock()).to.equal(true);
});
it("setShamans - 0 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, 0);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.NONE);
});
it("setShamans - 1 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 2 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.MANAGER);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.MANAGER);
});
it("setShamans - 3 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN_MANAGER);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 4 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.GOVERNANCE);
});
it("setShamans - 5 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN_GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 6 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.MANAGER_GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.MANAGER_GOVERNANCE);
});
it("setShamans - 7 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.summoner.address, SHAMAN_PERMISSIONS.ALL); // use summoner bc shaman default = 7
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.summoner.address)).to.equal(SHAMAN_PERMISSIONS.NONE);
});
});
describe("setShamans - managerLock (2, 3, 6, 7)", function () {
beforeEach(async function () {
const lockManager = baal.interface.encodeFunctionData(
"lockManager"
);
const lockManagerAction = encodeMultiAction(
multisend,
[lockManager],
[baal.address],
[BigNumber.from(0)],
[0]
);
proposal.data = lockManagerAction;
proposal.details = 'Manager Locks';
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
expect(await baal.managerLock()).to.equal(true);
});
it("setShamans - 0 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.NONE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.NONE);
});
it("setShamans - 1 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ADMIN);
});
it("setShamans - 2 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.MANAGER);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 3 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN_MANAGER);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 4 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.GOVERNANCE);
});
it("setShamans - 5 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN_GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ADMIN_GOVERNANCE);
});
it("setShamans - 6 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.MANAGER_GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 7 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.summoner.address, SHAMAN_PERMISSIONS.ALL); // use summoner bc shaman default = 7
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.summoner.address)).to.equal(SHAMAN_PERMISSIONS.NONE);
});
});
describe("setShamans - governorLock (4, 5, 6, 7)", function () {
beforeEach(async function () {
const lockGovernor = baal.interface.encodeFunctionData(
"lockGovernor"
);
const lockGovernorAction = encodeMultiAction(
multisend,
[lockGovernor],
[baal.address],
[BigNumber.from(0)],
[0]
);
proposal.data = lockGovernorAction;
proposal.details = 'Governor Locks';
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
expect(await baal.governorLock()).to.equal(true);
});
it("setShamans - 0 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.NONE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.NONE);
});
it("setShamans - 1 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ADMIN);
});
it("setShamans - 2 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.MANAGER);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.MANAGER);
});
it("setShamans - 3 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN_MANAGER);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ADMIN_MANAGER);
});
it("setShamans - 4 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 5 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN_GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 6 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.MANAGER_GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 7 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.summoner.address, SHAMAN_PERMISSIONS.ALL); // use summoner bc shaman default = 7
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.summoner.address)).to.equal(SHAMAN_PERMISSIONS.NONE);
});
});
describe("setShamans - all locked", function () {
beforeEach(async function () {
const lockAdmin = baal.interface.encodeFunctionData("lockAdmin");
const lockManager = baal.interface.encodeFunctionData(
"lockManager"
);
const lockGovernor = baal.interface.encodeFunctionData(
"lockGovernor"
);
const lockAllAction = encodeMultiAction(
multisend,
[lockAdmin, lockManager, lockGovernor],
[baal.address, baal.address, baal.address],
[BigNumber.from(0), BigNumber.from(0), BigNumber.from(0)],
[0, 0, 0]
);
proposal.data = lockAllAction;
proposal.details = 'Locks All';
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await baal.submitVote(1, true);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
expect(await baal.adminLock()).to.equal(true);
expect(await baal.managerLock()).to.equal(true);
expect(await baal.governorLock()).to.equal(true);
});
it("setShamans - 0 - success", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.NONE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, false]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.NONE);
});
it("setShamans - 1 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 2 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.MANAGER);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 3 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN_MANAGER);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 4 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 5 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.ADMIN_GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 6 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.shaman.address, SHAMAN_PERMISSIONS.MANAGER_GOVERNANCE);
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.shaman.address)).to.equal(SHAMAN_PERMISSIONS.ALL);
});
it("setShamans - 7 - fail", async () => {
const id = await proposalHelpers.setShamanProposal(baal, multisend, users.summoner.address, SHAMAN_PERMISSIONS.ALL); // use summoner bc shaman default = 7
const propStatus = await baal.getProposalStatus(id);
expect(propStatus).to.eql([false, true, true, true]); // [cancelled, processed, passed, actionFailed]
expect(await baal.shamans(users.summoner.address)).to.equal(SHAMAN_PERMISSIONS.NONE);
});
});
// -----------------------------------------------------------
// ------------------ SHARES ---------------------------------
// -----------------------------------------------------------
describe("erc20 shares - approve", function () {
const amountToApprove = 20;
it("happy case", async () => {
await users.summoner.shares?.approve(users.shaman.address, amountToApprove);
const allowance = await sharesToken.allowance(
users.summoner.address,
users.shaman.address
);
expect(allowance).to.equal(amountToApprove);
});
it("overwrites previous value", async () => {
await users.summoner.shares?.approve(users.shaman.address, amountToApprove);
const allowance = await sharesToken.allowance(
users.summoner.address,
users.shaman.address
);
expect(allowance).to.equal(amountToApprove);
await users.summoner.shares?.approve(users.shaman.address, 50);
const allowance2 = await sharesToken.allowance(
users.summoner.address,
users.shaman.address
);
expect(allowance2).to.equal(50);
});
});
describe("erc20 shares - transfer", function () {
it("transfer to first time recipient - auto self delegates", async () => {
await users.summoner.shares?.transfer(
users.shaman.address,
deploymentConfig.SPONSOR_THRESHOLD
);
const summonerBalance = await sharesToken.balanceOf(users.summoner.address);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
const shamanBalance = await sharesToken.balanceOf(users.shaman.address);
const shamanVotes = await sharesToken.getVotes(users.shaman.address);
expect(summonerBalance).to.equal(users.summoner.sharesInitial - deploymentConfig.SPONSOR_THRESHOLD);
expect(summonerVotes).to.equal(users.summoner.sharesInitial - deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanBalance).to.equal(deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanVotes).to.equal(deploymentConfig.SPONSOR_THRESHOLD);
const summonerCheckpoints = await sharesToken.numCheckpoints(
users.summoner.address
);
const shamanCheckpoints = await sharesToken.numCheckpoints(
users.shaman.address
);
const summonerCP0 = await sharesToken.checkpoints(users.summoner.address, 0);
const summonerCP1 = await sharesToken.checkpoints(users.summoner.address, 1);
const shamanCP0 = await sharesToken.checkpoints(users.shaman.address, 0);
const shamanCP1 = await sharesToken.checkpoints(users.shaman.address, 1);
expect(summonerCheckpoints).to.equal(2);
expect(shamanCheckpoints).to.equal(1);
expect(summonerCP0.votes).to.equal(users.summoner.sharesInitial);
expect(summonerCP1.votes).to.equal(users.summoner.sharesInitial - deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanCP0.votes).to.equal(1);
expect(shamanCP1.fromTimePoint).to.equal(0); // checkpoint DNE
const delegate = await sharesToken.delegates(users.shaman.address);
expect(delegate).to.equal(users.shaman.address);
});
it("require fails - shares paused", async () => {
await users.shaman.baal?.setAdminConfig(true, false); // pause shares
await expect(
users.summoner.shares?.transfer(users.shaman.address, deploymentConfig.SPONSOR_THRESHOLD)
).to.be.revertedWith(revertMessages.sharesTransferPaused);
});
it("require fails - insufficient balance", async () => {
await expect(
users.summoner.shares?.transfer(users.shaman.address, users.summoner.sharesInitial + 1)
).to.be.revertedWith(revertMessages.sharesInsufficientBalance);
});
it("0 transfer - doesnt update delegates", async () => {
await users.summoner.shares?.transfer(users.shaman.address, 0);
const summonerBalance = await sharesToken.balanceOf(users.summoner.address);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
const shamanBalance = await sharesToken.balanceOf(users.shaman.address);
const shamanVotes = await sharesToken.getVotes(users.shaman.address);
expect(summonerBalance).to.equal(users.summoner.sharesInitial);
expect(summonerVotes).to.equal(users.summoner.sharesInitial);
expect(shamanBalance).to.equal(0);
expect(shamanVotes).to.equal(0);
const summonerCheckpoints = await sharesToken.numCheckpoints(
users.summoner.address
);
const shamanCheckpoints = await sharesToken.numCheckpoints(
users.shaman.address
);
const summonerCP0 = await sharesToken.checkpoints(users.summoner.address, 0);
const shamanCP0 = await sharesToken.checkpoints(users.shaman.address, 0);
expect(summonerCheckpoints).to.equal(1);
expect(shamanCheckpoints).to.equal(0);
expect(summonerCP0.votes).to.equal(users.summoner.sharesInitial);
expect(shamanCP0.fromTimePoint).to.equal(0); // checkpoint DNE
});
it("self transfer - doesnt update delegates", async () => {
await users.summoner.shares?.transfer(users.summoner.address, 10);
const summonerBalance = await sharesToken.balanceOf(users.summoner.address);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
expect(summonerBalance).to.equal(users.summoner.sharesInitial);
expect(summonerVotes).to.equal(users.summoner.sharesInitial);
const summonerCheckpoints = await sharesToken.numCheckpoints(
users.summoner.address
);
const summonerCP0 = await sharesToken.checkpoints(users.summoner.address, 0);
expect(summonerCheckpoints).to.equal(1);
expect(summonerCP0.votes).to.equal(users.summoner.sharesInitial);
});
it("transferring to shareholder w/ delegate assigns votes to delegate", async () => {
await users.summoner.shares?.transfer(
users.shaman.address,
deploymentConfig.SPONSOR_THRESHOLD
);
await users.shaman.shares?.delegate(users.applicant.address); // set shaman delegate -> applicant
await users.summoner.shares?.transfer(
users.shaman.address,
deploymentConfig.SPONSOR_THRESHOLD
);
const summonerBalance = await sharesToken.balanceOf(users.summoner.address);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
const shamanBalance = await sharesToken.balanceOf(users.shaman.address);
const shamanVotes = await sharesToken.getVotes(users.shaman.address);
const applicantBalance = await sharesToken.balanceOf(users.applicant.address);
const applicantVotes = await sharesToken.getVotes(users.applicant.address);
expect(summonerBalance).to.equal(users.summoner.sharesInitial - 2 * deploymentConfig.SPONSOR_THRESHOLD);
expect(summonerVotes).to.equal(users.summoner.sharesInitial - 2 * deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanBalance).to.equal(2 * deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanVotes).to.equal(0);
expect(applicantBalance).to.equal(defaultSummonSetup.shares);
expect(applicantVotes).to.equal(defaultSummonSetup.shares + 2 * deploymentConfig.SPONSOR_THRESHOLD);
const delegate = await sharesToken.delegates(users.shaman.address);
expect(delegate).to.equal(users.applicant.address);
const summonerCheckpoints = await sharesToken.numCheckpoints(
users.summoner.address
);
const shamanCheckpoints = await sharesToken.numCheckpoints(
users.shaman.address
);
const applicantCheckpoints = await sharesToken.numCheckpoints(
users.applicant.address
);
const summonerCP0 = await sharesToken.checkpoints(users.summoner.address, 0);
const summonerCP1 = await sharesToken.checkpoints(users.summoner.address, 1);
const summonerCP2 = await sharesToken.checkpoints(users.summoner.address, 2);
const shamanCP0 = await sharesToken.checkpoints(users.shaman.address, 0);
const shamanCP1 = await sharesToken.checkpoints(users.shaman.address, 1);
const applicantCP0 = await sharesToken.checkpoints(users.applicant.address, 0);
const applicantCP1 = await sharesToken.checkpoints(users.applicant.address, 1);
const applicantCP2 = await sharesToken.checkpoints(users.applicant.address, 2);
expect(summonerCheckpoints).to.equal(3);
expect(shamanCheckpoints).to.equal(2);
expect(applicantCheckpoints).to.equal(3);
expect(summonerCP0.votes).to.equal(users.summoner.sharesInitial);
expect(summonerCP1.votes).to.equal(users.summoner.sharesInitial - deploymentConfig.SPONSOR_THRESHOLD);
expect(summonerCP2.votes).to.equal(users.summoner.sharesInitial - 2 * deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanCP0.votes).to.equal(deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanCP1.votes).to.equal(0);
expect(applicantCP0.votes).to.equal(defaultSummonSetup.shares);
expect(applicantCP1.votes).to.equal(defaultSummonSetup.shares + deploymentConfig.SPONSOR_THRESHOLD);
expect(applicantCP2.votes).to.equal(defaultSummonSetup.shares + 2 * deploymentConfig.SPONSOR_THRESHOLD);
});
});
describe("erc20 shares - transferFrom", function () {
it("transfer to first time recipient", async () => {
await users.summoner.shares?.approve(
users.shaman.address,
deploymentConfig.SPONSOR_THRESHOLD
);
const allowanceBefore = await sharesToken.allowance(
users.summoner.address,
users.shaman.address
);
expect(allowanceBefore).to.equal(1);
await users.shaman.shares?.transferFrom(
users.summoner.address,
users.shaman.address,
deploymentConfig.SPONSOR_THRESHOLD
);
const allowanceAfter = await sharesToken.allowance(
users.summoner.address,
users.shaman.address
);
expect(allowanceAfter).to.equal(0);
const summonerBalance = await sharesToken.balanceOf(users.summoner.address);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
const shamanBalance = await sharesToken.balanceOf(users.shaman.address);
const shamanVotes = await sharesToken.getVotes(users.shaman.address);
expect(summonerBalance).to.equal(users.summoner.sharesInitial - deploymentConfig.SPONSOR_THRESHOLD);
expect(summonerVotes).to.equal(users.summoner.sharesInitial - deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanBalance).to.equal(deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanVotes).to.equal(deploymentConfig.SPONSOR_THRESHOLD);
const summonerCheckpoints = await sharesToken.numCheckpoints(
users.summoner.address
);
const shamanCheckpoints = await sharesToken.numCheckpoints(
users.shaman.address
);
const summonerCP0 = await sharesToken.checkpoints(users.summoner.address, 0);
const summonerCP1 = await sharesToken.checkpoints(users.summoner.address, 1);
const shamanCP0 = await sharesToken.checkpoints(users.shaman.address, 0);
const shamanCP1 = await sharesToken.checkpoints(users.shaman.address, 1);
expect(summonerCheckpoints).to.equal(2);
expect(shamanCheckpoints).to.equal(1);
expect(summonerCP0.votes).to.equal(users.summoner.sharesInitial);
expect(summonerCP1.votes).to.equal(users.summoner.sharesInitial - deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanCP0.votes).to.equal(deploymentConfig.SPONSOR_THRESHOLD);
expect(shamanCP1.fromTimePoint).to.equal(0); // checkpoint DNE
});
it("require fails - shares paused", async () => {
await users.shaman.baal?.setAdminConfig(true, false); // pause shares
await users.shaman.shares?.approve(
users.summoner.address,
deploymentConfig.SPONSOR_THRESHOLD
);
await expect(
users.summoner.shares?.transferFrom(
users.shaman.address,
users.summoner.address,
deploymentConfig.SPONSOR_THRESHOLD
)
).to.be.revertedWith(revertMessages.sharesTransferPaused);
});
it("require fails - insufficeint approval", async () => {
await sharesToken.approve(users.shaman.address, 1);
await expect(
sharesToken.transferFrom(users.summoner.address, users.shaman.address, 2)
).to.be.revertedWith(revertMessages.sharesInsufficientApproval);
});
});
// -----------------------------------------------------------
// ------------------ LOOT -----------------------------------
// -----------------------------------------------------------
describe("erc20 loot - approve", function () {
const amountToTransfer = 20;
it("happy case", async () => {
await users.summoner.loot?.approve(users.shaman.address, amountToTransfer);
const allowance = await lootToken.allowance(
users.summoner.address,
users.shaman.address
);
expect(allowance).to.equal(amountToTransfer);
});
it("overwrites previous value", async () => {
await users.summoner.loot?.approve(users.shaman.address, amountToTransfer);
const allowance = await lootToken.allowance(
users.summoner.address,
users.shaman.address
);
expect(allowance).to.equal(amountToTransfer);
await users.summoner.loot?.approve(users.shaman.address, 50);
const allowance2 = await lootToken.allowance(
users.summoner.address,
users.shaman.address
);
expect(allowance2).to.equal(50);
});
});
describe("erc20 loot - transfer", function () {
const amountToTransfer = 500;
it("sends tokens, not votes", async () => {
await users.summoner.loot?.transfer(users.shaman.address, amountToTransfer);
const summonerBalance = await lootToken.balanceOf(users.summoner.address);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
const shamanBalance = await lootToken.balanceOf(users.shaman.address);
const shamanVotes = await sharesToken.getVotes(users.shaman.address);
expect(summonerBalance).to.equal(defaultSummonSetup.loot - amountToTransfer);
expect(summonerVotes).to.equal(users.summoner.sharesInitial);
expect(shamanBalance).to.equal(amountToTransfer);
expect(shamanVotes).to.equal(0);
});
it("require fails - loot paused", async () => {
await users.shaman.baal?.setAdminConfig(false, true); // pause loot
await expect(users.summoner.loot?.transfer(users.shaman.address, 1)).to.be.revertedWith(
revertMessages.lootTransferPaused
);
});
it("require fails - insufficient balance", async () => {
await expect(lootToken.transfer(users.shaman.address, 501)).to.be.revertedWith(
revertMessages.lootInsufficientBalance
);
});
});
describe("erc20 loot - transferFrom", function () {
const amountToTransfer = 500;
it("sends tokens, not votes", async () => {
await users.summoner.loot?.approve(users.shaman.address, amountToTransfer);
await users.shaman.loot?.transferFrom(users.summoner.address, users.shaman.address, amountToTransfer);
const summonerBalance = await lootToken.balanceOf(users.summoner.address);
const summonerVotes = await sharesToken.getVotes(users.summoner.address);
const shamanBalance = await lootToken.balanceOf(users.shaman.address);
const shamanVotes = await sharesToken.getVotes(users.shaman.address);
expect(summonerBalance).to.equal(0);
expect(summonerVotes).to.equal(users.summoner.sharesInitial);
expect(shamanBalance).to.equal(amountToTransfer);
expect(shamanVotes).to.equal(0);
});
it("require fails - loot paused", async () => {
await users.shaman.baal?.setAdminConfig(false, true); // pause loot
await users.summoner.loot?.approve(users.shaman.address, amountToTransfer);
await expect(
users.shaman.loot?.transferFrom(users.summoner.address, users.shaman.address, amountToTransfer)
).to.be.revertedWith(revertMessages.lootTransferPaused);
});
it("require fails - insufficient balance", async () => {
const toTransfer = defaultSummonSetup.loot + 1
await users.summoner.loot?.approve(users.shaman.address, toTransfer);
await expect(
users.shaman.loot?.transferFrom(users.summoner.address, users.shaman.address, toTransfer)
).to.be.revertedWith(revertMessages.lootInsufficientBalance);
});
it("require fails - insufficeint approval", async () => {
await users.summoner.loot?.approve(users.shaman.address, defaultSummonSetup.loot - 1);
await expect(
users.shaman.loot?.transferFrom(users.summoner.address, users.shaman.address, defaultSummonSetup.loot)
).to.be.revertedWith(revertMessages.lootInsufficientApproval);
});
});
// -----------------------------------------------------------
// ------------------ PROPOSALS ------------------------------
// -----------------------------------------------------------
describe("submitProposal", function () {
it("happy case", async () => {
// note - this also tests that members can submit proposals without offering tribute
// note - this also tests that member proposals are self-sponsored (bc votingStarts != 0)
const countBefore = await baal.proposalCount();
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
// TODO test return value - use a helper contract to submit + save the returned ID
const now = await blockTime();
const countAfter = await baal.proposalCount();
expect(countAfter).to.equal(countBefore + 1);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.VOTING);
const proposalData = await baal.proposals(1);
expect(proposalData.id).to.equal(1);
expect(proposalData.votingStarts).to.equal(now);
expect(proposalData.votingEnds).to.equal(
now + deploymentConfig.VOTING_PERIOD_IN_SECONDS
);
expect(proposalData.yesVotes).to.equal(0);
expect(proposalData.noVotes).to.equal(0);
expect(proposalData.expiration).to.equal(proposal.expiration);
expect(hashOperation(proposal.data)).to.equal(
proposalData.proposalDataHash
);
const proposalStatus = await baal.getProposalStatus(1);
expect(proposalStatus).to.eql([false, false, false, false]);
});
it("require fail - expiration passed", async () => {
const now = await blockTime();
await expect(
baal.submitProposal(
proposal.data,
now,
proposal.baalGas,
ethers.utils.id(proposal.details)
)
).to.be.revertedWith(revertMessages.submitProposalExpired);
});
it("edge case - expiration exists, but far enough ahead", async () => {
const countBefore = await baal.proposalCount();
const expiration =
(await blockTime()) +
deploymentConfig.VOTING_PERIOD_IN_SECONDS +
deploymentConfig.GRACE_PERIOD_IN_SECONDS +
10000;
await users.summoner.baal?.submitProposal(
proposal.data,
expiration,
0,
ethers.utils.id(proposal.details)
);
const countAfter = await baal.proposalCount();
expect(countAfter).to.equal(countBefore + 1);
const proposalData = await baal.proposals(1);
expect(proposalData.id).to.equal(1);
});
});
describe("sponsorProposal", function () {
it("happy case", async () => {
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const proposalData = await baal.proposals(1);
expect(proposalData.votingStarts).to.equal(0);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.SUBMITTED);
await users.summoner.baal?.sponsorProposal(1);
const now = await blockTime();
const proposalDataSponsored = await baal.proposals(1);
expect(proposalDataSponsored.votingStarts).to.equal(now);
expect(proposalDataSponsored.votingEnds).to.equal(
now + deploymentConfig.VOTING_PERIOD_IN_SECONDS
);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.VOTING);
});
it("require fail - proposal expired", async () => {
const now = await blockTime();
const expiration =
now +
deploymentConfig.VOTING_PERIOD_IN_SECONDS +
deploymentConfig.GRACE_PERIOD_IN_SECONDS +
1000;
await users.shaman.baal?.submitProposal(
proposal.data,
expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
// TODO: fix
await expect(baal.sponsorProposal(1)).to.be.revertedWith(
revertMessages.sponsorProposalExpired
);
});
it("edge case - expiration exists, but far enough ahead 2", async () => {
const now = await blockTime();
const expiration =
now +
deploymentConfig.VOTING_PERIOD_IN_SECONDS +
deploymentConfig.GRACE_PERIOD_IN_SECONDS +
100000;
await baal.submitProposal(
proposal.data,
expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const proposalDataSponsored = await baal.proposals(1);
const now2 = await blockTime();
expect(proposalDataSponsored.votingStarts).to.equal(now2);
});
it("require fail - not sponsor", async () => {
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await expect(users.shaman.baal?.sponsorProposal(1)).to.be.revertedWith(
revertMessages.proposalNotSponsored
);
});
it("edge case - just enough shares to sponsor", async () => {
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const proposalData = await baal.proposals(1);
expect(proposalData.votingStarts).to.equal(0);
await users.summoner.shares?.transfer(
users.shaman.address,
deploymentConfig.SPONSOR_THRESHOLD
);
await users.shaman.baal?.sponsorProposal(1);
const now = await blockTime();
const proposalDataSponsored = await baal.proposals(1);
expect(proposalDataSponsored.votingStarts).to.equal(now);
});
it("require fail - proposal doesnt exist", async () => {
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.UNBORN);
await expect(baal.sponsorProposal(1)).to.be.revertedWith(
revertMessages.sponsorProposalNotSubmitted
);
});
it("require fail - already sponsored", async () => {
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const proposalData = await baal.proposals(1);
expect(proposalData.votingStarts).to.equal(0);
await users.summoner.baal?.sponsorProposal(1);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.VOTING);
await expect(users.summoner.baal?.sponsorProposal(1)).to.be.revertedWith(
revertMessages.sponsorProposalNotSubmitted
);
});
});
describe("submitVote (w/ auto self-sponsor)", function () {
beforeEach(async function () {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
});
it("happy case - yes vote", async () => {
await users.summoner.baal?.submitVote(1, yes);
const prop = await baal.proposals(1);
const nCheckpoints = await sharesToken.numCheckpoints(users.summoner.address);
const votes = (
await sharesToken.checkpoints(users.summoner.address, nCheckpoints.sub(1))
).votes;
const priorVotes = await sharesToken.getPastVotes(
users.summoner.address,
prop.votingStarts
);
expect(priorVotes).to.equal(votes);
expect(prop.yesVotes).to.equal(votes);
expect(prop.maxTotalSharesAndLootAtVote)
.to.equal(defaultSummonSetup.shares * 3 + defaultSummonSetup.loot * 2);
});
it("happy case - no vote", async () => {
await users.summoner.baal?.submitVote(1, no);
const prop = await baal.proposals(1);
const nCheckpoints = await sharesToken.numCheckpoints(users.summoner.address);
const votes = (
await sharesToken.checkpoints(users.summoner.address, nCheckpoints.sub(1))
).votes;
expect(prop.noVotes).to.equal(votes);
});
it("require fail - voting period has ended", async () => {
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.DEEFEATED);
await expect(users.summoner.baal?.submitVote(1, no)).to.be.revertedWith(
revertMessages.submitVoteNotVoting
);
});
it("require fail - already voted", async () => {
await users.summoner.baal?.submitVote(1, yes);
await expect(users.summoner.baal?.submitVote(1, yes)).to.be.revertedWith(
revertMessages.submitVoteVoted
);
});
it("require fail - not a member", async () => {
await expect(users.shaman.baal?.submitVote(1, yes)).to.be.revertedWith(
revertMessages.submitVoteMember
);
});
it("scenario - two yes votes", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
); // p2
await users.summoner.baal?.submitVote(1, yes);
await users.summoner.baal?.submitVote(2, yes);
const prop1 = await baal.proposals(1);
const votes1 = await sharesToken.getPastVotes(
users.summoner.address,
prop1.votingStarts
);
expect(prop1.yesVotes).to.equal(votes1);
const prop2 = await baal.proposals(2);
const votes2 = await sharesToken.getPastVotes(
users.summoner.address,
prop2.votingStarts
);
expect(prop2.yesVotes).to.equal(votes2);
});
});
describe("submitVote (no self-sponsor)", function () {
const amountToMint = 100;
const currentShares = defaultSummonSetup.shares * 3;
const currentLoot = defaultSummonSetup.loot * 2;
it("require fail - voting not started", async () => {
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.SUBMITTED);
await expect(users.summoner.baal?.submitVote(1, no)).to.be.revertedWith(
revertMessages.submitVoteNotVoting
);
});
it("scenario - increase shares during voting", async () => {
await users.shaman.baal?.mintShares([users.shaman.address], [amountToMint]); // shares for shaman
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
const prop1 = await baal.proposals(1);
expect(prop1.maxTotalSharesAndLootAtVote).to.equal(
currentShares + currentLoot + amountToMint
);
await users.shaman.baal?.mintShares([users.shaman.address], [amountToMint]); // add another 100 shares for shaman
await users.shaman.baal?.submitVote(1, yes);
const prop = await baal.proposals(1);
expect(prop.yesVotes).to.equal(users.summoner.sharesInitial + amountToMint); // summoner shares and 1st shares from shaman are counted
expect(prop.maxTotalSharesAndLootAtVote).to.equal(
currentShares + currentLoot + 2 * amountToMint
);
});
it("scenario - decrease shares during voting", async () => {
await users.shaman.baal?.mintShares([users.shaman.address], [amountToMint]); // shares for shaman
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
const prop1 = await baal.proposals(1);
expect(prop1.maxTotalSharesAndLootAtVote).to.equal(
currentShares + currentLoot + amountToMint
);
await users.shaman.baal?.ragequit(users.shaman.address, amountToMint / 2, 0, [weth.address]);
await users.shaman.baal?.submitVote(1, yes);
const prop = await baal.proposals(1);
expect(prop.yesVotes).to.equal(users.summoner.sharesInitial + amountToMint); // summoner votes and initial votes from shaman are counted (not affected by rq)
expect(prop.maxTotalSharesAndLootAtVote).to.equal(currentShares + currentLoot + amountToMint); // unchanged
});
});
describe("submitVoteWithSig (w/ auto self-sponsor)", function () {
let signer: SignerWithAddress;
beforeEach(async function () {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
signer = await ethers.getSigner(users.summoner.address);
});
it("happy case - yes vote", async () => {
const expiry = await blockTime() + 1200;
const signature = await signVote(
chainId,
baal.address,
signer,
await sharesToken.name(),
expiry,
0,
1,
true
);
const { v, r, s } = ethers.utils.splitSignature(signature);
await baal.submitVoteWithSig(signer.address, expiry, 0, 1, true, v, r, s);
const prop = await baal.proposals(1);
const nCheckpoints = await sharesToken.numCheckpoints(signer.address);
const votes = (
await sharesToken.checkpoints(signer.address, nCheckpoints.sub(1))
).votes;
const priorVotes = await sharesToken.getPastVotes(
signer.address,
prop.votingStarts
);
expect(await baal.votingNonces(signer.address)).to.equal(1);
expect(priorVotes).to.equal(votes);
expect(prop.yesVotes).to.equal(votes);
});
it("fail case - fails with different voter", async () => {
const expiry = await blockTime() + 1200;
const signature = await signVote(
chainId,
baal.address,
signer,
await sharesToken.name(),
expiry,
0,
1,
true
);
const { v, r, s } = ethers.utils.splitSignature(signature);
expect(
baal.submitVoteWithSig(users.applicant.address, expiry, 0, 1, true, v, r, s)
).to.be.revertedWith("invalid signature");
expect(await baal.votingNonces(users.applicant.address)).to.equal(0);
});
it("fail case - cant vote twice", async () => {
const expiry = await blockTime() + 1200;
const signature = await signVote(
chainId,
baal.address,
signer,
await sharesToken.name(),
expiry,
0,
1,
true
);
const { v, r, s } = ethers.utils.splitSignature(signature);
await baal.submitVoteWithSig(signer.address, expiry, 0, 1, true, v, r, s);
const signatureTwo = await signVote(
chainId,
baal.address,
signer,
deploymentConfig.TOKEN_NAME,
expiry,
1,
1,
true
);
const sigTwo = await ethers.utils.splitSignature(signatureTwo);
expect(
baal.submitVoteWithSig(signer.address, expiry, 1, 1, true, sigTwo.v, sigTwo.r, sigTwo.s)
).to.be.revertedWith("voted");
expect(await baal.votingNonces(signer.address)).to.equal(1);
});
});
describe("delegateBySig", function () {
let signer: SignerWithAddress;
beforeEach(async function () {
await baal.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
signer = await ethers.getSigner(users.summoner.address);
});
it("happy case ", async () => {
const expiry = (await blockTime()) + 10000;
const nonce = 0;
const signature = await signDelegation(
chainId,
sharesToken.address,
signer,
await sharesToken.name(),
users.shaman.address,
nonce,
expiry
);
const { v, r, s } = ethers.utils.splitSignature(signature);
await users.shaman.shares?.delegateBySig(users.shaman.address, nonce, expiry, v, r, s);
const summonerDelegate = await sharesToken.delegates(users.summoner.address);
expect(summonerDelegate).to.equal(users.shaman.address);
});
it("require fail - nonce is re-used", async () => {
const expiry = (await blockTime()) + 10000;
const nonce = 0;
const signature = await signDelegation(
chainId,
sharesToken.address,
signer,
await sharesToken.name(),
users.shaman.address,
nonce,
expiry
);
const { v, r, s } = ethers.utils.splitSignature(signature);
await users.shaman.shares?.delegateBySig(users.shaman.address, nonce, expiry, v, r, s);
expect(
users.shaman.shares?.delegateBySig(users.shaman.address, nonce, expiry, v, r, s)
).to.be.revertedWith(sharesRevertMessages.invalidNonce);
});
it("require fail - signature expired", async () => {
const nonce = 0;
const signature = await signDelegation(
chainId,
sharesToken.address,
signer,
await sharesToken.name(),
users.shaman.address,
nonce,
0
);
const { v, r, s } = ethers.utils.splitSignature(signature);
expect(
users.shaman.shares?.delegateBySig(users.shaman.address, nonce, 0, v, r, s)
).to.be.revertedWith(sharesRevertMessages.signatureExpired);
});
});
describe("processProposal", function () {
const quorumGovernanceConfig = abiCoder.encode(
["uint32", "uint32", "uint256", "uint256", "uint256", "uint256"],
[
deploymentConfig.VOTING_PERIOD_IN_SECONDS,
deploymentConfig.GRACE_PERIOD_IN_SECONDS,
deploymentConfig.PROPOSAL_OFFERING,
10, // QUORUM_PERCENT
deploymentConfig.SPONSOR_THRESHOLD,
deploymentConfig.MIN_RETENTION_PERCENT,
]
);
const minRetetionGovernanceConfig = abiCoder.encode(
["uint32", "uint32", "uint256", "uint256", "uint256", "uint256"],
[
deploymentConfig.VOTING_PERIOD_IN_SECONDS,
deploymentConfig.GRACE_PERIOD_IN_SECONDS,
deploymentConfig.PROPOSAL_OFFERING,
deploymentConfig.QUORUM_PERCENT,
deploymentConfig.SPONSOR_THRESHOLD,
90, // MIN_RETENTION_PERCENT = 90%, ragequit > 10% of shares+loot to trigger
]
);
it("happy case yes wins", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
const beforeProcessed = await baal.proposals(1);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, true, false]);
});
it("require fail - not enough gas", async () => {
const baalGas = 10000000;
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 3);
// const procprop = baal.processProposal(1, proposal.data);
const procprop = baal.processProposal(1, proposal.data, {gasPrice: ethers.utils.parseUnits('1', 'gwei'), gasLimit: 10000000})
expect(procprop).to.be.revertedWith(revertMessages.notEnoughGas);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.READY);
});
it("require fail - baalGas to high", async () => {
const baalGas = 20000001;
await expect(users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
baalGas,
ethers.utils.id(proposal.details)
)).to.be.revertedWith(revertMessages.baalGasToHigh);
});
it("has enough baalGas", async () => {
const baalGas = 1000000;
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 5);
await baal.processProposal(1, proposal.data, {
gasPrice: ethers.utils.parseUnits("100", "gwei"),
gasLimit: 10000000,
});
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.PROCESSED);
});
it("require fail - no wins, proposal is defeated", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, no);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 5);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.DEEFEATED);
await expect(baal.processProposal(1, proposal.data)).to.be.revertedWith(
revertMessages.processProposalNotReady
);
});
it("require fail - proposal does not exist", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
const state = await baal.state(2);
expect(state).to.equal(PROPOSAL_STATES.UNBORN);
});
it("require fail - no sponser", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
const state = await baal.state(2);
expect(state).to.equal(PROPOSAL_STATES.UNBORN);
// proposal.sponsor = null;
await expect(baal.processProposal(2, proposal.data)).to.be.revertedWith(
revertMessages.proposalNotSponsored
);
});
it("require fail - prev proposal not processed", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(2, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await expect(baal.processProposal(2, proposal.data)).to.be.revertedWith(
"prev!processed" // TODO:
);
});
it("require fail - proposal data mismatch on processing", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const badSelfTransferAction = encodeMultiAction(
multisend,
["0xbeefbabe"],
[baal.address],
[BigNumber.from(0)],
[0]
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await expect(
baal.processProposal(1, badSelfTransferAction)
).to.be.revertedWith("incorrect calldata"); // TODO:
});
it("require fail - proposal not in voting", async () => {
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await expect(users.summoner.baal?.processProposal(1, proposal.data)).to.be.revertedWith(
revertMessages.proposalNotSponsored
); // fail at submitted
await users.summoner.baal?.sponsorProposal(1);
await expect(baal.processProposal(1, proposal.data)).to.be.revertedWith(
revertMessages.processProposalNotReady
); // fail at voting
await users.summoner.baal?.submitVote(1, yes);
const beforeProcessed = await baal.proposals(1);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 1);
const state1 = await baal.state(1);
expect(state1).to.equal(PROPOSAL_STATES.GRACE);
await expect(baal.processProposal(1, proposal.data)).to.be.revertedWith(
revertMessages.processProposalNotReady
); // fail at grace
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 1);
await baal.processProposal(1, proposal.data); // propsal ready, works
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, true, false]);
});
it("require fail - proposal cancelled", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await users.shaman.baal?.cancelProposal(1);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state = await baal.state(1);
expect(state).to.equal(PROPOSAL_STATES.CANCELLED);
await expect(baal.processProposal(1, proposal.data)).to.be.revertedWith(
revertMessages.processProposalNotReady
);
});
it("require fail - proposal expired 2", async () => {
const now = await blockTime();
const expiration =
now +
deploymentConfig.VOTING_PERIOD_IN_SECONDS +
deploymentConfig.GRACE_PERIOD_IN_SECONDS +
60;
await users.summoner.baal?.submitProposal(
proposal.data,
expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
expect(state1).to.equal(PROPOSAL_STATES.READY);
const beforeProcessed = await baal.proposals(1);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, false, false]); // passed [3] is false
});
it("edge case - exactly at quorum", async () => {
// mint shares to make total shares supply 2000 so summoner has exectly 10% w/ 200 shares
const amountToMint = BigNumber.from(2000).sub(await baal.totalShares());
await users.shaman.baal?.mintShares([users.shaman.address], [amountToMint]);
// const totalSupply = await baal.totalSupply();
const totalSupply = await baal.totalShares();
expect(totalSupply).to.equal(2000);
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
expect(state1).to.equal(PROPOSAL_STATES.READY);
await users.shaman.baal?.setGovernanceConfig(quorumGovernanceConfig); // set quorum to 10%
const beforeProcessed = await baal.proposals(1);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, true, false]); // passed [3] is true
});
it("quorum should not factor loot", async () => {
// mint shares so summoner has > 10% w/ 200 shares
const amountToMint = BigNumber.from(1000).sub(await baal.totalShares());
await users.shaman.baal?.mintShares([users.shaman.address], [amountToMint]);
await users.shaman.baal?.mintLoot([users.shaman.address], [100000000000]); // mint 100000000000 loot so summoner has a ton of it
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
expect(state1).to.equal(PROPOSAL_STATES.READY);
await users.shaman.baal?.setGovernanceConfig(quorumGovernanceConfig); // set quorum to 10%
const beforeProcessed = await baal.proposals(1);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, true, false]); // passed [3] is true
});
it("edge case - just under quorum", async () => {
// mint shares so summoner has <10% w/ 200 shares
const amountToMint = BigNumber.from(2001).sub(await baal.totalShares());
await users.shaman.baal?.mintShares([users.shaman.address], [amountToMint]);
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
expect(state1).to.equal(PROPOSAL_STATES.READY);
await users.shaman.baal?.setGovernanceConfig(quorumGovernanceConfig); // set quorum to 10%
const beforeProcessed = await baal.proposals(1);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, false, false]); // passed [3] is false
});
it("edge case - exactly at minRetentionPercent", async () => {
await users.shaman.baal?.setGovernanceConfig(minRetetionGovernanceConfig); // set min retention to 90%
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
// ragequit 30 shares out of 200 shares and 100 loot out of 500 -> 10% totalSupply
await users.summoner.baal?.ragequit(users.summoner.address, 30, 100, [weth.address]);
expect(state1).to.equal(PROPOSAL_STATES.READY);
const beforeProcessed = await baal.proposals(1);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, true, false]); // passed [3] is true
});
it("edge case - just below minRetentionPercent - shares+loot", async () => {
await users.shaman.baal?.setGovernanceConfig(minRetetionGovernanceConfig); // set min retention to 90%
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
// ragequit 31 shares out of 200, and 101 loot out of 500 -> > 10% totalSupply
await users.summoner.baal?.ragequit(users.summoner.address, 31, 101, [weth.address]);
expect(state1).to.equal(PROPOSAL_STATES.READY);
const beforeProcessed = await baal.proposals(1);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, false, false]); // passed [3] is false - min retention exceeded
});
it("edge case - just below minRetentionPercent - just shares", async () => {
await users.shaman.baal?.setGovernanceConfig(minRetetionGovernanceConfig); // set min retention to 90%
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
// ragequit 131 shares out of 200, and 0 out of 500 -> > 10% totalSupply
await users.summoner.baal?.ragequit(users.summoner.address, 131, 0, [weth.address]);
expect(state1).to.equal(PROPOSAL_STATES.READY);
const beforeProcessed = await baal.proposals(1);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, false, false]); // passed [3] is false - min retention exceeded
});
it("edge case - just below minRetentionPercent - just loot", async () => {
await users.shaman.baal?.setGovernanceConfig(minRetetionGovernanceConfig); // set min retention to 90%
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
// ragequit 0 shares out of 200, and 131 loot out of 500 -> > 10% totalSupply
await users.summoner.baal?.ragequit(users.summoner.address, 0, 131, [weth.address]);
expect(state1).to.equal(PROPOSAL_STATES.READY);
const beforeProcessed = await baal.proposals(1);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(1);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(1);
expect(propStatus).to.eql([false, true, false, false]); // passed [3] is false - min retention exceeded
});
it("scenario - offer tribute unsafe", async () => {
await users.summoner.weth?.transfer(users.s1.address, 100); // summoner transfer 100 weth
const offerWeth = weth.interface.encodeFunctionData("transferFrom", [
users.s1.address,
gnosisSafe.address,
100,
]);
const tributeMultiAction = encodeMultiAction(
multisend,
[offerWeth],
[weth.address],
[BigNumber.from(0)],
[0]
);
proposal.data = tributeMultiAction;
proposal.details = 'Tribute Proposal';
await users.s1.weth?.approve(gnosisSafe.address, 100);
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
const beforeProcessed = await baal.proposals(1);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
const afterProcessed = await baal.proposals(1);
verifyProposal(afterProcessed, beforeProcessed, {
processed: true,
passed: true,
});
const applicantWethBalance = await weth.balanceOf(users.s1.address);
expect(applicantWethBalance).to.equal(0);
const safeWethBalance = await weth.balanceOf(gnosisSafe.address);
expect(safeWethBalance).to.equal(100);
});
it("scenario - two propsals, prev is processed", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(2, yes);
const beforeProcessed = await baal.proposals(2);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, proposal.data);
const state1 = await baal.state(1);
expect(state1).to.equal(PROPOSAL_STATES.PROCESSED); // prev prop processed
await baal.processProposal(2, proposal.data);
const afterProcessed = await baal.proposals(2);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(2);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(2);
expect(propStatus).to.eql([false, true, true, false]);
});
it("scenario - two propsals, prev is defeated", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, no);
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(2, yes);
const beforeProcessed = await baal.proposals(2);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
expect(state1).to.equal(PROPOSAL_STATES.DEEFEATED); // prev prop defeated
await baal.processProposal(2, proposal.data);
const afterProcessed = await baal.proposals(2);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(2);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(2);
expect(propStatus).to.eql([false, true, true, false]);
});
it("scenario - two propsals, prev is cancelled", async () => {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(1, yes);
await users.shaman.baal?.cancelProposal(1);
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
await users.summoner.baal?.submitVote(2, yes);
const beforeProcessed = await baal.proposals(2);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
const state1 = await baal.state(1);
expect(state1).to.equal(PROPOSAL_STATES.CANCELLED); // prev prop cancelled
await baal.processProposal(2, proposal.data);
const afterProcessed = await baal.proposals(2);
verifyProposal(afterProcessed, beforeProcessed);
const state2 = await baal.state(2);
expect(state2).to.equal(PROPOSAL_STATES.PROCESSED);
const propStatus = await baal.getProposalStatus(2);
expect(propStatus).to.eql([false, true, true, false]);
});
it("happy case - mint shares via proposal", async () => {
const minting = 100;
expect(
await sharesToken.balanceOf(users.applicant.address)
).to.equal(defaultSummonSetup.shares);
const mintAction = baal.interface.encodeFunctionData(
"mintShares",
[[users.applicant.address], [minting]]
);
const encodedAction = encodeMultiAction(
multisend,
[mintAction],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1,
})
).to.emit(baal, "ProcessProposal").withArgs(1, true, false);
expect(
await sharesToken.balanceOf(users.applicant.address)
).to.equal(defaultSummonSetup.shares + minting);
});
it("happy case - burn shares via proposal", async () => {
const burning = 100;
expect(
await sharesToken.balanceOf(users.summoner.address)
).to.equal(users.summoner.sharesInitial);
const burnAction = baal.interface.encodeFunctionData(
"burnShares",
[[users.summoner.address], [burning]]
);
const encodedAction = encodeMultiAction(
multisend,
[burnAction],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1
})
).to.emit(baal, "ProcessProposal").withArgs(1, true, false);
expect(
await sharesToken.balanceOf(users.summoner.address)
).to.equal(users.summoner.sharesInitial - burning);
});
it("happy case - mint loot via proposal", async () => {
const minting = 100;
expect(
await lootToken.balanceOf(users.applicant.address)
).to.equal(defaultSummonSetup.loot);
const mintAction = await baal.interface.encodeFunctionData(
"mintLoot",
[[users.applicant.address], [minting]]
);
const encodedAction = encodeMultiAction(
multisend,
[mintAction],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1
})
).to.emit(baal, "ProcessProposal").withArgs(1, true, false);
expect(
await lootToken.balanceOf(users.applicant.address)
).to.equal(defaultSummonSetup.loot + minting);
});
it("happy case - burn loot via proposal", async () => {
const burning = 100;
expect(
await lootToken.balanceOf(users.summoner.address)
).to.equal(defaultSummonSetup.loot);
const burnAction = baal.interface.encodeFunctionData(
"burnLoot",
[[users.summoner.address], [burning]]
);
const encodedAction = encodeMultiAction(
multisend,
[burnAction],
[baal.address],
[BigNumber.from(0)],
[0]
);
await expect(proposalHelpers.submitAndProcessProposal({
baal,
encodedAction,
proposal,
proposalId: 1
})
).to.emit(baal, "ProcessProposal").withArgs(1, true, false);
expect(
await lootToken.balanceOf(users.summoner.address)
).to.equal(defaultSummonSetup.loot - burning);
});
// setting and unsetting shamans covered
// TODO set / unset tokens via proposal
});
// ----------------------------------------------------------
// ------------------ RAGEQUIT ------------------------------
// ----------------------------------------------------------
describe("ragequit", function () {
const depositAmount = 100;
it("happy case - full ragequit", async () => {
const summonerWethBefore = await weth.balanceOf(users.summoner.address);
await weth.transfer(gnosisSafe.address, depositAmount);
const share = BigNumber.from(
users.summoner.sharesInitial + defaultSummonSetup.loot
).mul(depositAmount).div(await baal.totalSupply());
await users.summoner.baal?.ragequit(
users.summoner.address,
users.summoner.sharesInitial,
defaultSummonSetup.loot,
[weth.address]
);
const sharesAfter = await sharesToken.balanceOf(users.summoner.address);
const lootAfter = await lootToken.balanceOf(users.summoner.address);
const summonerWethAfter = await weth.balanceOf(users.summoner.address);
const safeWethAfter = await weth.balanceOf(gnosisSafe.address);
expect(lootAfter).to.equal(0);
expect(sharesAfter).to.equal(0);
expect(summonerWethAfter).to.equal(summonerWethBefore.add(share));
expect(safeWethAfter).to.equal(BigNumber.from(depositAmount).sub(share));
});
it("happy case - partial ragequit", async () => {
const lootBefore = await lootToken.balanceOf(users.summoner.address);
const sharesBefore = await sharesToken.balanceOf(users.summoner.address);
const lootToBurn = defaultSummonSetup.loot / 2;
// const sharesToBurn = (await baal.totalShares()).div(2); // half of shares supplied
const sharesToBurn = defaultSummonSetup.shares / 2;
const summonerWethBefore = await weth.balanceOf(users.summoner.address);
await weth.transfer(gnosisSafe.address, depositAmount);
const share = BigNumber.from(
sharesToBurn + lootToBurn
).mul(depositAmount).div(await baal.totalSupply());
await users.summoner.baal?.ragequit(
users.summoner.address,
sharesToBurn,
lootToBurn,
[weth.address]
);
const sharesAfter = await sharesToken.balanceOf(users.summoner.address);
const lootAfter = await lootToken.balanceOf(users.summoner.address);
const summonerWethAfter = await weth.balanceOf(users.summoner.address);
const safeWethAfter = await weth.balanceOf(gnosisSafe.address);
expect(lootAfter).to.equal(lootBefore.sub(lootToBurn));
expect(sharesAfter).to.equal(sharesBefore.sub(sharesToBurn));
expect(summonerWethAfter).to.equal(summonerWethBefore.add(share));
expect(safeWethAfter).to.equal(BigNumber.from(depositAmount).sub(share));
});
it("happy case - full ragequit to different address", async () => {
const applicantWethBefore = await weth.balanceOf(users.applicant.address);
const summonerWethBefore = await weth.balanceOf(users.summoner.address);
await weth.transfer(gnosisSafe.address, depositAmount);
const share = BigNumber.from(
users.summoner.sharesInitial + defaultSummonSetup.loot
).mul(depositAmount).div(await baal.totalSupply());
await users.summoner.baal?.ragequit(
users.applicant.address, // ragequit to applicant
users.summoner.sharesInitial,
defaultSummonSetup.loot,
[weth.address]
);
const sharesAfter = await sharesToken.balanceOf(users.summoner.address);
const lootAfter = await lootToken.balanceOf(users.summoner.address);
const summonerWethAfter = await weth.balanceOf(users.summoner.address);
const safeWethAfter = await weth.balanceOf(gnosisSafe.address);
const applicantWethAfter = await weth.balanceOf(users.applicant.address);
expect(lootAfter).to.equal(0);
expect(sharesAfter).to.equal(0);
expect(summonerWethAfter).to.equal(summonerWethBefore);
expect(safeWethAfter).to.equal(BigNumber.from(depositAmount).sub(share));
expect(applicantWethAfter).to.equal(applicantWethBefore.add(share));
});
it("happy case - full ragequit - two tokens", async () => {
// expect: receive 50% of weth and dai from DAO
const summonerWethBefore = await weth.balanceOf(users.summoner.address);
const summonerDaiBefore = await dai.balanceOf(users.summoner.address);
await weth.transfer(gnosisSafe.address, depositAmount);
await dai.transfer(gnosisSafe.address, depositAmount * 2);
const lootToBurn = defaultSummonSetup.loot;
const lootBefore = await lootToken.balanceOf(users.summoner.address);
const shareWeth = BigNumber.from(
users.summoner.sharesInitial + lootToBurn
).mul(depositAmount).div(await baal.totalSupply());
const shareDai = BigNumber.from(
users.summoner.sharesInitial + lootToBurn
).mul(depositAmount * 2).div(await baal.totalSupply());
const orderedTokens = [dai.address, weth.address].sort((a, b) => {
return parseInt(a.slice(2), 16) - parseInt(b.slice(2), 16);
});
await users.summoner.baal?.ragequit(
users.summoner.address,
users.summoner.sharesInitial,
lootToBurn,
orderedTokens
);
const sharesAfter = await sharesToken.balanceOf(users.summoner.address);
const lootAfter = await lootToken.balanceOf(users.summoner.address);
const summonerWethAfter = await weth.balanceOf(users.summoner.address);
const summonerDaiAfter = await dai.balanceOf(users.summoner.address);
const safeWethAfter = await weth.balanceOf(gnosisSafe.address);
const safeDaiAfter = await dai.balanceOf(gnosisSafe.address);
expect(sharesAfter).to.equal(0);
expect(lootAfter).to.equal(lootBefore.sub(lootToBurn));
expect(summonerWethAfter).to.equal(summonerWethBefore.add(shareWeth)); // minus 100, plus 50
expect(summonerDaiAfter).to.equal(summonerDaiBefore.add(shareDai)); // minus 200, plus 100
expect(safeWethAfter).to.equal(BigNumber.from(depositAmount).sub(shareWeth));
expect(safeDaiAfter).to.equal(BigNumber.from(depositAmount * 2).sub(shareDai));
});
});
describe("ragequit", function () {
const depositAmount = 100;
// TODO:
// it("collects tokens not on the list", async () => {
// // note - skips having shaman add LOOT to guildTokens
// // transfer 300 loot to DAO (summoner has 200 shares + 500 loot, so that's 50% of total)
// // transfer 100 weth to DAO
// // ragequit 100% of remaining shares & loot
// // expect: receive 50% of weth / loot from DAO
// await users.shaman.baal?.mintShares([users.applicant.address], [100]); // 100 extra to even loot supply
// const summonerWethBefore = await weth.balanceOf(users.summoner.address);
// await weth.transfer(gnosisSafe.address, depositAmount);
// await users.summoner.loot?.transfer(gnosisSafe.address, 300);
// const tokens = [lootToken.address, weth.address].sort((a, b) => {
// return parseInt(a.slice(2), 16) - parseInt(b.slice(2), 16);
// });
// console.log('Supply', (await baal.totalSupply()).toString());
// await users.summoner.baal?.ragequit(
// users.summoner.address,
// users.summoner.sharesInitial,
// defaultSummonSetup.loot - 300,
// tokens
// );
// const sharesAfter = await sharesToken.balanceOf(users.summoner.address);
// const lootAfter = await lootToken.balanceOf(users.summoner.address);
// const safeLootAfter = await lootToken.balanceOf(gnosisSafe.address);
// const summonerWethAfter = await weth.balanceOf(users.summoner.address);
// const safeWethAfter = await weth.balanceOf(gnosisSafe.address);
// // TODO: Should be 85?
// // expect(lootAfter).to.equal(150); // burn 200, receive 150
// expect(sharesAfter).to.equal(0);
// expect(summonerWethAfter).to.equal(summonerWethBefore.add(50)); // minus 100, plus 50
// expect(safeWethAfter).to.equal(50);
// expect(safeLootAfter).to.equal(150);
// });
it("require fail - enforces ascending order", async () => {
await weth.transfer(gnosisSafe.address, depositAmount);
await users.summoner.loot?.transfer(baal.address, 300);
const tokens = [lootToken.address, weth.address]
.sort((a, b) => {
return parseInt(a.slice(2), 16) - parseInt(b.slice(2), 16);
})
.reverse();
await expect(
users.summoner.baal?.ragequit(
users.summoner.address,
users.summoner.sharesInitial,
defaultSummonSetup.loot - 300,
tokens
)
).to.be.revertedWith(revertMessages.ragequitUnordered);
});
it("require fail - prevents actual duplicate", async () => {
await weth.transfer(gnosisSafe.address, depositAmount);
await expect(
users.summoner.baal?.ragequit(
users.summoner.address,
defaultSummonSetup.shares,
defaultSummonSetup.loot - 300,
[
weth.address,
weth.address,
]
)
).to.be.revertedWith(revertMessages.ragequitUnordered);
});
});
// --------------------------------------------------------
// ------------------ VOTING ------------------------------
// --------------------------------------------------------
describe("getVotes", function () {
it("happy case - account with votes", async () => {
const currentVotes = await sharesToken.getVotes(users.summoner.address);
const nCheckpoints = await sharesToken.numCheckpoints(users.summoner.address);
const checkpoints = await sharesToken.checkpoints(
users.summoner.address,
nCheckpoints.sub(1)
);
const votes = checkpoints.votes;
expect(currentVotes).to.equal(votes);
});
it("happy case - account without votes", async () => {
const currentVotes = await sharesToken.getVotes(users.shaman.address);
expect(currentVotes).to.equal(0);
});
});
describe("getPastVotes", function () {
beforeEach(async function () {
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
});
it("happy case - yes vote", async () => {
const blockT = await blockTime();
await users.summoner.baal?.submitVote(1, yes);
const priorVote = await sharesToken.getPastVotes(users.summoner.address, blockT);
const nCheckpoints = await sharesToken.numCheckpoints(users.summoner.address);
const votes = (
await sharesToken.checkpoints(users.summoner.address, nCheckpoints.sub(1))
).votes;
expect(priorVote).to.equal(votes);
});
it("happy case - no vote", async () => {
const blockT = await blockTime();
await users.summoner.baal?.submitVote(1, no);
const priorVote = await sharesToken.getPastVotes(users.summoner.address, blockT);
const nCheckpoints = await sharesToken.numCheckpoints(users.summoner.address);
const votes = (
await sharesToken.checkpoints(users.summoner.address, nCheckpoints.sub(1))
).votes;
expect(priorVote).to.equal(votes);
});
it("require fail - timestamp not determined", async () => {
const blockT = await blockTime();
await expect(
sharesToken.getPastVotes(users.summoner.address, blockT)
).to.be.revertedWith("!determined");
});
});
});
describe("Baal contract - offering required", function () {
let customConfig = {
...deploymentConfig,
PROPOSAL_OFFERING: 69,
SPONSOR_THRESHOLD: 1,
};
let baal: Baal;
let multisend: MultiSend;
let gnosisSafe: GnosisSafe;
let proposal: ProposalType;
let users: {
[key: string]: Signer;
};
beforeEach(async function () {
const {
Baal,
GnosisSafe,
MultiSend,
signers
} = await baalSetup({
daoSettings: customConfig,
});
baal = Baal;
gnosisSafe = GnosisSafe;
multisend = MultiSend;
users = signers;
const selfTransferAction = encodeMultiAction(
multisend,
["0x"],
[gnosisSafe.address],
[BigNumber.from(0)],
[0]
);
proposal = {
flag: 0,
data: selfTransferAction,
details: "all hail baal",
expiration: 0,
baalGas: 0,
};
});
describe("submitProposal", function () {
it("submit proposal", async () => {
// note - this also tests that the proposal is NOT sponsored
const countBefore = await baal.proposalCount();
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details),
{ value: 69 }
);
const countAfter = await baal.proposalCount();
expect(countAfter).to.equal(countBefore + 1);
const proposalData = await baal.proposals(1);
expect(proposalData.id).to.equal(1);
});
it("happy case - sponsors can submit without offering, auto-sponsors", async () => {
const countBefore = await baal.proposalCount();
await users.summoner.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const now = await blockTime();
const countAfter = await baal.proposalCount();
expect(countAfter).to.equal(countBefore + 1);
const proposalData = await baal.proposals(1);
expect(proposalData.id).to.equal(1);
expect(proposalData.votingStarts).to.equal(now);
});
it("edge case - sponsors can submit without offering at threshold", async () => {
const countBefore = await baal.proposalCount();
await users.summoner.shares?.transfer(users.shaman.address, 1); // transfer 1 share to shaman, putting them at threshold (1)
await users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
);
const now = await blockTime();
const countAfter = await baal.proposalCount();
expect(countAfter).to.equal(countBefore + 1);
const proposalData = await baal.proposals(1);
expect(proposalData.id).to.equal(1);
expect(proposalData.votingStarts).to.equal(now);
});
it("require fail - no offering offered", async () => {
await expect(
users.shaman.baal?.submitProposal(
proposal.data,
proposal.expiration,
proposal.baalGas,
ethers.utils.id(proposal.details)
)
).to.be.revertedWith(revertMessages.submitProposalOffering);
});
});
});
describe("BaalSummoner contract", function () {
let avatar: TestAvatar;
let baalSingleton: Baal;
let baalSummoner: BaalSummoner;
let expectedAddress: string;
let moduleProxyFactory: ModuleProxyFactory;
let gnosisSafeProxyFactory: GnosisSafeProxyFactory;
let poster: Poster;
beforeEach(async function () {
const { deployer } = await getNamedAccounts();
await deployments.fixture(['Infra', 'BaalSummoner']); // Deployment Tags
baalSingleton = (await ethers.getContract('Baal', deployer)) as Baal;
baalSummoner = (await ethers.getContract('BaalSummoner', deployer)) as BaalSummoner;
moduleProxyFactory = (await ethers.getContract('ModuleProxyFactory', deployer)) as ModuleProxyFactory;
gnosisSafeProxyFactory = (await ethers.getContract('GnosisSafeProxyFactory', deployer)) as GnosisSafeProxyFactory;
poster = (await ethers.getContract('Poster', deployer)) as Poster
const deployedAvatar = await deployments.deploy('TestAvatar', {
from: deployer,
args: []
});
avatar = (await ethers.getContractAt('TestAvatar', deployedAvatar.address, deployer)) as TestAvatar;
});
describe("Baal summoned after safe", function () {
it("should have the expected address of the module the same as the deployed", async () => {
const [summoner, applicant, shaman] = await getUnnamedAccounts();
const initData = baalSingleton.interface.encodeFunctionData("avatar");
const saltNonce = getSaltNonce();
expectedAddress = calculateProxyAddress(
moduleProxyFactory,
baalSingleton.address,
initData,
saltNonce
);
await avatar.enableModule(expectedAddress);
const loot = defaultSummonSetup.loot;
const lootPaused = defaultSummonSetup.lootPaused;
const shares = defaultSummonSetup.shares;
const sharesPaused = defaultSummonSetup.sharesPaused;
const shamanPermissions = defaultSummonSetup.shamanPermissions;
const addresses = await setupBaal({
baalSummoner,
baalSingleton,
poster,
config: defaultDAOSettings,
adminConfig: [sharesPaused, lootPaused],
shamans: [[shaman], [shamanPermissions]],
shares: [
[summoner, applicant],
[shares, shares]
],
loots: [
[summoner, applicant],
[loot, loot]
],
safeAddress: avatar.address as `0x${string}`,
forwarderAddress: ethers.constants.AddressZero,
lootAddress: ethers.constants.AddressZero,
sharesAddress: ethers.constants.AddressZero as `0x${string}`,
saltNonceOverride: saltNonce
});
expect(expectedAddress).to.equal(addresses.baal);
});
});
describe("Baal summoner with fresh treasury vault", function () {
it("should have the expected address for both the module and safe", async () => {
const [summoner, applicant, shaman] = await getUnnamedAccounts();
const initData = baalSingleton.interface.encodeFunctionData("avatar");
const saltNonce = getSaltNonce();
expectedAddress = calculateProxyAddress(
moduleProxyFactory,
baalSingleton.address,
initData,
saltNonce
);
const masterCopyAddress = await baalSummoner.gnosisSingleton();
const expectedSafeAddress = await calculateSafeProxyAddress({
gnosisSafeProxyFactory,
masterCopyAddress,
saltNonce,
});
const loot = defaultSummonSetup.loot;
const lootPaused = defaultSummonSetup.lootPaused;
const shares = defaultSummonSetup.shares;
const sharesPaused = defaultSummonSetup.sharesPaused;
const shamanPermissions = defaultSummonSetup.shamanPermissions;
const addresses = await setupBaal({
baalSummoner,
baalSingleton,
poster,
config: defaultDAOSettings,
adminConfig: [sharesPaused, lootPaused],
shamans: [[shaman], [shamanPermissions]],
shares: [
[summoner, applicant],
[shares, shares]
],
loots: [
[summoner, applicant],
[loot, loot]
],
forwarderAddress: ethers.constants.AddressZero,
lootAddress: ethers.constants.AddressZero,
sharesAddress: ethers.constants.AddressZero as `0x${string}`,
saltNonceOverride: saltNonce
});
expect(expectedAddress).to.equal(addresses.baal);
expect(expectedSafeAddress).to.equal(addresses.safe);
});
});
});