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

316 lines
10 KiB
TypeScript

import { expect } from 'chai';
import { BigNumber } from 'ethers';
import { ethers } from 'hardhat';
import {
DAOSettings,
defaultDAOSettings,
defaultProposalSettings,
revertMessages,
} from './utils/baal';
import { moveForwardPeriods } from './utils/evm';
import { baalSetup, ProposalHelpers, Signer } from './utils/fixtures';
import {
Baal,
TestERC20,
TributeMinion,
Loot,
MultiSend,
BaalSummoner,
GnosisSafe,
Shares,
} from '../src/types';
import { encodeMultiAction } from '../src/util';
describe("Tribute proposal type", function () {
let baal: Baal;
let lootToken: Loot;
let sharesToken: Shares;
let weth: TestERC20;
let multisend: MultiSend;
let baalSummoner: BaalSummoner;
let gnosisSafe: GnosisSafe;
let tributeMinion: TributeMinion;
let users: {
[key: string]: Signer;
};
const yes = true;
const no = false;
let proposalHelpers: ProposalHelpers;
beforeEach(async () => {
const {
Baal,
Loot,
Shares,
BaalSummoner,
GnosisSafe,
MultiSend,
TributeMinion,
WETH,
signers,
helpers,
} = await baalSetup({});
baal = Baal;
lootToken = Loot;
sharesToken = Shares;
baalSummoner = BaalSummoner;
gnosisSafe = GnosisSafe;
multisend = MultiSend;
weth = WETH;
// poster = Poster;
tributeMinion = TributeMinion;
users = signers;
proposalHelpers = helpers;
});
describe("Dangerous proposal tribute", () => {
const tribute = 100;
const requestedShares = 100;
it("Allows applicant to tribute tokens in exchange for shares", async () => {
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(0);
await users.applicant.weth?.approve(gnosisSafe.address, tribute);
const currentShares = await sharesToken.balanceOf(users.applicant.address);
const mintShares = baal.interface.encodeFunctionData("mintShares", [
[users.applicant.address],
[requestedShares],
]);
const sendTribute = weth.interface.encodeFunctionData(
"transferFrom",
[users.applicant.address, gnosisSafe.address, tribute]
);
const encodedProposal = encodeMultiAction(
multisend,
[mintShares, sendTribute],
[baal.address, weth.address],
[BigNumber.from(0), BigNumber.from(0)],
[0, 0]
);
await users.applicant.baal?.submitProposal(
encodedProposal,
defaultProposalSettings.EXPIRATION,
defaultProposalSettings.BAAL_GAS,
ethers.utils.id(defaultProposalSettings.DETAILS),
{ value: defaultDAOSettings.PROPOSAL_OFFERING }
);
await baal.submitVote(1, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
await baal.processProposal(1, encodedProposal);
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(tribute);
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(
currentShares.add(requestedShares) // current shares plus new shares
);
});
it("EXPLOIT - Allows another proposal to spend tokens intended for tribute", async () => {
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(0);
await users.applicant.weth?.approve(gnosisSafe.address, tribute);
const currentShares = await sharesToken.balanceOf(users.applicant.address);
const mintShares = baal.interface.encodeFunctionData("mintShares", [
[users.applicant.address],
[requestedShares],
]);
const sendTribute = weth.interface.encodeFunctionData(
"transferFrom",
[users.applicant.address, gnosisSafe.address, tribute]
);
const encodedProposal = encodeMultiAction(
multisend,
[mintShares, sendTribute],
[baal.address, weth.address],
[BigNumber.from(0), BigNumber.from(0)],
[0, 0]
);
const maliciousProposal = encodeMultiAction(
multisend,
[sendTribute],
[weth.address],
[BigNumber.from(0)],
[0]
);
await users.applicant.baal?.submitProposal(
encodedProposal,
defaultProposalSettings.EXPIRATION,
defaultProposalSettings.BAAL_GAS,
ethers.utils.id(defaultProposalSettings.DETAILS),
{ value: defaultDAOSettings.PROPOSAL_OFFERING }
);
await users.applicant.baal?.submitProposal(
maliciousProposal,
defaultProposalSettings.EXPIRATION,
defaultProposalSettings.BAAL_GAS,
ethers.utils.id(defaultProposalSettings.DETAILS),
{ value: defaultDAOSettings.PROPOSAL_OFFERING }
);
await baal.submitVote(1, no);
await baal.submitVote(2, yes);
await moveForwardPeriods(defaultDAOSettings.VOTING_PERIOD_IN_SECONDS, 2);
// await baal.processProposal(1, encodedProposal)
await baal.processProposal(2, maliciousProposal);
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(tribute);
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(currentShares); // only current shares no new ones
});
});
describe("Baal with NO proposal offering - Safe Tribute Proposal", () => {
const tribute = 100;
const requestedShares = 1234;
const requestedLoot = 1007;
it("allows external tribute minion to submit share proposal in exchange for tokens", async () => {
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(0);
expect(await weth.balanceOf(users.applicant.address)).to.equal(1000);
await users.applicant.weth?.approve(tributeMinion.address, 10000);
const currentShares = await sharesToken.balanceOf(users.applicant.address);
users.applicant.tributeMinion && await proposalHelpers.submitAndProcessTributeProposal({
tributeMinion: users.applicant.tributeMinion,
baal,
applicantAddress: users.applicant.address,
tributeToken: weth.address,
tribute,
requestedShares,
requestedLoot,
});
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(
requestedShares + parseInt(currentShares.toString())
);
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(tribute);
});
});
describe("Baal with proposal offering - Safe Tribute Proposal", function () {
let daoConfig: Partial<DAOSettings>;
let baal: Baal;
let gnosisSafe: GnosisSafe;
let sharesToken: Shares;
let tributeMinion: TributeMinion;
const tribute = 100;
const requestedShares = 1234;
const requestedLoot = 1007;
this.beforeEach(async function () {
daoConfig = {
PROPOSAL_OFFERING: 69,
SPONSOR_THRESHOLD: 101,
};
const {
Baal,
Shares,
GnosisSafe,
TributeMinion,
} = await baalSetup({
daoSettings: daoConfig,
});
baal = Baal;
gnosisSafe = GnosisSafe;
sharesToken = Shares;
tributeMinion = TributeMinion;
});
it("allows external tribute minion to submit share proposal in exchange for tokens", async () => {
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(0);
expect(await weth.balanceOf(users.applicant.address)).to.equal(1000);
await users.applicant.weth?.approve(tributeMinion.address, 10000);
const currentShares = await sharesToken.balanceOf(users.applicant.address);
users.applicant.tributeMinion && await proposalHelpers.submitAndProcessTributeProposal({
tributeMinion: users.applicant.tributeMinion,
baal,
applicantAddress: users.applicant.address,
tributeToken: weth.address,
tribute,
requestedShares,
requestedLoot,
proposalOffering: daoConfig.PROPOSAL_OFFERING
});
expect(await sharesToken.balanceOf(users.applicant.address)).to.equal(
requestedShares + parseInt(currentShares.toString())
);
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(tribute);
});
// tribute proposal can not self sponsor because of potential tx.origin issues
// it("should not fail to tribute without offering", async () => {
// const currentShares = await sharesToken.balanceOf(users.summoner.address);
// // CONDITION: Member should be able to self-sponsor if shares >= SPONSOR_THRESHOLD
// expect(currentShares.gte(BigNumber.from(daoConfig.SPONSOR_THRESHOLD)));
// // const summonerTributeMinion = tributeMinion.connect(summoner);
// // const requestedShares = 1234;
// // const tribute = 1000;
// // const tributeToken = weth.connect(summoner);
// expect(await weth.balanceOf(gnosisSafe.address)).to.equal(0);
// expect(await weth.balanceOf(users.summoner.address)).to.gte(tribute);
// await users.summoner.weth?.approve(tributeMinion.address, tribute);
// users.summoner.tributeMinion && await proposalHelpers.submitAndProcessTributeProposal({
// tributeMinion: users.summoner.tributeMinion,
// baal,
// applicantAddress: users.summoner.address,
// tributeToken: weth.address,
// tribute,
// requestedShares,
// requestedLoot,
// // proposalOffering: daoConfig.PROPOSAL_OFFERING
// });
// expect(await sharesToken.balanceOf(users.summoner.address))
// .to.eq(
// currentShares.add(BigNumber.from(requestedShares)),
// );
// });
it("fails to tribute without offering", async () => {
const currentShares = await sharesToken.balanceOf(users.applicant.address);
// CONDITION: Member should send tribute if shares < SPONSOR_THRESHOLD
expect(currentShares.lt(BigNumber.from(daoConfig.SPONSOR_THRESHOLD)));
expect(await weth.balanceOf(gnosisSafe.address)).to.equal(0);
expect(await weth.balanceOf(users.applicant.address)).to.equal(1000);
await users.applicant.weth?.approve(tributeMinion.address, 10000);
users.applicant.tributeMinion && await expect(users.applicant.tributeMinion.submitTributeProposal(
baal.address,
weth.address,
tribute,
requestedShares,
requestedLoot,
defaultProposalSettings.EXPIRATION,
defaultProposalSettings.BAAL_GAS,
"tribute"
)).to.be.revertedWith(revertMessages.submitProposalOffering);
});
});
});