2024-01-31 15:04:44 +03:00
|
|
|
const { ethers, network } = require("hardhat");
|
|
|
|
const contentHash = require("content-hash");
|
|
|
|
const { expect, assert } = require("chai");
|
|
|
|
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
|
2024-02-10 21:43:54 +03:00
|
|
|
const { deployAndExecuteProposal, getGovernance, getTorn } = require("./utils");
|
2024-01-31 15:04:44 +03:00
|
|
|
|
|
|
|
const tornAddr = "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C";
|
|
|
|
const sablierAddr = "0xCD18eAa163733Da39c232722cBC4E8940b1D8888";
|
|
|
|
const governanceAddr = "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce";
|
|
|
|
async function getManyEth(addr) {
|
|
|
|
await network.provider.send("hardhat_setBalance", [addr, "0x111166630153555558483537"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getEnsRegistry() {
|
|
|
|
const ensAddr = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
|
|
|
|
return await ethers.getContractAt(require("./abi/ensRegistry.abi.json"), ensAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getEnsResolver(ensName) {
|
|
|
|
const ensRegistry = await getEnsRegistry();
|
|
|
|
const resolverAddr = await ensRegistry.resolver(ethers.namehash(ensName));
|
|
|
|
return await ethers.getContractAt(require("./abi/ensResolver.abi.json"), resolverAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function resolveAddr(ensName) {
|
|
|
|
if (ethers.isAddress(ensName)) return ensName;
|
|
|
|
const ensResolver = await getEnsResolver(ensName);
|
|
|
|
|
|
|
|
return await ensResolver.addr(ethers.namehash(ensName));
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getTornContract() {
|
|
|
|
return await ethers.getContractAt(require("./abi/torn.abi.json"), tornAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getSablierContract() {
|
|
|
|
return await ethers.getContractAt(require("./abi/sabler.abi.json"), sablierAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getMe() {
|
|
|
|
return await resolveAddr("butterfly-attractor.eth");
|
|
|
|
}
|
|
|
|
|
|
|
|
describe("Proposal results check", function () {
|
|
|
|
beforeEach(async function () {
|
|
|
|
await network.provider.request({
|
|
|
|
method: "hardhat_reset",
|
|
|
|
params: [
|
|
|
|
{
|
|
|
|
forking: {
|
|
|
|
jsonRpcUrl: config.networks.hardhat.forking.url,
|
|
|
|
blockNumber: config.networks.hardhat.forking.blockNumber,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-02-10 21:43:54 +03:00
|
|
|
async function getEvents(contract, eventName, fromBlock = 0) {
|
|
|
|
const filter = contract.filters[eventName];
|
|
|
|
const events = await contract.queryFilter(filter, fromBlock); //config.networks.hardhat.forking.blockNumber - 1
|
|
|
|
return events;
|
2024-01-31 15:04:44 +03:00
|
|
|
}
|
|
|
|
|
2024-02-10 21:43:54 +03:00
|
|
|
function normalizeAirdropAmount(amount) {
|
|
|
|
const halfYear = 180 * 24 * 60 * 60;
|
|
|
|
return amount - (amount % BigInt(halfYear));
|
|
|
|
}
|
|
|
|
|
|
|
|
it("Airdrop recipients storage contracts should deployed successfully", async function () {
|
|
|
|
const { airdropRecipientsContract } = await deployAndExecuteProposal();
|
|
|
|
expect(await ethers.provider.getCode(await airdropRecipientsContract.getAddress())).to.not.be.equal("0x");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Airdrop recipients deposits sum should be almost equal 1m", async function () {
|
|
|
|
const { airdropRecipientsContract } = await deployAndExecuteProposal();
|
|
|
|
const recipients = await airdropRecipientsContract.getAirdropRecipients();
|
|
|
|
const airdropSum = await recipients.reduce((acc, r) => acc + r[1], 0n);
|
|
|
|
const decimals = 10n ** 18n;
|
|
|
|
const millionTorn = 1000n * 1000n * decimals;
|
|
|
|
|
|
|
|
expect(airdropSum).to.greaterThan(millionTorn - 1n * decimals);
|
|
|
|
expect(airdropSum).to.lessThan(millionTorn + 1n * decimals);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Airdrop recipient deposit should calculated correctly", async function () {
|
|
|
|
const someRecipient = await resolveAddr("butterfly-effect.eth");
|
|
|
|
const governance = await getGovernance();
|
|
|
|
const recipientLockedBalance = await governance.lockedBalance(someRecipient);
|
|
|
|
|
|
|
|
const decimals = 10n ** 18n;
|
|
|
|
const millionTorn = 1000n * 1000n * decimals;
|
|
|
|
const { airdropRecipientsContract } = await deployAndExecuteProposal();
|
|
|
|
const recipients = await airdropRecipientsContract.getAirdropRecipients();
|
|
|
|
const airdropLockedSum = await recipients.reduce((acc, r) => acc + r[2], 0n);
|
|
|
|
const selectedRecipient = recipients.find((r) => r[0] === someRecipient);
|
|
|
|
const expectedRecipientAirdrop =
|
|
|
|
(recipientLockedBalance * ((millionTorn * decimals) / airdropLockedSum)) / decimals;
|
|
|
|
|
|
|
|
expect(selectedRecipient[1]).to.be.equal(expectedRecipientAirdrop);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Airdrop should start", async function () {
|
|
|
|
const { airdropContract } = await deployAndExecuteProposal();
|
|
|
|
const filter = airdropContract.filters.CreateAirdrop;
|
|
|
|
const events = await airdropContract.queryFilter(filter);
|
|
|
|
const args = events[0].args;
|
|
|
|
expect(args[1] - args[0]).to.be.equal(180 * 24 * 60 * 60);
|
|
|
|
expect(args[2]).to.be.equal([...require("../data/airdropRecipients.json")].length);
|
|
|
|
expect(args[3]).to.be.equal(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Airdrop recipient should be able to withdraw funds", async function () {
|
|
|
|
const someRecipient = await resolveAddr("butterfly-effect.eth");
|
|
|
|
const { airdropContract, airdropRecipientsContract } = await deployAndExecuteProposal();
|
|
|
|
const recipients = await airdropRecipientsContract.getAirdropRecipients();
|
|
|
|
const selectedRecipientInfo = recipients.find((r) => r[0] === someRecipient);
|
|
|
|
const recipientAirdropAmount = selectedRecipientInfo[1];
|
|
|
|
const halfYear = 180 * 24 * 60 * 60;
|
|
|
|
const normalizedAmount = recipientAirdropAmount - (recipientAirdropAmount % BigInt(halfYear));
|
|
|
|
const events = await getEvents(airdropContract, "CreateStream");
|
|
|
|
const recipientStreamId = events.find((e) => e.args[1] === someRecipient).args[0];
|
|
|
|
|
|
|
|
await time.increase(halfYear);
|
|
|
|
const torn = await getTorn();
|
|
|
|
const recipientBalance = await torn.balanceOf(someRecipient);
|
|
|
|
const connectedAirdrop = airdropContract.connect(await ethers.getImpersonatedSigner(someRecipient));
|
2024-02-12 12:27:34 +03:00
|
|
|
await connectedAirdrop.withdrawFromStream(recipientStreamId);
|
2024-02-10 21:43:54 +03:00
|
|
|
|
|
|
|
expect(await torn.balanceOf(someRecipient)).to.be.equal(recipientBalance + normalizedAmount);
|
|
|
|
});
|
|
|
|
|
2024-02-12 12:27:34 +03:00
|
|
|
it("Airdrop recipient should be able to withdraw part of funds early", async function () {
|
2024-02-10 21:43:54 +03:00
|
|
|
const someRecipient = await resolveAddr("butterfly-effect.eth");
|
2024-02-12 12:27:34 +03:00
|
|
|
const { airdropContract } = await deployAndExecuteProposal();
|
2024-02-10 21:43:54 +03:00
|
|
|
const halfYear = 180 * 24 * 60 * 60;
|
|
|
|
const events = await getEvents(airdropContract, "CreateStream");
|
|
|
|
const recipientStreamId = events.find((e) => e.args[1] === someRecipient).args[0];
|
|
|
|
|
|
|
|
await time.increase(halfYear / 2);
|
2024-02-12 12:27:34 +03:00
|
|
|
const torn = await getTorn();
|
|
|
|
const recipientBalance = await torn.balanceOf(someRecipient);
|
2024-02-10 21:43:54 +03:00
|
|
|
const connectedAirdrop = airdropContract.connect(await ethers.getImpersonatedSigner(someRecipient));
|
2024-02-12 12:27:34 +03:00
|
|
|
|
|
|
|
// Add timestamps and manual calculation bcs withdrawFromStream call mine next block and increase stream balance
|
|
|
|
const streamBalance = await connectedAirdrop.balanceOf(recipientStreamId, someRecipient);
|
|
|
|
const beforeWithdrawalTimestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp;
|
|
|
|
await connectedAirdrop.withdrawFromStream(recipientStreamId);
|
|
|
|
const afterWithdrawalTimestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp;
|
|
|
|
const blockChangeTime = afterWithdrawalTimestamp - beforeWithdrawalTimestamp;
|
|
|
|
const stream = await connectedAirdrop.getStream(recipientStreamId);
|
|
|
|
const ratePerSecond = stream[5];
|
|
|
|
const addedInCurrentBlock = BigInt(blockChangeTime) * ratePerSecond;
|
|
|
|
expect(await torn.balanceOf(someRecipient)).to.be.equal(recipientBalance + streamBalance + addedInCurrentBlock);
|
2024-02-10 21:43:54 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
it("Airdrop recipient should not be able to withdraw funds if his stake balance is lower than initial", async function () {
|
|
|
|
const someRecipient = await resolveAddr("butterfly-effect.eth");
|
2024-02-12 12:27:34 +03:00
|
|
|
const { airdropContract } = await deployAndExecuteProposal();
|
2024-02-10 21:43:54 +03:00
|
|
|
const halfYear = 180 * 24 * 60 * 60;
|
|
|
|
const events = await getEvents(airdropContract, "CreateStream");
|
|
|
|
const recipientStreamId = events.find((e) => e.args[1] === someRecipient).args[0];
|
|
|
|
|
|
|
|
await time.increase(halfYear);
|
|
|
|
const governance = await getGovernance(await ethers.getImpersonatedSigner(someRecipient));
|
|
|
|
governance.unlock(1000n * 10n ** 18n);
|
|
|
|
const connectedAirdrop = airdropContract.connect(await ethers.getImpersonatedSigner(someRecipient));
|
2024-02-12 12:27:34 +03:00
|
|
|
await expect(connectedAirdrop.withdrawFromStream(recipientStreamId)).to.be.revertedWith(
|
2024-02-10 21:43:54 +03:00
|
|
|
"not enough locked tokens in governance",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Airdrop recipient should not be able to withdraw funds if stream is canceled", async function () {
|
|
|
|
const someRecipient = await resolveAddr("butterfly-effect.eth");
|
|
|
|
const { airdropContract, airdropRecipientsContract } = await deployAndExecuteProposal();
|
|
|
|
const halfYear = 180 * 24 * 60 * 60;
|
|
|
|
const events = await getEvents(airdropContract, "CreateStream");
|
|
|
|
const recipientStreamId = events.find((e) => e.args[1] === someRecipient).args[0];
|
|
|
|
|
|
|
|
await time.increase(halfYear);
|
|
|
|
await airdropContract.cancelStream(recipientStreamId);
|
|
|
|
const connectedAirdrop = airdropContract.connect(await ethers.getImpersonatedSigner(someRecipient));
|
2024-02-12 12:27:34 +03:00
|
|
|
await expect(connectedAirdrop.withdrawFromStream(recipientStreamId)).to.be.revertedWith(
|
2024-02-10 21:43:54 +03:00
|
|
|
"stream does not exist",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Airdrop recipient should not be able to withdraw funds if airdrop is canceled", async function () {
|
|
|
|
const someRecipient = await resolveAddr("butterfly-effect.eth");
|
|
|
|
const { airdropContract, airdropRecipientsContract } = await deployAndExecuteProposal();
|
|
|
|
const recipients = await airdropRecipientsContract.getAirdropRecipients();
|
|
|
|
const halfYear = 180 * 24 * 60 * 60;
|
|
|
|
const events = await getEvents(airdropContract, "CreateStream");
|
|
|
|
const recipientStreamId = events.find((e) => e.args[1] === someRecipient).args[0];
|
|
|
|
|
|
|
|
await time.increase(halfYear);
|
|
|
|
await airdropContract.cancelAirdrop(1, recipients.length);
|
|
|
|
const connectedAirdrop = airdropContract.connect(await ethers.getImpersonatedSigner(someRecipient));
|
2024-02-12 12:27:34 +03:00
|
|
|
await expect(connectedAirdrop.withdrawFromStream(recipientStreamId)).to.be.revertedWith(
|
2024-02-10 21:43:54 +03:00
|
|
|
"stream does not exist",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("Airdrop cancelation should return tokens to governance", async function () {
|
|
|
|
const torn = await getTorn();
|
|
|
|
const tornDecimals = 10n ** 18n;
|
|
|
|
const govBalanceBeforeProposal = await torn.balanceOf(governanceAddr);
|
|
|
|
const { airdropContract, airdropAddr, airdropRecipientsContract } = await deployAndExecuteProposal();
|
|
|
|
const recipients = await airdropRecipientsContract.getAirdropRecipients();
|
|
|
|
const airdropSum = await recipients.reduce((acc, r) => acc + normalizeAirdropAmount(r[1]), 0n);
|
|
|
|
const governanceBalance = await torn.balanceOf(governanceAddr);
|
|
|
|
const airdropContractBalance = await torn.balanceOf(airdropAddr);
|
|
|
|
await airdropContract.cancelAirdrop(1, recipients.length);
|
|
|
|
|
|
|
|
expect(await torn.balanceOf(governanceAddr)).to.be.equal(governanceBalance + airdropSum);
|
|
|
|
expect(await torn.balanceOf(airdropAddr)).to.be.equal(airdropContractBalance - airdropSum);
|
|
|
|
expect((await torn.balanceOf(airdropAddr)) < 1n * tornDecimals);
|
|
|
|
|
|
|
|
await airdropContract.withdrawFunds();
|
|
|
|
expect(await torn.balanceOf(airdropAddr)).to.be.equal(0);
|
|
|
|
expect(await torn.balanceOf(governanceAddr)).to.be.equal(govBalanceBeforeProposal);
|
2024-01-31 15:04:44 +03:00
|
|
|
});
|
|
|
|
});
|