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);
    });
  });
});
