3672 lines
139 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|