hardhat#network TypeScript Examples
The following examples show how to use
hardhat#network.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts From nouns-monorepo with GNU General Public License v3.0 | 7 votes |
rpc = <T = unknown>({
method,
params,
}: {
method: string;
params?: unknown[];
}): Promise<T> => {
return network.provider.send(method, params);
}
Example #2
Source File: main.ts From pawnft with GNU General Public License v3.0 | 6 votes |
/**
* Returns impersonated signer
* @param {string} account to impersonate
* @returns {Signer} authenticated as account
*/
async function impersonateSigner(account: string): Promise<Signer> {
// Impersonate account
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [account],
});
// Return ethers signer
return await ethers.provider.getSigner(account);
}
Example #3
Source File: itemPriceUpdate.ts From aavegotchi-contracts with MIT License | 6 votes |
describe("Testing Slot Change", async function () {
this.timeout(300000);
const diamondAddress = "0x86935F11C86623deC8a25696E1C19a8659CbF95d";
let daoFacet: DAOFacet;
let itemManager = "0xa370f2ADd2A9Fba8759147995d6A0641F8d7C119";
let itemsFacet: ItemsFacet;
// this.timeout(300000)
before(async function () {
await upgrade();
daoFacet = (await ethers.getContractAt(
"DAOFacet",
diamondAddress
)) as DAOFacet;
itemsFacet = (await ethers.getContractAt(
"contracts/Aavegotchi/facets/ItemsFacet.sol:ItemsFacet",
diamondAddress
)) as ItemsFacet;
});
it("Make sure prices are updated", async function () {
console.log('updating prices')
daoFacet = await impersonate(itemManager, daoFacet, ethers, network);
await daoFacet.batchUpdateItemsPrice([60,61],[1000,9000])
const wizardHatPrice= await (await itemsFacet.getItemType(60)).ghstPrice
expect(wizardHatPrice.toString()).to.equal("1000")
const LegendaryWizardHatPrice=await (await itemsFacet.getItemType(61)).ghstPrice
expect(LegendaryWizardHatPrice.toString()).to.equal("9000")
});
});
Example #4
Source File: utils.ts From nouns-monorepo with GNU General Public License v3.0 | 6 votes |
minerStop = async (): Promise<void> => {
await network.provider.send('evm_setAutomine', [false]);
await network.provider.send('evm_setIntervalMining', [0]);
}
Example #5
Source File: updateWearableSet.ts From aavegotchi-contracts with MIT License | 6 votes |
async function main() {
const setIds = [137];
const setInfo: WearableSetOutput[] = [
{
name: "Daimyogotchi",
traitsBonuses: [7, 0, 1, 2, -1],
wearableIds: [155, 313, 314, 315],
allowedCollaterals: [],
},
];
let signer: Signer;
const testing = ["hardhat", "localhost"].includes(network.name);
if (testing) {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [itemManagerAlt],
});
signer = await ethers.provider.getSigner(itemManagerAlt);
} else if (network.name === "matic") {
signer = (await ethers.getSigners())[0];
} else {
throw Error("Incorrect network selected");
}
const daoFacet = (await ethers.getContractAt(
"DAOFacet",
maticDiamondAddress,
signer
)) as DAOFacet;
const tx = await daoFacet.updateWearableSets(setIds, setInfo);
console.log("tx hash:", tx.hash);
await tx.wait();
console.log("Updated wearable sets");
}
Example #6
Source File: pauseTickets.ts From ghst-staking with MIT License | 6 votes |
async function togglePause() {
let stakingFacet = await ethers.getContractAt(
"StakingFacet",
maticStakingAddress
);
const ownershipFacet = await ethers.getContractAt(
"OwnershipFacet",
maticStakingAddress
);
const owner = await ownershipFacet.owner();
const signer = await getDiamondSigner(ethers, network, undefined, true);
if (network.name === "matic") {
const tx: PopulatedTransaction =
await stakingFacet.populateTransaction.togglePauseTickets();
console.log("tx data:", tx.data);
await sendToMultisig(stakingDiamondUpgrader, signer, tx, ethers);
} else {
stakingFacet = await impersonate(owner, stakingFacet, ethers, network);
await stakingFacet.togglePauseTickets();
stakingFacet = await impersonate(
"0x51208e5cC9215c6360210C48F81C8270637a5218",
stakingFacet,
ethers,
network
);
await stakingFacet.claimTickets([0], [1]);
}
}
Example #7
Source File: removeXpTest.ts From aavegotchi-contracts with MIT License | 5 votes |
describe("Testing Slot Change", async function () {
this.timeout(300000);
const diamondAddress = "0x86935F11C86623deC8a25696E1C19a8659CbF95d";
let daoFacet: DAOFacet;
let itemManager = "0xa370f2ADd2A9Fba8759147995d6A0641F8d7C119";
let itemsFacet: ItemsFacet;
// this.timeout(300000)
before(async function () {
await upgrade();
daoFacet = (await ethers.getContractAt(
"DAOFacet",
diamondAddress
)) as DAOFacet;
itemsFacet = (await ethers.getContractAt(
"contracts/Aavegotchi/facets/ItemsFacet.sol:ItemsFacet",
diamondAddress
)) as ItemsFacet;
});
it("Test Remove XP", async function () {
daoFacet = await impersonate(itemManager, daoFacet, ethers, network);
const aavegotchiFacet = (await ethers.getContractAt(
"contracts/Aavegotchi/facets/AavegotchiFacet.sol:AavegotchiFacet",
diamondAddress
)) as AavegotchiFacet;
let aavegotchi = await aavegotchiFacet.getAavegotchi("22449");
expect(aavegotchi.experience).to.equal("40");
console.log("xp:", aavegotchi.experience.toString());
const tx = await daoFacet.removeExperience(["22449"], ["40"]);
const receipt = await tx.wait();
if (!receipt.status) {
throw Error(`Transfer failed: ${tx.hash}`);
}
aavegotchi = await aavegotchiFacet.getAavegotchi("22449");
expect(aavegotchi.experience).to.equal("0");
console.log("xp:", aavegotchi.experience.toString());
});
it("Reverts for Gotchi with 0 XP", async function () {
daoFacet = await impersonate(itemManager, daoFacet, ethers, network);
await expect(
daoFacet.removeExperience(["18937"], ["40"])
).to.be.revertedWith("DAOFacet: Remove XP would underflow");
});
});
Example #8
Source File: time.ts From balancer-v2-monorepo with GNU General Public License v3.0 | 5 votes |
currentTimestamp = async (): Promise<BigNumber> => {
const { timestamp } = await network.provider.send('eth_getBlockByNumber', ['latest', true]);
return bn(timestamp);
}
Example #9
Source File: blocks.ts From shoyu with MIT License | 5 votes |
autoMining = async (setting: boolean) => {
await network.provider.send("evm_setAutomine", [setting]);
}
Example #10
Source File: update-sideViewsDimensions2.ts From aavegotchi-contracts with MIT License | 5 votes |
async function main() {
const diamondAddress = "0x86935F11C86623deC8a25696E1C19a8659CbF95d";
let itemManager = "0xa370f2ADd2A9Fba8759147995d6A0641F8d7C119";
let signer: Signer;
const testing = ["hardhat", "localhost"].includes(network.name);
if (testing) {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [itemManager],
});
signer = await ethers.getSigner(itemManager);
} else if (network.name === "matic") {
const accounts = await ethers.getSigners();
signer = accounts[0]; //new LedgerSigner(ethers.provider);
console.log("signer:", signer);
} else {
throw Error("Incorrect network selected");
}
//dimensions
const svgViewsFacet = await ethers.getContractAt(
"SvgViewsFacet",
diamondAddress,
signer
);
console.log("Update dimensions");
let tx = await svgViewsFacet.setSideViewDimensions(sideViewDimensions2, {
gasPrice: gasPrice,
});
let receipt = await tx.wait();
if (!receipt.status) {
throw Error(`Error:: ${tx.hash}`);
}
// // **** Test ****
// // BODY = 0;
// // FACE = 1;
// // EYES = 2;
// // HEAD = 3;
// // RIGHT = 4;
// // LEFT = 5;
// // PET = 6;
// // BG = 7;
let numTraits1 : [number,number,number,number,number,number]=[99, 99, 99, 99, 12, 9];
let wearables1 :[
number,number,number,number,
number,number,number,number,
number,number,number,number,
number,number,number,number
]= [91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
const sidePreview = await svgViewsFacet.previewSideAavegotchi(
"2",
"0xE0b22E0037B130A9F56bBb537684E6fA18192341",
numTraits1,
wearables1
);
console.log("Side Preview: ", sidePreview);
}
Example #11
Source File: fundingFromCoinbase.ts From gateway-sol with GNU General Public License v3.0 | 5 votes |
async function main() {
console.log('funding from coinbase ...');
let found;
while (!found) {
try {
await ethers.provider.send('eth_chainId', []);
found = true;
} catch (e) {} // TODO timeout ?
if (!found) {
console.log(`retrying...`);
await wait(1);
}
}
if (!('url' in network.config)) {
console.log('cannot run on in memory hardhat network.');
return;
}
const coinbase = await ethers.provider.send('eth_coinbase', []);
if (!coinbase) {
console.log('no coinbase');
return;
}
const accounts = await ethers.provider.listAccounts();
let accountsToFund = accounts;
if (coinbase === accounts[0]) {
accountsToFund = accounts.slice(1);
}
const coinbaseBalance = await ethers.provider.getBalance(coinbase);
const nonce = await ethers.provider.getTransactionCount(coinbase);
const maxAmount = BigNumber.from('10000000000000000000');
let amount = coinbaseBalance.div(accountsToFund.length);
if (amount.gt(maxAmount)) {
amount = maxAmount;
}
if (coinbaseBalance.gt(0)) {
const rawProvider = new JsonRpcProvider(network.config.url);
const coinbaseSigner = rawProvider.getSigner(coinbase);
for (let i = 0; i < accountsToFund.length; i++) {
const tx = await coinbaseSigner.sendTransaction({
to: accountsToFund[i],
value: amount.sub(21000).toHexString(),
nonce: BigNumber.from(nonce + i).toHexString(),
});
console.log(tx.hash);
}
} else {
console.log('coinbase has zero balance');
}
}
Example #12
Source File: updateItemPrices.ts From aavegotchi-contracts with MIT License | 5 votes |
async function main() {
const itemIds = [
245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259,
260, 261, 262, 263, 71, 72, 73, 74, 75,
];
const ghstPrice = [
100, 10, 10, 10, 100, 100, 100, 5, 5, 5, 300, 300, 300, 10000, 10000, 10000,
2000, 2000, 2000, 100, 2000, 2000, 2000, 2000,
];
const ghstPricesFinal = ghstPrice.map((price) =>
ethers.utils.parseEther(price.toString())
);
let signer: Signer;
let owner = "0xa370f2ADd2A9Fba8759147995d6A0641F8d7C119";
const testing = ["hardhat", "localhost"].includes(network.name);
if (testing) {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [owner],
});
signer = await ethers.provider.getSigner(owner);
} else if (network.name === "matic") {
signer = new LedgerSigner(ethers.provider, "hid", "m/44'/60'/2'/0/0");
} else {
throw Error("Incorrect network selected");
}
const daoFacet = (await ethers.getContractAt(
"DAOFacet",
maticDiamondAddress,
signer
)) as DAOFacet;
const tx = await daoFacet.batchUpdateItemsPrice(itemIds, ghstPricesFinal);
console.log("tx hash:", tx.hash);
await tx.wait();
console.log("Updated item prices");
}
Example #13
Source File: updateRate2Test.ts From ghst-staking with MIT License | 4 votes |
describe("Unstake and re-stake pools tokens", async function () {
const diamondAddress = maticStakingAddress;
before(async function () {
this.timeout(2000000000);
await updateRates();
stakingFacet = (await ethers.getContractAt(
"StakingFacet",
diamondAddress
)) as StakingFacet;
stakingFacet = await impersonate(
rateManager,
stakingFacet,
ethers,
network
);
});
it("User can stake GHST-MATIC token balance", async function () {
stakingFacet = (await impersonate(
testAddress,
stakingFacet,
ethers,
network
)) as StakingFacet;
let staked = await stakingFacet.stakedInCurrentEpoch(testAddress);
let stakedGhstMatic = staked[4];
expect(stakedGhstMatic.amount).to.equal(0);
let poolToken = (await ethers.getContractAt(
"ERC20",
stakedGhstMatic.poolAddress
)) as IERC20;
poolToken = (await impersonate(
testAddress,
poolToken,
ethers,
network
)) as IERC20;
await poolToken.approve(diamondAddress, ghstMaticStakeAmount);
await stakingFacet.stakeIntoPool(
stakedGhstMatic.poolAddress,
ghstMaticStakeAmount
);
staked = await stakingFacet.stakedInCurrentEpoch(testAddress);
stakedGhstMatic = staked[4];
expect(stakedGhstMatic.amount).to.equal(ghstMaticStakeAmount);
});
it("Check frens calculation", async function () {
const stakedDays = 1;
const beforeFrens = await stakingFacet.frens(testAddress);
console.log("befofre frens:", ethers.utils.formatEther(beforeFrens));
const currentEpoch = await stakingFacet.currentEpoch();
const rates = await stakingFacet.poolRatesInEpoch(currentEpoch);
ethers.provider.send("evm_increaseTime", [86400 * stakedDays]);
ethers.provider.send("evm_mine", []);
const afterFrens = await stakingFacet.frens(testAddress);
const estimatedFrens = rates[4].rate
.mul(ghstMaticStakeAmount)
.mul(stakedDays);
const finalDifference = ethers.utils.formatEther(
beforeFrens.add(estimatedFrens).sub(afterFrens)
);
expect(Math.abs(Number(finalDifference))).to.be.lessThan(1);
});
it("User can unstake GHST-MATIC token balance", async function () {
let staked = await stakingFacet.stakedInCurrentEpoch(testAddress);
let stakedGhstMatic = staked[4];
await stakingFacet.withdrawFromPool(
stakedGhstMatic.poolAddress,
stakedGhstMatic.amount
);
staked = await stakingFacet.stakedInCurrentEpoch(testAddress);
stakedGhstMatic = staked[4];
expect(stakedGhstMatic.amount).to.equal(0);
});
});
Example #14
Source File: Migration.test.ts From trident with GNU General Public License v3.0 | 4 votes |
describe.only("Migration", function () {
let _owner, owner, chef, migrator, usdcWethLp, usdc, weth, masterDeployer, factory, Pool, snapshotId, ERC20;
let manualMigrator: TridentSushiRollCP;
before(async () => {
snapshotId = await ethers.provider.send("evm_snapshot", []);
await network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
blockNumber: 13390000,
},
},
],
});
_owner = "0x9a8541ddf3a932a9a922b607e9cf7301f1d47bd1"; // timelock, owner of MasterChef
owner = await ethers.getSigner(_owner);
const [alice] = await ethers.getSigners();
const BentoBox = await ethers.getContractFactory("BentoBoxV1");
const MasterDeployer = await ethers.getContractFactory("MasterDeployer");
const Factory = await ethers.getContractFactory("ConstantProductPoolFactory");
const ManualMigrator = await ethers.getContractFactory<TridentSushiRollCP__factory>("TridentSushiRollCP");
const Migrator = await ethers.getContractFactory<Migrator__factory>("Migrator");
Pool = await ethers.getContractFactory("ConstantProductPool");
ERC20 = await ethers.getContractFactory("ERC20Mock");
chef = await ethers.getContractAt(mcABI, "0xc2EdaD668740f1aA35E4D8f227fB8E17dcA888Cd", owner);
usdc = await ERC20.attach("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
weth = await ERC20.attach("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
usdcWethLp = await ERC20.attach("0x397FF1542f962076d0BFE58eA045FfA2d347ACa0"); // pid 1
await network.provider.send("hardhat_setBalance", [chef.address, "0x100000000000000000000"]);
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [chef.address],
});
await network.provider.send("hardhat_setBalance", [_owner, "0x100000000000000000000"]);
await usdcWethLp.connect(await ethers.getSigner(chef.address)).transfer(_owner, "0xfffffffff"); // give some LP tokens to _owner for testing purposes
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [_owner],
});
const bentoBox = await BentoBox.deploy(weth.address);
masterDeployer = await MasterDeployer.deploy(0, alice.address, bentoBox.address);
factory = await Factory.deploy(masterDeployer.address);
migrator = await Migrator.deploy(bentoBox.address, masterDeployer.address, factory.address, chef.address);
manualMigrator = await ManualMigrator.deploy(bentoBox.address, factory.address, masterDeployer.address);
await masterDeployer.addToWhitelist(factory.address);
await chef.setMigrator(migrator.address);
});
it("Should prepare for migration in chef", async () => {
const _migrator = await chef.migrator();
expect(_migrator).to.be.eq(migrator.address);
});
it("Should migrate successfully from chef", async () => {
const oldTotalSupply = await usdcWethLp.totalSupply();
const oldUsdcBalance = await usdc.balanceOf(usdcWethLp.address);
const oldWethBalance = await weth.balanceOf(usdcWethLp.address);
const oldLpToken = (await chef.poolInfo(1)).lpToken;
const mcBalance = await usdcWethLp.balanceOf(chef.address);
expect(oldLpToken).to.be.eq(usdcWethLp.address, "We don't have the corect LP address");
await chef.migrate(1);
const newTotalSupply = await usdcWethLp.totalSupply();
const newUsdcBalance = await usdc.balanceOf(usdcWethLp.address);
const newWethBalance = await weth.balanceOf(usdcWethLp.address);
const deployData = ethers.utils.defaultAbiCoder.encode(
["address", "address", "uint256", "bool"],
[usdc.address, weth.address, 30, false]
);
const salt = ethers.utils.keccak256(deployData);
const pool = await Pool.attach(await factory.configAddress(salt));
expect((await pool.totalSupply()).gt(0)).to.be.true;
expect(oldTotalSupply.gt(newTotalSupply)).to.be.true;
expect(oldUsdcBalance.gt(newUsdcBalance)).to.be.true;
expect(oldWethBalance.gt(newWethBalance)).to.be.true;
// we must not allow two calls for the same pool
await expect(chef.migrate(1)).to.be.revertedWith("ONLY_ONCE");
const _intermediaryToken = (await chef.poolInfo(1)).lpToken;
expect(_intermediaryToken).to.not.be.eq(oldLpToken, "we dodn't swap out tokens in masterchef");
const intermediaryToken = await ERC20.attach(_intermediaryToken);
const intermediaryTokenBalance = await pool.balanceOf(_intermediaryToken);
expect(intermediaryTokenBalance.gt(0)).to.be.true;
const newMcBalance = await intermediaryToken.balanceOf(chef.address);
expect(newMcBalance.toString()).to.be.eq(
newMcBalance.toString(),
"MC didn't receive the correct amount of the intermediary token"
);
});
it("Should migrate uniswap v2 style Lp positions outside of MasterChef", async () => {
// _owner has some usdc-weth lp coins we can migrate
const balance = (await usdcWethLp.balanceOf(_owner)).div(10);
usdcWethLp.connect(owner).approve(manualMigrator.address, balance);
await expect(
manualMigrator
.connect(owner)
.migrateLegacyToCP(usdcWethLp.address, balance.div(2), 30, false, balance, balance, balance)
).to.be.revertedWith(customError("MinimumOutput"));
await manualMigrator.connect(owner).migrateLegacyToCP(usdcWethLp.address, balance.div(2), 30, false, 0, 0, 0);
const poolAddy = (await factory.getPools(usdc.address, weth.address, 0, 1))[0];
const pool = await ERC20.attach(poolAddy);
const newBalance = await pool.balanceOf(_owner);
await manualMigrator.connect(owner).migrateLegacyToCP(usdcWethLp.address, balance.div(2), 30, false, 0, 0, 0);
expect(newBalance.gt(0)).to.be.true;
expect((await pool.balanceOf(_owner)).gt(newBalance)).to.be.true;
});
it("Should migrate from one trident CP configuration to another", async () => {
// _owner has some usdc-weth lp coins we can migrate
const balance = (await usdcWethLp.balanceOf(_owner)).div(10);
usdcWethLp.connect(owner).approve(manualMigrator.address, balance);
await manualMigrator.connect(owner).migrateLegacyToCP(usdcWethLp.address, balance, 30, false, 0, 0, 0);
const poolAddy = (await factory.getPools(usdc.address, weth.address, 0, 1))[0];
const pool = await ERC20.attach(poolAddy);
const poolBalance = await pool.balanceOf(_owner);
pool.connect(owner).approve(manualMigrator.address, poolBalance);
await manualMigrator.connect(owner).migrateCP(poolAddy, poolBalance, 30, true, 0, 0, 0);
const newPoolAddy = (await factory.getPools(usdc.address, weth.address, 1, 1))[0];
const newPool = await ERC20.attach(newPoolAddy);
const newPoolBalance = await newPool.balanceOf(_owner);
expect((await pool.balanceOf(_owner)).eq(0)).to.be.true;
expect(poolBalance.gt(newPoolBalance)).to.be.true; // balance won't be equal since the pool burns some liquidity
expect(poolBalance.lt(newPoolBalance.add(10001))).to.be.true;
});
after(async () => {
await network.provider.request({
method: "hardhat_reset",
params: [],
});
// disable forking and reset to not affect the wholes repo's test suite
await network.provider.send("evm_revert", [snapshotId]);
});
});
Example #15
Source File: stkwamGHSTTest.ts From ghst-staking with MIT License | 4 votes |
describe("Perform all staking calculations", async function () {
before(async function () {
this.timeout(200000000);
deployedAddresses = await deploy();
receiptToken = deployedAddresses.stkwamGHST;
//set signer
const accounts = await ethers.getSigners();
let testing = ["hardhat", "localhost"].includes(network.name);
if (testing) {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [ghstOwner],
});
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [amGHSTHolder],
});
ghstSigner = await ethers.provider.getSigner(ghstOwner);
amGHSTSigner = await ethers.provider.getSigner(amGHSTHolder);
} else if (network.name === "matic") {
ghstSigner = accounts[0];
} else {
throw Error("Incorrect network selected");
}
amGHSTContract = (await ethers.getContractAt(
"contracts/test/GHST/ERC20.sol:ERC20",
amGHSTV3,
amGHSTSigner
)) as ERC20;
console.log("wamghst:", deployedAddresses);
wamGHST = await ethers.getContractAt(
"WrappedAToken",
deployedAddresses.wamGHST.address
);
ghstContract = (await ethers.getContractAt(
"contracts/test/GHST/ERC20.sol:ERC20",
ghstAddress
)) as ERC20;
});
it("Owner should be deployer", async function () {
const owner = await wamGHST.owner();
expect(owner.toLowerCase()).to.equal(ghstOwner.toLowerCase());
});
it("Non-owner cannot change owner", async function () {
wamGHST = await impersonate(secondAddress, wamGHST, ethers, network);
await expect(wamGHST.transferOwnership(secondAddress)).to.be.revertedWith(
"Ownable: caller is not the owner"
);
});
it("Owner can change owner", async function () {
wamGHST = await impersonate(ghstOwner, wamGHST, ethers, network);
await wamGHST.transferOwnership(secondAddress);
const owner = await wamGHST.owner();
expect(owner.toLowerCase()).to.equal(secondAddress.toLowerCase());
});
it("Should make an empty wamGHST contract", async () => {
const WamGHST = await ethers.getContractFactory("WrappedAToken");
wamGHST = (await WamGHST.connect(ghstSigner).deploy()) as WrappedAToken;
const aToken = await ethers.getContractAt(
"contracts/interfaces/IERC20.sol:IERC20",
amGHSTV3,
ghstSigner
);
await aToken.approve(wamGHST.address, ethers.utils.parseEther("0.1"));
await wamGHST
.connect(ghstSigner)
.initialize(
amGHSTV3,
ghstAddress,
lendingPoolV3,
rewardsControllerV3,
daoTreasury,
amGHSTHolder,
0,
"Wrapped AAVE Polygon GHST",
"WaPolGHST"
);
let amGHSTSignerBalance = await amGHSTContract.balanceOf(
await amGHSTSigner.getAddress()
);
await amGHSTContract
.connect(amGHSTSigner)
.approve(wamGHST.address, amGHSTSignerBalance);
});
it("Should deposit 10 atokens and receive wamGHST 1:1", async () => {
const depositAmount = ethers.utils.parseEther("10");
await wamGHST.connect(amGHSTSigner).enter(depositAmount);
expect(await wamGHST.balanceOf(await amGHSTSigner.getAddress())).to.equal(
depositAmount
);
expect(await amGHSTContract.balanceOf(wamGHST.address)).to.be.gte(
depositAmount
);
});
it("Should withdraw at least 5 aTokens, and the contract must retain at least 5 aTokens", async () => {
const redemptionAmount = ethers.utils.parseEther("5");
const balanceBefore = await amGHSTContract.balanceOf(
await amGHSTSigner.getAddress()
);
await wamGHST.connect(amGHSTSigner).leave(redemptionAmount);
expect(await wamGHST.balanceOf(await amGHSTSigner.getAddress())).to.equal(
redemptionAmount
);
expect(
await amGHSTContract.balanceOf(await amGHSTSigner.getAddress())
).to.be.gte(balanceBefore.add(redemptionAmount));
expect(await amGHSTContract.balanceOf(wamGHST.address)).to.be.gte(
redemptionAmount
);
});
it("Should withdraw the rest of the aTokens", async () => {
const redemptionAmount = ethers.utils.parseEther("5");
await wamGHST.connect(amGHSTSigner).leave(redemptionAmount);
expect(await wamGHST.balanceOf(await amGHSTSigner.getAddress())).to.equal(
0
);
expect(await amGHSTContract.balanceOf(wamGHST.address)).to.equal(0);
});
it("Should deposit from GHST", async () => {
const initialVaultBalance = await amGHSTContract.balanceOf(wamGHST.address);
const depositAmount = ethers.utils.parseEther("10");
await ghstContract
.connect(ghstSigner)
.approve(wamGHST.address, depositAmount);
await wamGHST.connect(ghstSigner).enterWithUnderlying(depositAmount);
let wamGHSTBalance = await wamGHST.balanceOf(await ghstSigner.getAddress());
expect(wamGHSTBalance).to.lte(depositAmount);
expect(wamGHSTBalance).to.be.gte(depositAmount.mul(1e6 - 1).div(1e6));
expect(await amGHSTContract.balanceOf(wamGHST.address)).to.be.gte(
initialVaultBalance.add(depositAmount)
);
});
it("Should withdraw to GHST", async () => {
const initialVaultAssets = await amGHSTContract.balanceOf(wamGHST.address);
const initialUserUnderlying = await ghstContract.balanceOf(
await ghstSigner.getAddress()
);
const initialUserShares = await wamGHST.balanceOf(
await ghstSigner.getAddress()
);
const initialShareSupply = await wamGHST.totalSupply();
const assetsReturned = await wamGHST
.connect(ghstSigner)
.leaveToUnderlying(initialUserShares);
expect(await wamGHST.balanceOf(await ghstSigner.getAddress())).to.equal(0);
expect(await amGHSTContract.balanceOf(wamGHST.address)).to.be.gte(
initialVaultAssets
.mul(initialShareSupply.sub(initialUserShares))
.div(initialShareSupply)
);
const userUnderlying = await ghstContract.balanceOf(
await ghstSigner.getAddress()
);
const expectedUnderlying = initialUserUnderlying.add(
initialVaultAssets.mul(initialUserShares).div(initialShareSupply)
);
expect(userUnderlying).to.be.lte(expectedUnderlying.mul(1e6 + 1).div(1e6));
expect(userUnderlying).to.be.gte(expectedUnderlying.mul(1e6 - 1).div(1e6));
});
it(
"Should be able to claim rewards (Difficult to test, and likely not necessary to test tbh)"
);
});
Example #16
Source File: ConcentratedLiquidityPool.test.ts From trident with GNU General Public License v3.0 | 4 votes |
describe("Concentrated Liquidity Product Pool", function () {
let _snapshotId: string;
let snapshotId: string;
let trident: Trident;
let defaultAddress: string;
const helper = new LinkedListHelper(-887272);
const step = 10800; // 2^5 * 3^2 * 5^2 (nicely divisible number)
before(async () => {
_snapshotId = await ethers.provider.send("evm_snapshot", []);
trident = await Trident.Instance.init();
defaultAddress = trident.accounts[0].address;
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
afterEach(async () => {
await network.provider.send("evm_revert", [snapshotId]);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
after(async () => {
await network.provider.send("evm_revert", [_snapshotId]);
_snapshotId = await ethers.provider.send("evm_snapshot", []);
});
describe("Valid actions", async () => {
it("Should mint liquidity (in / out of range, native / from bento, reusing ticks / new ticks)", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
// assume increasing tick value by one step brings us to a valid tick
// satisfy "lower even" & "upper odd" conditions
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(50),
amount1Desired: getBigNumber(50),
native: true,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
// normal mint
await addLiquidityViaManager(addLiquidityParams);
// same range mint, from bento
addLiquidityParams.native = !addLiquidityParams.native;
await addLiquidityViaManager(addLiquidityParams);
// normal mint, narrower range
addLiquidityParams = helper.setTicks(lower + step / 2, upper - step / 2, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
// mint on the same lower tick
// @dev if a tick exists we dont' have to provide the tickOld param
addLiquidityParams = helper.setTicks(lower, upper + step, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
// mint on the same upper tick
addLiquidityParams = helper.setTicks(lower - step, upper, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
// mint below trading price
addLiquidityParams = helper.setTicks(lower - 2 * step, upper - 2 * step, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
// mint above trading price
addLiquidityParams = helper.setTicks(lower + 2 * step, upper + 2 * step, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
}
});
it("should add liquidity", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
}
});
it("should add liquidity again and mint new NFT", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
positionId: 0,
};
await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
positionId: 0,
};
await addLiquidityViaManager(addLiquidityParams);
}
});
it("should increase liquidity", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
positionId: 0,
};
const mint = await addLiquidityViaManager(addLiquidityParams);
const userPositionMint1 = (await trident.concentratedPoolManager.positions(mint.tokenId)).liquidity;
addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
positionId: mint.tokenId.toNumber(),
};
const mint2 = await addLiquidityViaManager(addLiquidityParams);
const userPositionMint2 = (await trident.concentratedPoolManager.positions(mint.tokenId)).liquidity;
expect(userPositionMint1.add(mint2.liquidity)).to.be.eq(userPositionMint2);
}
});
it("Should add liquidity and swap (without crossing)", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
// assume increasing tick value by one step brings us to a valid tick
// satisfy "lower even" & "upper odd" conditions
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
const lowerPrice = await trident.tickMath.getSqrtRatioAtTick(lower);
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const maxDx = await getDx(await pool.liquidity(), lowerPrice, currentPrice, false);
// swap back and forth
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: true,
inAmount: maxDx,
recipient: defaultAddress,
});
await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: false,
inAmount: swapTx.output,
recipient: defaultAddress,
});
}
});
it("Should add liquidity and swap (with crossing ticks)", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick - tickSpacing;
let lower = nearestEvenValidTick - 2 * tickSpacing;
let upper = nearestEvenValidTick + 3 * tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(100),
amount1Desired: getBigNumber(100),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams = helper.setTicks(lower - 10 * step, upper + 10 * step, addLiquidityParams);
const lp = await addLiquidityViaManager(addLiquidityParams);
// swap accross the range and back
// â–¼ - - - - - - -> â–¼
// ----------------|xxxxxxxxxxx|-------------------------------
// ----|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|-----
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const upperPrice = await trident.tickMath.getSqrtRatioAtTick(upper);
const maxDy = await getDy(await pool.liquidity(), currentPrice, upperPrice, false);
let swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy.mul(2),
recipient: defaultAddress,
});
swapTx = await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: true,
inAmount: swapTx.output,
recipient: defaultAddress,
});
// swap accross the range and back
// â–¼ - - - - - - -> â–¼
// ----------------|xxxxxxxxxxx|-----|xxxxxxxxxx|--------
// ------------------------------------------------------
await removeLiquidityViaManager({
pool,
tokenId: lp.tokenId.toNumber(),
liquidityAmount: lp.liquidity,
recipient: defaultAddress,
unwrapBento: true,
});
addLiquidityParams = helper.setTicks(lower + 3 * step, upper + 5 * step, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: swapTx.output,
recipient: defaultAddress,
});
await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: true,
inAmount: swapTx.output,
recipient: defaultAddress,
});
}
});
it("Should add liquidity and swap (with crossing through empty space)", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(100),
amount1Desired: getBigNumber(100),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams.amount0Desired = addLiquidityParams.amount0Desired.mul(2);
addLiquidityParams.amount1Desired = addLiquidityParams.amount1Desired.mul(2);
addLiquidityParams = helper.setTicks(lower + 3 * step, upper + 3 * step, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
// swap accross a zero liquidity range and back
// â–¼ - - - - - - - - - -> â–¼
// ----|----|-------|xxxxxxxxxxxx|-------|xxxxxxxxxxx|-----
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const upperPrice = await trident.tickMath.getSqrtRatioAtTick(upper);
const maxDy = await getDy(await pool.liquidity(), currentPrice, upperPrice, false);
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy.mul(2),
recipient: defaultAddress,
});
await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: true,
inAmount: swapTx.output,
recipient: defaultAddress,
});
}
});
it("Should distribute fees correctly", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(100),
amount1Desired: getBigNumber(100),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
const tokenIdA = (await addLiquidityViaManager(addLiquidityParams)).tokenId;
addLiquidityParams.amount0Desired = addLiquidityParams.amount0Desired.mul(2);
addLiquidityParams.amount1Desired = addLiquidityParams.amount1Desired.mul(2);
const tokenIdB = (await addLiquidityViaManager(addLiquidityParams)).tokenId;
addLiquidityParams = helper.setTicks(lower - step * 2, upper - step * 2, addLiquidityParams);
const tokenIdC = (await addLiquidityViaManager(addLiquidityParams)).tokenId;
// swap within tick
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const upperPrice = await trident.tickMath.getSqrtRatioAtTick(upper);
const maxDy = await getDy(await pool.liquidity(), currentPrice, upperPrice, false);
let swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy,
recipient: defaultAddress,
});
swapTx = await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: true,
inAmount: swapTx.output,
recipient: defaultAddress,
});
const positionNewFeeGrowth = await pool.rangeFeeGrowth(lower, upper);
const globalFeeGrowth0 = await pool.feeGrowthGlobal0();
const globalFeeGrowth1 = await pool.feeGrowthGlobal1();
expect(positionNewFeeGrowth.feeGrowthInside0.toString()).to.be.eq(
globalFeeGrowth0.toString(),
"Fee growth 0 wasn't accredited to the positions"
);
expect(positionNewFeeGrowth.feeGrowthInside1.toString()).to.be.eq(
globalFeeGrowth1.toString(),
"Fee growth 1 wasn't accredited to the positions"
);
const smallerPositionFees1 = await collectFees({
pool,
tokenId: tokenIdA,
recipient: defaultAddress,
unwrapBento: false,
});
swapTx = await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: false,
inAmount: swapTx.output,
recipient: defaultAddress,
});
await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: true,
inAmount: swapTx.output,
recipient: defaultAddress,
});
const smallerPositionFees2 = await collectFees({
pool,
tokenId: tokenIdA,
recipient: defaultAddress,
unwrapBento: false,
});
const smallerPositionFeesDy = smallerPositionFees2.dy.add(smallerPositionFees1.dy);
const smallerPositionFeesDx = smallerPositionFees2.dx.add(smallerPositionFees1.dx);
const biggerPositionFees = await collectFees({
pool,
tokenId: tokenIdB,
recipient: defaultAddress,
unwrapBento: false,
});
const outsidePositionFees = await collectFees({
pool,
tokenId: tokenIdC,
recipient: defaultAddress,
unwrapBento: false,
});
const ratioY = smallerPositionFeesDy.mul(1e6).div(biggerPositionFees.dy.div(2));
const ratioX = smallerPositionFeesDx.mul(1e6).div(biggerPositionFees.dx.div(2));
// allow for small rounding errors that happen when users claim on different intervals
expect(ratioY.lt(1000100) && ratioY.lt(999900), "fees 1 weren't proportionally split");
expect(ratioX.lt(1000100) && ratioX.lt(999900), "fees 0 weren't proportionally split");
expect(outsidePositionFees.dy.toString()).to.be.eq("0", "fees were acredited to a position not in range");
}
});
it("Should distribute fees correctly after crossing ticks", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
const lower = nearestEvenValidTick - step;
const upper = nearestEvenValidTick + step + tickSpacing;
const lower1 = nearestEvenValidTick + step;
const upper1 = nearestEvenValidTick + step * 2 + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(200),
amount1Desired: getBigNumber(200),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
// in range liquiditiy addition
(await addLiquidityViaManager(addLiquidityParams)).tokenId;
addLiquidityParams = helper.setTicks(
nearestEvenValidTick + step + tickSpacing + tickSpacing,
nearestEvenValidTick + step * 2 + tickSpacing,
addLiquidityParams
);
addLiquidityParams.amount1Desired = getBigNumber("0");
// out of range (1 sided) liq addition
(await addLiquidityViaManager(addLiquidityParams)).tokenId;
// swap within tick
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const upperPrice = await trident.tickMath.getSqrtRatioAtTick(upper);
const lowerPrice = await trident.tickMath.getSqrtRatioAtTick(lower);
const maxDx = await getDx(await pool.liquidity(), lowerPrice, currentPrice, false);
const feeGrowthGlobal0_init = await pool.feeGrowthGlobal0();
const feeGrowthGlobal1_init = await pool.feeGrowthGlobal1();
let swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: true,
inAmount: maxDx,
recipient: defaultAddress,
});
const positionNewFeeGrowth = await pool.rangeFeeGrowth(lower, upper);
const feeGrowthGlobal0 = await pool.feeGrowthGlobal0();
const feeGrowthGlobal1 = await pool.feeGrowthGlobal1();
expect(feeGrowthGlobal0.eq(feeGrowthGlobal0_init)).to.be.eq(true, "accreddited fees for the wrong token");
expect(feeGrowthGlobal1.gt(feeGrowthGlobal1_init)).to.be.eq(true, "didn't take fees");
expect(positionNewFeeGrowth.feeGrowthInside0.toString()).to.be.eq(
feeGrowthGlobal0.toString(),
"Fee growth 0 wasn't accredited to the positions"
);
expect(positionNewFeeGrowth.feeGrowthInside1.toString()).to.be.eq(
feeGrowthGlobal1.toString(),
"Fee growth 1 wasn't accredited to the positions"
);
// now trade out of the current range and check if the range still has the correct amount of fees credited
const maxDy = await getDy(
await pool.liquidity(),
(
await pool.getPriceAndNearestTicks()
)._price,
upperPrice,
false
);
swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy.mul(2),
recipient: defaultAddress,
});
const newPrice = (await pool.getPriceAndNearestTicks())._price;
const upper1Price = await trident.tickMath.getSqrtRatioAtTick(upper1);
expect(newPrice.gt(upperPrice)).to.be.true;
expect(newPrice.lt(upper1Price)).to.be.true; // ensure we crossed out of the initial range
const positionNewFeeGrowth_end = await pool.rangeFeeGrowth(lower, upper);
const feeGrowthGlobal0_end = await pool.feeGrowthGlobal0();
const feeGrowthGlobal1_end = await pool.feeGrowthGlobal1();
expect(feeGrowthGlobal0_end.gt(feeGrowthGlobal0)).to.be.eq(true, "accredited fees for the wrong token");
expect(feeGrowthGlobal1_end.eq(feeGrowthGlobal1)).to.be.eq(true, "didn't take fees");
expect(positionNewFeeGrowth_end.feeGrowthInside0.gt(positionNewFeeGrowth.feeGrowthInside0)).to.be.eq(
true,
"didn't account for token1 fees"
);
expect(positionNewFeeGrowth_end.feeGrowthInside1.toString()).to.be.eq(
positionNewFeeGrowth.feeGrowthInside1.toString(),
"position fee growth 1 isn't persistent"
);
}
});
it("Should collect protocolFee", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const immutables = await pool.getImmutables();
const tickSpacing = immutables._tickSpacing;
const token0 = immutables._token0;
const token1 = immutables._token1;
const barFeeTo = immutables._barFeeTo;
const oldBarFeeToBalanceToken0 = await trident.bento.balanceOf(token0, barFeeTo);
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
const oldBarFeeToBalanceToken1 = await trident.bento.balanceOf(token1, barFeeTo);
// assume increasing tick value by one step brings us to a valid tick
// satisfy "lower even" & "upper odd" conditions
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
const lowerPrice = await trident.tickMath.getSqrtRatioAtTick(lower);
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const maxDx = await getDx(await pool.liquidity(), lowerPrice, currentPrice, false);
// swap back and forth
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: true,
inAmount: maxDx,
recipient: defaultAddress,
});
await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: false,
inAmount: swapTx.output,
recipient: defaultAddress,
});
const { token0ProtocolFee, token1ProtocolFee } = await collectProtocolFee({ pool: pool });
const barFeeToBalanceToken0 = await trident.bento.balanceOf(token0, barFeeTo);
const barFeeToBalanceToken1 = await trident.bento.balanceOf(token1, barFeeTo);
expect(barFeeToBalanceToken0.toString()).to.be.eq(
oldBarFeeToBalanceToken0.add(token0ProtocolFee),
"didn't send the correct amount of token0 protocol fee to bar fee to"
);
expect(barFeeToBalanceToken1.toString()).to.be.eq(
oldBarFeeToBalanceToken1.add(token1ProtocolFee),
"didn't send the correct amount of token0 protocol fee to bar fee to"
);
}
});
it("Should burn the position and receive tokens back", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(100),
amount1Desired: getBigNumber(100),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
const mint = await addLiquidityViaManager(addLiquidityParams);
const userLiquidity = (await trident.concentratedPoolManager.positions(mint.tokenId)).liquidity;
const userLiquidityPartial = userLiquidity.sub(userLiquidity.div(3));
let removeLiquidityParams = {
pool: pool,
tokenId: Number(mint.tokenId.toString()),
liquidityAmount: userLiquidityPartial,
recipient: trident.accounts[0].address,
unwrapBento: false,
};
await removeLiquidityViaManager(removeLiquidityParams);
removeLiquidityParams.liquidityAmount = userLiquidity.sub(userLiquidityPartial);
await removeLiquidityViaManager(removeLiquidityParams);
}
});
it("Should calcualte seconds inside correctly", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lowerA = nearestEvenValidTick - 10 * step;
let upperA = nearestEvenValidTick + 10 * step + tickSpacing;
let lowerB = nearestEvenValidTick + 3 * step;
let upperB = nearestEvenValidTick + 9 * step + tickSpacing;
let lowerC = nearestEvenValidTick + 6 * step;
let upperC = nearestEvenValidTick + 9 * step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(100),
amount1Desired: getBigNumber(100),
native: false,
lowerOld: helper.insert(lowerA),
lower: lowerA,
upperOld: helper.insert(upperA),
upper: upperA,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
const mintA = await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams = helper.setTicks(lowerB, upperB, addLiquidityParams);
const mintB = await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams = helper.setTicks(lowerC, upperC, addLiquidityParams);
const mintC = await addLiquidityViaManager(addLiquidityParams);
const liquidityA = mintA.liquidity;
const liquidityB = mintB.liquidity;
const liquidityC = mintC.liquidity;
// execute each swap after some time
// â–¼ - - -> â–¼ - - -> â–¼ - - - -> â–¼ - - -
// --------------------------------------|xxxxxxxxxxxxxxxx|-----
// --------------|----------------|xxxxxxxxxxxxxxxxxxxxxxx|-----
// --------------|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|-----
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const upperPrice = await Trident.Instance.tickMath.getSqrtRatioAtTick(lowerB);
const maxDy = await getDy(await pool.liquidity(), currentPrice, upperPrice, false);
let output = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy.div(3).mul(2),
recipient: defaultAddress,
});
const fistSplData = await pool.getSecondsGrowthAndLastObservation();
let firstSplA = await trident.concentratedPoolStaker.rangeSecondsInside(pool.address, lowerA, upperA);
expect((await fistSplData)._secondsGrowthGlobal.toString()).to.be.eq(
firstSplA.toString(),
"didn't credit seconds per liquidity to active position"
);
await network.provider.send("evm_setNextBlockTimestamp", [fistSplData._lastObservation + 10000]);
output = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy.div(3).mul(2),
recipient: defaultAddress,
});
const secondSplData = await pool.getSecondsGrowthAndLastObservation();
const secondSplA = await trident.concentratedPoolStaker.rangeSecondsInside(pool.address, lowerA, upperA);
const secondSplB = await trident.concentratedPoolStaker.rangeSecondsInside(pool.address, lowerB, upperB);
expect(secondSplData._secondsGrowthGlobal.toString()).to.be.eq(
secondSplA.toString(),
"didn't credit seconds per liquidity to active position"
);
expect(secondSplB.eq(0)).to.be.true;
const timeIncrease = 10000;
await network.provider.send("evm_setNextBlockTimestamp", [secondSplData._lastObservation + timeIncrease]);
output = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy,
recipient: defaultAddress,
});
const thirdSplA = await trident.concentratedPoolStaker.rangeSecondsInside(pool.address, lowerA, upperA);
const thirdSplB = await trident.concentratedPoolStaker.rangeSecondsInside(pool.address, lowerB, upperB);
const splAseconds = thirdSplA.sub(secondSplA).mul(liquidityA);
const splBseconds = thirdSplB.sub(secondSplB).mul(liquidityB);
const totalSeconds = splAseconds.add(splBseconds).div(TWO_POW_128);
expect(totalSeconds.lte(timeIncrease) && totalSeconds.gte(timeIncrease - 3)).to.be.true;
}
});
it("Should create incentive", async () => {
helper.reset();
const pool = trident.concentratedPools[0];
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
const lower = nearestEvenValidTick - step;
const upper = nearestEvenValidTick + step + tickSpacing;
// add the following global liquidity
// â–¼
// ---------------------|xxxxxxxxxxx|-----
// --------|xxxxxxxxxxxxxxxxxxxxxxxx|----------
// --------|xxxxxxxxxxxxxxxxxxxxxxxx|----------
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(100),
amount1Desired: getBigNumber(100),
native: true,
lowerOld: helper.insert(lower - step),
lower: lower - step,
upperOld: helper.insert(upper + step),
upper: upper + step,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
const mintA = await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams.amount0Desired = getBigNumber(200);
addLiquidityParams.amount1Desired = getBigNumber(200);
const mintB = await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams.amount0Desired = getBigNumber(100);
addLiquidityParams.amount1Desired = getBigNumber(100);
addLiquidityParams = helper.setTicks(lower + 2 * step, upper + 4 * step, addLiquidityParams);
const mintC = await addLiquidityViaManager(addLiquidityParams);
// 1 swap should happen before we start an incentive
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const upperPrice = await Trident.Instance.tickMath.getSqrtRatioAtTick(lower + 2 * step);
const maxDy = await getDy(await pool.liquidity(), currentPrice, upperPrice, false);
let swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy.div(3),
recipient: defaultAddress,
});
const block = await ethers.provider.getBlock(swapTx.tx.blockNumber as number);
const incentiveLength = 10000; // in seconds
const incentiveAmount = getBigNumber(1000);
await trident.concentratedPoolStaker.addIncentive(pool.address, {
owner: defaultAddress,
token: trident.extraToken.address,
rewardsUnclaimed: incentiveAmount,
secondsClaimed: 123,
startTime: block.timestamp + 1,
endTime: block.timestamp + 1 + incentiveLength,
expiry: block.timestamp + 999999999,
});
let incentive = await trident.concentratedPoolStaker.incentives(pool.address, 0);
await network.provider.send("evm_setNextBlockTimestamp", [block.timestamp + 2]);
expect(incentive.secondsClaimed.toString()).to.be.eq("0", "didn't reset seconds claimed");
await trident.concentratedPoolStaker.subscribe(mintA.tokenId, [0]);
await trident.concentratedPoolStaker.subscribe(mintB.tokenId, [0]);
await trident.concentratedPoolStaker.subscribe(mintC.tokenId, [0]);
await network.provider.send("evm_setNextBlockTimestamp", [block.timestamp + incentiveLength / 4]);
swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy.div(3),
recipient: defaultAddress,
});
const recipientA = trident.accounts[1].address;
const recipientB = trident.accounts[2].address;
const recipientC = trident.accounts[3].address;
await trident.concentratedPoolStaker.claimRewards(mintA.tokenId, [0], recipientA, false);
await trident.concentratedPoolStaker.claimRewards(mintB.tokenId, [0], recipientB, false);
incentive = await trident.concentratedPoolStaker.incentives(pool.address, 0);
const secondsClaimed = incentive.secondsClaimed.div(TWO_POW_128);
const rewardsUnclaimed = incentive.rewardsUnclaimed;
const expectedRewardsUnclaimed = incentiveAmount.div(4).mul(3); // tree quarters
const rewardsA = await trident.bento.balanceOf(trident.extraToken.address, recipientA);
const rewardsB = await trident.bento.balanceOf(trident.extraToken.address, recipientB);
expect(secondsClaimed.sub(1).lte(incentiveLength / 4) && secondsClaimed.add(1).gte(incentiveLength / 4)).to.be.eq(
true,
"didn't claim a querter of reward duration"
);
expect(
rewardsUnclaimed.sub(10).lte(expectedRewardsUnclaimed) && rewardsUnclaimed.add(10).gte(expectedRewardsUnclaimed)
).to.be.eq(true, "didn't claim a quarter of rewards");
let ratio = rewardsA.mul(2).mul(1e6).div(rewardsB);
expect(ratio.gte(999900) && ratio.lte(1000100)).to.be.eq(true, "Didn't split rewards proportionally");
const newCurrentPrice = (await pool.getPriceAndNearestTicks())._price;
const oldLiq = await pool.liquidity();
const newMaxDy = await getDy(await pool.liquidity(), newCurrentPrice, upperPrice, false);
swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: newMaxDy.mul(2),
recipient: defaultAddress,
});
const newLiq = await pool.liquidity();
expect(newLiq.gt(oldLiq), "we didn't move into another range");
await network.provider.send("evm_setNextBlockTimestamp", [block.timestamp + incentiveLength + 1000]);
swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: newMaxDy.div(10),
recipient: defaultAddress,
});
await trident.concentratedPoolStaker.claimRewards(mintA.tokenId, [0], recipientA, false);
await trident.concentratedPoolStaker.claimRewards(mintB.tokenId, [0], recipientB, false);
await trident.concentratedPoolStaker.claimRewards(mintC.tokenId, [0], recipientC, false);
const newRewardsA = await trident.bento.balanceOf(trident.extraToken.address, recipientA);
const newRewardsB = await trident.bento.balanceOf(trident.extraToken.address, recipientB);
const newRewardsC = await trident.bento.balanceOf(trident.extraToken.address, recipientC);
ratio = rewardsA.mul(2).mul(1e6).div(rewardsB);
expect(ratio.gte(999900) && ratio.lte(1000100)).to.be.eq(true, "Didn't split rewards proportionally");
incentive = await trident.concentratedPoolStaker.incentives(pool.address, 0);
const sum = newRewardsA.add(newRewardsB).add(newRewardsC);
expect(sum.add(incentive.rewardsUnclaimed)).to.be.eq(
incentiveAmount.toString(),
"We distributed the wrong amount of tokens"
);
expect(incentive.rewardsUnclaimed.lt("99999"), "didn't leave dust in incentive");
});
});
describe("Invalid actions", async () => {
it("Should fail to mint with incorrect parameters for INVALID_TICK (LOWER), LOWER_EVEN, INVALID_TICK (UPPER), UPPER_ODD, WRONG_ORDER, LOWER_RANGE, UPPER_RANGE", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
// INVALID_TICK (LOWER)
let lower = nearestEvenValidTick - step + 1;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(50),
amount1Desired: getBigNumber(50),
native: true,
lowerOld: helper.insert(lower),
lower: nearestEvenValidTick - step + 1,
upperOld: helper.insert(upper),
upper: nearestEvenValidTick + step + tickSpacing,
positionOwner: trident.concentratedPoolManager.address,
recipient: trident.accounts[0].address,
};
if (tickSpacing != 1)
await expect(addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith(customError("InvalidTick"));
// LOWER_EVEN
addLiquidityParams.lower = nearestEvenValidTick - step + tickSpacing;
addLiquidityParams.upper = nearestEvenValidTick + step + tickSpacing;
addLiquidityParams.lowerOld = helper.insert(addLiquidityParams.lower);
addLiquidityParams.upperOld = helper.insert(addLiquidityParams.upper);
await expect(addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith(customError("LowerEven"));
// INVALID_TICK (UPPER)
addLiquidityParams.lower = nearestEvenValidTick - step;
addLiquidityParams.upper = nearestEvenValidTick + step + tickSpacing + 1;
addLiquidityParams.lowerOld = helper.insert(addLiquidityParams.lower);
addLiquidityParams.upperOld = helper.insert(addLiquidityParams.upper);
if (tickSpacing != 1)
await expect(addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith(customError("InvalidTick"));
// UPPER_ODD
addLiquidityParams.lower = nearestEvenValidTick - step;
addLiquidityParams.upper = nearestEvenValidTick + step;
addLiquidityParams.lowerOld = helper.insert(addLiquidityParams.lower);
addLiquidityParams.upperOld = helper.insert(addLiquidityParams.upper);
await expect(addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith(customError("UpperOdd"));
// WRONG ORDER
addLiquidityParams.lower = nearestEvenValidTick + 3 * step;
addLiquidityParams.upper = nearestEvenValidTick + step + tickSpacing;
addLiquidityParams.lowerOld = helper.insert(addLiquidityParams.lower);
addLiquidityParams.upperOld = helper.insert(addLiquidityParams.upper);
await expect(_addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith("WRONG_ORDER");
/* // LOWER_RANGE
addLiquidityParams.lower = -Math.floor(887272 / tickSpacing) * tickSpacing - tickSpacing;
addLiquidityParams.upper = nearestEvenValidTick + tickSpacing;
addLiquidityParams.lowerOld = lower;
addLiquidityParams.upperOld = helper.insert(addLiquidityParams.upper);
await expect(_addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith(customError("TickOutOfBounds")); */
/* // UPPER_RANGE
addLiquidityParams.lower = nearestEvenValidTick;
addLiquidityParams.upper = Math.floor(887272 / tickSpacing) * tickSpacing + tickSpacing;
addLiquidityParams.lowerOld = helper.insert(addLiquidityParams.lower);
addLiquidityParams.upperOld = helper.insert(addLiquidityParams.upper);
await expect(_addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith(customError("TickOutOfBounds")); */
// LOWER_ORDER - TO DO
//addLiquidityParams.lower = nearestEvenValidTick;
//addLiquidityParams.upper = Math.floor(887272 / tickSpacing) * tickSpacing + tickSpacing;
//addLiquidityParams.lowerOld = helper.insert(addLiquidityParams.lower);
//addLiquidityParams.upperOld = helper.insert(addLiquidityParams.upper);
//await expect(_addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith("LOWER_ORDER");
// UPPER_ORDER - TO DO
//addLiquidityParams.lower = nearestEvenValidTick;
//addLiquidityParams.upper = Math.floor(887272 / tickSpacing) * tickSpacing + tickSpacing;
//addLiquidityParams.lowerOld = helper.insert(addLiquidityParams.lower);
//addLiquidityParams.upperOld = helper.insert(addLiquidityParams.upper);
//await expect(_addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith("UPPER_ORDER");
}
});
it("should not increase liquidity if fees not collected", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
positionId: 0,
};
const mint = await addLiquidityViaManager(addLiquidityParams);
const userPositionMint1 = (await trident.concentratedPoolManager.positions(mint.tokenId)).liquidity;
const lowerPrice = await trident.tickMath.getSqrtRatioAtTick(lower);
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const maxDx = await getDx(await pool.liquidity(), lowerPrice, currentPrice, false);
// swap back and forth
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: true,
inAmount: maxDx,
recipient: defaultAddress,
});
addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
positionId: mint.tokenId.toNumber(),
};
await addLiquidityViaManager(addLiquidityParams);
}
});
it("should not increase liquidity if token id is wrong", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
positionId: 0,
};
const mint = await addLiquidityViaManager(addLiquidityParams);
const userPositionMint1 = (await trident.concentratedPoolManager.positions(mint.tokenId)).liquidity;
const lowerPrice = await trident.tickMath.getSqrtRatioAtTick(lower);
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const maxDx = await getDx(await pool.liquidity(), lowerPrice, currentPrice, false);
// swap back and forth
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: true,
inAmount: maxDx,
recipient: defaultAddress,
});
const ts = (await trident.concentratedPoolManager.totalSupply()).toNumber();
addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
positionId: ts,
};
await expect(addLiquidityViaManager(addLiquidityParams)).to.be.revertedWith("POOL_MIS_MATCH");
}
});
it("Should fail to burn if overflow", async () => {
const pool = trident.concentratedPools[0];
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
lower = 609332;
upper = lower + 1;
const tokens = await pool.getAssets();
await expect(
pool.connect(trident.accounts[4]).burn(lower, upper, BigNumber.from(`2`).pow(128).sub(`1`))
).to.be.revertedWith(customError("Overflow"));
});
});
});
Example #17
Source File: epochTest9.ts From ghst-staking with MIT License | 4 votes |
describe("Unstake and re-stake pools tokens", async function () {
const diamondAddress = maticStakingAddress;
before(async function () {
this.timeout(2000000000);
await upgrade();
stakingFacet = (await ethers.getContractAt(
"StakingFacet",
diamondAddress
)) as StakingFacet;
stakingFacet = await impersonate(
rateManager,
stakingFacet,
ethers,
network
);
});
it("User can unstake GHST-QUICK token balance", async function () {
stakingFacet = (await impersonate(
testAddressGhstQuick,
stakingFacet,
ethers,
network
)) as StakingFacet;
let staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstQuick);
let stakedGhstQuick = staked[1];
stakedAmountGhstQuick = stakedGhstQuick.amount;
await stakingFacet.withdrawFromPool(
stakedGhstQuick.poolAddress,
stakedGhstQuick.amount
);
staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstQuick);
stakedGhstQuick = staked[1];
expect(stakedGhstQuick.amount).to.equal(0);
});
it("User can re-stake GHST-QUICK token balance", async function () {
stakingFacet = (await impersonate(
testAddressGhstQuick,
stakingFacet,
ethers,
network
)) as StakingFacet;
let staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstQuick);
let stakedGhstQuick = staked[1];
expect(stakedGhstQuick.amount).to.equal(0);
await stakingFacet.stakeIntoPool(
stakedGhstQuick.poolAddress,
stakedAmountGhstQuick
);
staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstQuick);
stakedGhstQuick = staked[1];
expect(stakedGhstQuick.amount).to.equal(stakedAmountGhstQuick);
});
it("User can unstake GHST-USDC token balance", async function () {
stakingFacet = (await impersonate(
testAddressGhstUsdc,
stakingFacet,
ethers,
network
)) as StakingFacet;
let staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstUsdc);
let stakedGhstUsdc = staked[2];
stakedAmountGhstUsdc = stakedGhstUsdc.amount;
await stakingFacet.withdrawFromPool(
stakedGhstUsdc.poolAddress,
stakedGhstUsdc.amount
);
staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstUsdc);
stakedGhstUsdc = staked[2];
expect(stakedGhstUsdc.amount).to.equal(0);
});
it("User can re-stake GHST-USDC token balance", async function () {
stakingFacet = (await impersonate(
testAddressGhstUsdc,
stakingFacet,
ethers,
network
)) as StakingFacet;
let staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstUsdc);
let stakedGhstUsdc = staked[2];
expect(stakedGhstUsdc.amount).to.equal(0);
await stakingFacet.stakeIntoPool(
stakedGhstUsdc.poolAddress,
stakedAmountGhstUsdc
);
staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstUsdc);
stakedGhstUsdc = staked[2];
expect(stakedGhstUsdc.amount).to.equal(stakedAmountGhstUsdc);
});
it("User can unstake GHST-WETH token balance", async function () {
stakingFacet = (await impersonate(
testAddressGhstWeth,
stakingFacet,
ethers,
network
)) as StakingFacet;
let staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstWeth);
let stakedGhstWeth = staked[3];
stakedAmountGhstWeth = stakedGhstWeth.amount;
await stakingFacet.withdrawFromPool(
stakedGhstWeth.poolAddress,
stakedGhstWeth.amount
);
staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstWeth);
stakedGhstWeth = staked[3];
expect(stakedGhstWeth.amount).to.equal(0);
});
it("User can re-stake GHST-WETH token balance", async function () {
stakingFacet = (await impersonate(
testAddressGhstWeth,
stakingFacet,
ethers,
network
)) as StakingFacet;
let staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstWeth);
let stakedGhstWeth = staked[3];
expect(stakedGhstWeth.amount).to.equal(0);
await stakingFacet.stakeIntoPool(
stakedGhstWeth.poolAddress,
stakedAmountGhstWeth
);
staked = await stakingFacet.stakedInCurrentEpoch(testAddressGhstWeth);
stakedGhstWeth = staked[3];
expect(stakedGhstWeth.amount).to.equal(stakedAmountGhstWeth);
});
});
Example #18
Source File: ConcentratedLiquidityRouting.test.ts From trident with GNU General Public License v3.0 | 4 votes |
describe("Concentrated Pool Routing", async () => {
before(async () => {
trident = await Trident.Instance.init();
defaultAddress = trident.accounts[0].address;
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
afterEach(async () => {
await network.provider.send("evm_revert", [snapshotId]);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
it("swap without crossing", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
const lowerPrice = await trident.tickMath.getSqrtRatioAtTick(lower);
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const maxDx = (await getDx(await pool.liquidity(), lowerPrice, currentPrice, false)).mul(9).div(10);
const routePool = await createCLRPool(pool);
const predictedOutput = routePool.calcOutByIn(parseInt(maxDx.toString()), true);
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: true,
inAmount: maxDx,
recipient: defaultAddress,
});
const out = parseInt(swapTx.output.toString());
// console.log("0 in", maxDx.toString(), 'out', out, "pred", predictedOutput[0],
// Math.abs(out/predictedOutput[0]-1));
expect(Math.abs(out / predictedOutput.out - 1)).lessThan(1e-12);
const routePool2 = await createCLRPool(pool);
const predictedOutput2 = routePool2.calcOutByIn(parseInt(swapTx.output.toString()), false);
const swapTx2 = await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: false,
inAmount: swapTx.output,
recipient: defaultAddress,
});
const out2 = parseInt(swapTx2.output.toString());
// console.log("1 in", swapTx.output.toString(), 'out', out2, "pred", predictedOutput2[0],
// Math.abs(out2/predictedOutput2[0]-1));
expect(Math.abs(out2 / predictedOutput2.out - 1)).lessThan(1e-12);
}
});
it("swap with input exact at cross point", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(1000),
amount1Desired: getBigNumber(1000),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
const lowerPrice = await trident.tickMath.getSqrtRatioAtTick(lower);
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const maxDx = await getDx(await pool.liquidity(), lowerPrice, currentPrice, false);
const routePool = await createCLRPool(pool);
const predictedOutput = routePool.calcOutByIn(parseInt(maxDx.toString()), true);
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: true,
inAmount: maxDx,
recipient: defaultAddress,
});
const out = parseInt(swapTx.output.toString());
// console.log("0 in", maxDx.toString(), 'out', out, "pred", predictedOutput[0],
// Math.abs(out/predictedOutput[0]-1));
expect(Math.abs(out / predictedOutput.out - 1)).lessThan(1e-12);
const routePool2 = await createCLRPool(pool);
const predictedOutput2 = routePool2.calcOutByIn(parseInt(swapTx.output.toString()), false);
const swapTx2 = await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: false,
inAmount: swapTx.output,
recipient: defaultAddress,
});
const out2 = parseInt(swapTx2.output.toString());
// console.log("1 in", swapTx.output.toString(), 'out', out2, "pred", predictedOutput2[0],
// Math.abs(out2/predictedOutput2[0]-1));
expect(Math.abs(out2 / predictedOutput2.out - 1)).lessThan(1e-12);
}
});
it("Should add liquidity and swap (with crossing ticks)", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(100),
amount1Desired: getBigNumber(100),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams = helper.setTicks(lower + 3 * step, upper + 5 * step, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams = helper.setTicks(lower - 10 * step, upper + 10 * step, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
// swap accross the range and back
// â–¼ - - - - - - -> â–¼
// ----------------|xxxxxxxxxxx|-----|xxxxxxxxxx|--------
// ----|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|-----
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const upperPrice = await trident.tickMath.getSqrtRatioAtTick(upper);
const maxDy = await getDy(await pool.liquidity(), currentPrice, upperPrice, false).mul(2);
const routePool = await createCLRPool(pool);
const predictedOutput = routePool.calcOutByIn(parseInt(maxDy.toString()), false);
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy,
recipient: defaultAddress,
});
const out = parseInt(swapTx.output.toString());
// console.log("0 in", maxDy.toString(), 'out', out, "pred", predictedOutput[0],
// Math.abs(out/predictedOutput[0]-1));
expect(Math.abs(out / predictedOutput.out - 1)).lessThan(1e-12);
const routePool2 = await createCLRPool(pool);
const predictedOutput2 = routePool2.calcOutByIn(out, true);
const swapTx2 = await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: true,
inAmount: swapTx.output,
recipient: defaultAddress,
});
const out2 = parseInt(swapTx2.output.toString());
// console.log("1 in", out, 'out', out2, "pred", predictedOutput2[0],
// Math.abs(out2/predictedOutput2[0]-1));
expect(Math.abs(out2 / predictedOutput2.out - 1)).lessThan(1e-9);
}
});
it("Swap with crossing through empty space", async () => {
for (const pool of trident.concentratedPools) {
helper.reset();
const tickSpacing = (await pool.getImmutables())._tickSpacing;
const tickAtPrice = await getTickAtCurrentPrice(pool);
const nearestValidTick = tickAtPrice - (tickAtPrice % tickSpacing);
const nearestEvenValidTick =
(nearestValidTick / tickSpacing) % 2 == 0 ? nearestValidTick : nearestValidTick + tickSpacing;
let lower = nearestEvenValidTick - step;
let upper = nearestEvenValidTick + step + tickSpacing;
let addLiquidityParams = {
pool: pool,
amount0Desired: getBigNumber(100),
amount1Desired: getBigNumber(100),
native: false,
lowerOld: helper.insert(lower),
lower,
upperOld: helper.insert(upper),
upper,
positionOwner: trident.concentratedPoolManager.address,
recipient: defaultAddress,
};
await addLiquidityViaManager(addLiquidityParams);
addLiquidityParams.amount0Desired = addLiquidityParams.amount0Desired.mul(2);
addLiquidityParams.amount1Desired = addLiquidityParams.amount1Desired.mul(2);
addLiquidityParams = helper.setTicks(lower + 3 * step, upper + 3 * step, addLiquidityParams);
await addLiquidityViaManager(addLiquidityParams);
// swap accross a zero liquidity range and back
// â–¼ - - - - - - - - - -> â–¼
// ----|----|-------|xxxxxxxxxxxx|-------|xxxxxxxxxxx|-----
const currentPrice = (await pool.getPriceAndNearestTicks())._price;
const upperPrice = await trident.tickMath.getSqrtRatioAtTick(upper);
const maxDy = await getDy(await pool.liquidity(), currentPrice, upperPrice, false).mul(2);
const routePool = await createCLRPool(pool);
const predictedOutput = routePool.calcOutByIn(parseInt(maxDy.toString()), false);
const swapTx = await swapViaRouter({
pool: pool,
unwrapBento: true,
zeroForOne: false,
inAmount: maxDy,
recipient: defaultAddress,
});
const out = parseInt(swapTx.output.toString());
// console.log("0 in", maxDy.toString(), 'out', out, "pred", predictedOutput[0],
// Math.abs(out/predictedOutput[0]-1));
expect(Math.abs(out / predictedOutput.out - 1)).lessThan(1e-12);
const routePool2 = await createCLRPool(pool);
const predictedOutput2 = routePool2.calcOutByIn(out, true);
const swapTx2 = await swapViaRouter({
pool: pool,
unwrapBento: false,
zeroForOne: true,
inAmount: swapTx.output,
recipient: defaultAddress,
});
const out2 = parseInt(swapTx2.output.toString());
// console.log("1 in", out, 'out', out2, "pred", predictedOutput2[0],
// Math.abs(out2/predictedOutput2[0]-1));
expect(Math.abs(out2 / predictedOutput2.out - 1)).lessThan(1e-9);
}
});
});
Example #19
Source File: epochTest7.ts From ghst-staking with MIT License | 4 votes |
describe("More tests", async function () {
const diamondAddress = maticStakingAddress;
before(async function () {
this.timeout(2000000000);
await upgrade();
stakingFacet = (await ethers.getContractAt(
"StakingFacet",
diamondAddress
)) as StakingFacet;
ghstToken = (await ethers.getContractAt("ERC20", ghstAddress)) as ERC20;
stakingFacet = (await impersonate(
testAddress,
stakingFacet,
ethers,
network
)) as StakingFacet;
});
// it("Stake and withdraw loop GHST and GHST-QUICK", async function () {
// await network.provider.send("evm_setAutomine", [false]);
// const pools = await stakingFacet.poolRatesInEpoch("0");
// const frensBefore = await stakingFacet.frens(testAddress);
// for (let i = 0; i < 5; i++) {
// await stakingFacet.stakeIntoPool(
// pools[1].poolAddress,
// ethers.utils.parseUnits("1")
// );
// await stakingFacet.withdrawFromPool(
// pools[1].poolAddress,
// ethers.utils.parseUnits("1")
// );
// }
// const frensAfter = await stakingFacet.frens(testAddress);
// expect(frensBefore).to.equal(frensAfter);
// for (let i = 0; i < 5; i++) {
// await stakingFacet.stakeIntoPool(
// pools[0].poolAddress,
// ethers.utils.parseUnits("1")
// );
// await stakingFacet.withdrawFromPool(
// pools[0].poolAddress,
// ethers.utils.parseUnits("1")
// );
// }
// const frensAfter2 = await stakingFacet.frens(testAddress);
// expect(frensBefore).to.equal(frensAfter2);
// await network.provider.send("evm_setAutomine", [true]);
// });
it("Migrate big list of users and check frens before and after", async function () {
stakingFacet = (await impersonate(
rateManager,
stakingFacet,
ethers,
network
)) as StakingFacet;
let frensBeforeArr = [];
for (let i = 0; i < stakersList.length; i++) {
const frensBefore = await stakingFacet.frens(stakersList[i]);
frensBeforeArr.push(frensBefore);
await stakingFacet.migrateToV2([stakersList[i]]);
const frensAfter = await stakingFacet.frens(stakersList[i]);
const difference = Number(
ethers.utils.formatEther(frensAfter.sub(frensBeforeArr[i]))
);
// check that the frens difference is less than 2%
const tinyPercent =
(Number(ethers.utils.formatUnits(frensAfter)) * 0.02 +
Number(ethers.utils.formatUnits(frensBeforeArr[i])) * 0.02) /
2;
// check that the frens difference is less than 0.05%
const tiniestPercent =
(Number(ethers.utils.formatUnits(frensAfter)) * 0.0005 +
Number(ethers.utils.formatUnits(frensBeforeArr[i])) * 0.0005) /
2;
const percDiff =
(difference /
((Number(ethers.utils.formatUnits(frensAfter)) +
Number(ethers.utils.formatUnits(frensBeforeArr[i]))) /
2)) *
100;
if (difference > tiniestPercent) {
console.log("address", stakersList[i]);
console.log("frensBefore", ethers.utils.formatUnits(frensBeforeArr[i]));
console.log("frensAfter", ethers.utils.formatUnits(frensAfter));
console.log("difference", difference);
console.log("percdiff", percDiff);
}
expect(difference).to.lessThanOrEqual(tinyPercent);
}
});
it("Withdrawh funds from pool after epoch has ended, new epoch doesn't contain that pool", async function () {
stakingFacet = (await impersonate(
rateManager,
stakingFacet,
ethers,
network
)) as StakingFacet;
const staked = await stakingFacet.stakedInCurrentEpoch(testAddress2);
const ghstStaked = staked[0].amount;
await stakingFacet.migrateToV2([testAddress2]);
const pool = {
_poolAddress: testAddress2,
_poolReceiptToken: testAddress2,
_rate: "0",
_poolName: "TEST",
_poolUrl: "test",
};
const currentEpoch = await stakingFacet.currentEpoch();
await stakingFacet.updateRates(currentEpoch, [pool]);
stakingFacet = (await impersonate(
testAddress2,
stakingFacet,
ethers,
network
)) as StakingFacet;
const balanceBeforeWithdraw = await ghstToken.balanceOf(testAddress2);
await stakingFacet.withdrawFromPool(ghstAddress, ghstStaked);
const stakedAfterWithdraw = await stakingFacet.stakedInEpoch(
testAddress2,
0
);
const balanceAfterWithdraw = await ghstToken.balanceOf(testAddress2);
expect(balanceAfterWithdraw.sub(balanceBeforeWithdraw)).to.equal(
ghstStaked
);
expect(stakedAfterWithdraw[0].amount).to.equal(0);
});
});
Example #20
Source File: gotchiLendingOptimizeTest.ts From aavegotchi-contracts with MIT License | 4 votes |
describe("Testing Aavegotchi Lending", async function () {
this.timeout(300000);
const diamondAddress = "0x86935F11C86623deC8a25696E1C19a8659CbF95d";
const borrowerAddress = "0xb4473cfEeDC9a0E94612c6ce883677b63f830DB8"; // borrower should be GHST holder
const thirdParty = "0x382038b034fa8Ea64C74C81d680669bDaC4D0636";
const originalPetOperator = "0x4E59235b35d504D1372ABf67a835031F98114d64"; // original pet operator should be MATIC holder
const gotchiHolder = "0x8FEebfA4aC7AF314d90a0c17C3F91C800cFdE44B";
const gameManager = "0xa370f2ADd2A9Fba8759147995d6A0641F8d7C119";
const linkiest = "0xaCC227235cCAb6C058B76600D4EC2e86072d0813";
const diamondOwner = "0x35FE3dF776474a7B24B3B1EC6e745a830FdAd351";
const tokens = [
"0x403E967b044d4Be25170310157cB1A4Bf10bdD0f",
"0x44A6e0BE76e1D9620A7F76588e4509fE4fa8E8C8",
"0x6a3E7C3c6EF65Ee26975b12293cA1AAD7e1dAeD2",
"0x42E5E06EF5b90Fe15F853F59299Fc96259209c5C",
"0x385Eeac5cB85A38A9a07A70c73e0a3271CfB54A7",
"0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
];
const lockedAavegotchiId = 16911;
let lendingFacetWithBorrower: GotchiLendingFacet;
let lendingFacetWithGotchiOwner: GotchiLendingFacet;
let lendingFacetWithGameManager: GotchiLendingFacet;
let lendingFacetWithSirLinkiest: GotchiLendingFacet;
let lendingFacetWithDiamondOwner: GotchiLendingFacet;
let whitelistFacetWithOwner: WhitelistFacet;
let aavegotchiFacet: AavegotchiFacet;
let aavegotchiGameFacet: AavegotchiGameFacet;
let aavegotchiOwnerAddress: any;
let whitelistId: any;
before(async function () {
await upgrade();
const gotchiLendingFacet = (await ethers.getContractAt(
"GotchiLendingFacet",
diamondAddress
)) as GotchiLendingFacet;
const whitelistFacet = (await ethers.getContractAt(
"WhitelistFacet",
diamondAddress
)) as WhitelistFacet;
aavegotchiFacet = (await ethers.getContractAt(
"contracts/Aavegotchi/facets/AavegotchiFacet.sol:AavegotchiFacet",
diamondAddress
)) as AavegotchiFacet;
aavegotchiGameFacet = (await ethers.getContractAt(
"AavegotchiGameFacet",
diamondAddress
)) as AavegotchiGameFacet;
aavegotchiOwnerAddress = await aavegotchiFacet.ownerOf(lockedAavegotchiId);
lendingFacetWithDiamondOwner = await impersonate(
diamondOwner,
gotchiLendingFacet,
ethers,
network
);
lendingFacetWithBorrower = await impersonate(
borrowerAddress,
gotchiLendingFacet,
ethers,
network
);
lendingFacetWithGotchiOwner = await impersonate(
gotchiHolder,
gotchiLendingFacet,
ethers,
network
);
lendingFacetWithGameManager = await impersonate(
gameManager,
gotchiLendingFacet,
ethers,
network
);
lendingFacetWithSirLinkiest = await impersonate(
linkiest,
gotchiLendingFacet,
ethers,
network
);
whitelistFacetWithOwner = await impersonate(
aavegotchiOwnerAddress,
whitelistFacet,
ethers,
network
);
aavegotchiFacet = await impersonate(
borrowerAddress,
aavegotchiFacet,
ethers,
network
);
aavegotchiGameFacet = await impersonate(
originalPetOperator,
aavegotchiGameFacet,
ethers,
network
);
});
describe("Testing lending and whitelist", async () => {
it("Should be able to add revenue tokens to a revenue token whitelist", async () => {
// Already allowed in upgrade script
for (let i = 0; i < tokens.length; i++) {
expect(
await lendingFacetWithDiamondOwner.revenueTokenAllowed(tokens[i])
).to.be.true;
}
});
it("Should not be able to add revenue tokens to a revenue token whitelist if not owner", async () => {
await expect(
lendingFacetWithBorrower.allowRevenueTokens([diamondOwner])
).to.be.revertedWith("LibDiamond: Must be contract owner");
});
it("Should be able to remove revenue tokens from a revenue token whitelist", async () => {
await lendingFacetWithDiamondOwner.disallowRevenueTokens(tokens);
for (let i = 0; i < tokens.length; i++) {
expect(
await lendingFacetWithDiamondOwner.revenueTokenAllowed(tokens[i])
).to.be.false;
}
});
it("Should not be able to remove revenue tokens from a revenue token whitelist if not owner", async () => {
await expect(
lendingFacetWithBorrower.disallowRevenueTokens([diamondOwner])
).to.be.revertedWith("LibDiamond: Must be contract owner");
});
it("Should be able to list a gotchi if a revenue token is whitelisted", async () => {
await lendingFacetWithDiamondOwner.allowRevenueTokens(tokens);
await lendingFacetWithGotchiOwner.addGotchiLending(
11939,
0,
2591000,
[50, 50, 0],
gotchiHolder,
ethers.constants.AddressZero,
0,
tokens
);
expect(
await lendingFacetWithGotchiOwner.getGotchiLendingIdByToken(11939)
).to.be.gt(0);
});
it("Should not be able to list a gotchi if a revenue token is not whitelisted", async () => {
await lendingFacetWithDiamondOwner.disallowRevenueTokens(tokens);
await expect(
lendingFacetWithGotchiOwner.addGotchiLending(
22003,
0,
1,
[50, 50, 0],
gotchiHolder,
ethers.constants.AddressZero,
0,
tokens
)
).to.be.revertedWith("GotchiLending: Invalid revenue token address");
});
it("Diamond owner should be able to change revenue tokens of a listing", async () => {
await lendingFacetWithGameManager.emergencyChangeRevenueTokens(
[11939],
[diamondOwner]
);
const lendingInfo =
await lendingFacetWithGotchiOwner.getLendingListingInfo(11939);
expect(lendingInfo.revenueTokens).to.be.deep.equal([diamondOwner]);
});
it("Nobody but the diamond owner should be able to change revenue tokens of a listing", async () => {
await expect(
lendingFacetWithBorrower.emergencyChangeRevenueTokens(
[11939],
[diamondOwner]
)
).to.be.revertedWith("LibAppStorage: Do not have access");
});
it("Should be able to transfer ownership of a whitelist", async () => {
await whitelistFacetWithOwner.createWhitelist("yore mum", [thirdParty]);
whitelistId = await whitelistFacetWithOwner.getWhitelistsLength();
await whitelistFacetWithOwner.transferOwnershipOfWhitelist(
whitelistId,
ethers.constants.AddressZero
);
expect(
await whitelistFacetWithOwner.whitelistOwner(whitelistId)
).to.equal(ethers.constants.AddressZero);
});
it("Should not be able to transfer ownership of a whitelist if not the owner", async () => {
await expect(
whitelistFacetWithOwner.transferOwnershipOfWhitelist(
1,
ethers.constants.AddressZero
)
).to.be.revertedWith("WhitelistFacet: Not whitelist owner");
});
it("Shouldn't be able to list with a period of more than 30 days", async () => {
await expect(
lendingFacetWithGotchiOwner.addGotchiLending(
22003,
0,
2_600_000,
[50, 50, 0],
gotchiHolder,
ethers.constants.AddressZero,
0,
[]
)
).to.be.revertedWith("GotchiLending: Period too long");
});
it("Setup for next tests", async () => {
let lendingIds: [BigNumberish, BigNumberish] = [
await lendingFacetWithGotchiOwner.getGotchiLendingIdByToken(11939),
await lendingFacetWithGotchiOwner.getGotchiLendingIdByToken(22003),
];
await lendingFacetWithBorrower.agreeGotchiLending(
lendingIds[0],
11939,
0,
2591000,
[50, 50, 0]
);
});
it("Lender cannot end the term early", async () => {
await expect(
lendingFacetWithGotchiOwner.claimAndEndGotchiLending(11939)
).to.be.revertedWith("GotchiLending: Not allowed during agreement");
});
it("Borrower should be able to end lending early", async () => {
await lendingFacetWithBorrower.claimAndEndGotchiLending(11939);
expect(await lendingFacetWithBorrower.isAavegotchiLent(22003)).to.be
.false;
});
it("Lender should be able to end a long existing borrow early", async () => {
await network.provider.send("evm_increaseTime", [2592000]);
await network.provider.send("evm_mine");
await lendingFacetWithSirLinkiest.claimAndEndGotchiLending(13835);
expect(await lendingFacetWithSirLinkiest.isAavegotchiLent(13835)).to.be
.false;
});
});
});
Example #21
Source File: main.ts From pawnft with GNU General Public License v3.0 | 4 votes |
describe("PawnBank", () => {
// Pre-setup
beforeEach(async () => {
// Reset hardhat forknet
await network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
blockNumber: 12864983,
},
},
],
});
// Deploy contract
await deploy();
// Scaffold initial loan
await scaffoldLoan();
});
describe("Loan creation", () => {
it("Should allow creating a loan", async () => {
// Collect details
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
const SquiggleOGOwner: string = (await PawnBankContract.pawnLoans(1)).tokenOwner;
// Verify that contract holds NFT
expect(SquiggleOwner).to.equal(PawnBankContractAddress);
// Verify that PawnLoan is created with correct owner
expect(SquiggleOGOwner).to.equal(ADDRESSES.SNOWFRO);
});
it("Should prevent creating a loan in the past", async () => {
const { SnowfroBank } = await impersonateBanks();
// Force delete scaffold loan
await SnowfroBank.cancelLoan(1);
// Approve NFT for transfer
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
await SquigglesContract.approve(PawnBankContractAddress, SQUIGGLE_0);
// Create loan w/ Chrome Squiggle #0 in past
const tx: Transaction = SnowfroBank.createLoan(
// Token address
ADDRESSES.SQUIGGLE,
// Token ID
SQUIGGLE_0,
// Interest rate
5,
// Max loan amount
ethers.utils.parseEther("10"),
// Loan completion time (5 years ago)
Math.floor(Date.now() / 1000) - 157680000
);
// Expect tx to revert because loan completion time < current time
await expect(tx).revertedWith(ERROR_MESSAGES.CREATE.NO_EXPIRED_LOAN);
});
});
describe("Loan underwriting", () => {
it("Should allow underwriting a new loan", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Collect details
const PawnBankBalance: BigNumber = await waffle.provider.getBalance(PawnBankContractAddress);
const PawnLoanDetails = await LenderOneBank.pawnLoans(1);
// Verify contract balance is increased
expect(PawnBankBalance.toString()).to.equal("1000000000000000000");
// Verify loan has 1 ether available to draw
expect(PawnLoanDetails.loanAmount.toString()).to.equal("1000000000000000000");
// Verify loan has 0 capital drawn
expect(PawnLoanDetails.loanAmountDrawn.toString()).to.equal("0");
// Verify new lender is Lender One
expect(PawnLoanDetails.lender).to.equal(ADDRESSES.LENDER_ONE);
});
it("Should prevent underwriting a new loan with 0 Ether", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 0 ETH
const tx: Transaction = LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("0.0"),
});
// Expect tx to fail
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.NO_0_UNDERWRITE);
});
it("Should prevent underwriting a repaid loan", async () => {
const { SnowfroBank, LenderOneBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Draw and repay loan
await SnowfroBank.drawLoan(1);
const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
await SnowfroBank.repayLoan(1, { value: repaymentAmount.mul(101).div(100) });
// Attempt to re-underwrite loan
const tx: Transaction = LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.1"),
});
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.ALREADY_REPAID);
});
it("Should prevent underwriting an expired loan", async () => {
const { LenderOneBank } = await impersonateBanks();
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Back loan with 1 ETH
const tx: Transaction = LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.EXPIRED);
});
it("Should prevent underwriting a loan under the current top bid", async () => {
const { LenderOneBank, LenderTwoBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Back loan with <1 ETH
const tx: Transaction = LenderTwoBank.underwriteLoan(1, {
value: ethers.utils.parseEther("0.5"),
});
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.INSUFFICIENT_BID);
});
it("Should prevent underwriting a loan over the maxLoanAmount", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 11 ETH
const tx: Transaction = LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("11.0"),
});
// Expect tx to fail
await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.OVER_MAX_UNDERWRITE);
});
it("Should allow underwriting a loan with existing bid", async () => {
const { LenderOneBank, LenderTwoBank } = await impersonateBanks();
// First lender balance
const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);
// Back loan with 5 ETH
const tx = await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
const receipt = await waffle.provider.getTransactionReceipt(tx.hash);
// Back loan with 6 ETH
const totalInterest: BigNumber = await LenderTwoBank.calculateTotalInterest(1, 1);
const higherLoan: BigNumber = ethers.utils.parseEther("6.0").add(totalInterest);
await LenderTwoBank.underwriteLoan(1, { value: higherLoan });
// Collect details
const NewTopLender: string = (await LenderTwoBank.pawnLoans(1)).lender;
const expectedLenderOneBalance: BigNumber = previousBalance
.sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
.add(totalInterest);
const acutalLenderOneBalance: BigNumber = await waffle.provider.getBalance(
ADDRESSES.LENDER_ONE
);
// Verify first lender received principle + interest
expect(acutalLenderOneBalance).to.be.gte(expectedLenderOneBalance);
// Verify second lender is now top bidder
expect(NewTopLender).to.equal(ADDRESSES.LENDER_TWO);
});
});
describe("Loan drawing", () => {
it("Should allow drawing from a loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Collect previous Snowfro balance
const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
// Draw 5ETH
const tx = await SnowfroBank.drawLoan(1);
const receipt = await waffle.provider.getTransactionReceipt(tx.hash);
// Collect after Snowfro balance
const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
const expectedAfterBalance: BigNumber = previousBalance
.sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
.add(ethers.utils.parseEther("5.0"));
expect(afterBalance).to.equal(expectedAfterBalance);
});
it("Should allow drawing additional capital from a new bid", async () => {
const { LenderOneBank, LenderTwoBank, SnowfroBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Collect previous Snowfro balance
const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
// Draw 5ETH
const tx = await SnowfroBank.drawLoan(1);
const receipt = await waffle.provider.getTransactionReceipt(tx.hash);
// Check for Snowfro balance increment
const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
const expectedAfterBalance: BigNumber = previousBalance
.sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
.add(ethers.utils.parseEther("5.0"));
expect(afterBalance).to.equal(expectedAfterBalance);
// Back loan with 6 ETH
const totalInterest: BigNumber = await LenderTwoBank.calculateTotalInterest(1, 5);
const higherLoan: BigNumber = ethers.utils.parseEther("6.0").add(totalInterest);
await LenderTwoBank.underwriteLoan(1, { value: higherLoan });
// Draw 6ETH
const tx2 = await SnowfroBank.drawLoan(1);
const receipt2 = await waffle.provider.getTransactionReceipt(tx2.hash);
// Check for Snowfro balance increment
const afterBalance2: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
const expectedAfterBalance2: BigNumber = afterBalance
.sub(tx2.gasPrice.mul(receipt2.cumulativeGasUsed))
.add(ethers.utils.parseEther("1.0"));
expect(afterBalance2).to.be.gte(expectedAfterBalance2);
});
it("Should prevent drawing from a loan with no bids", async () => {
const { SnowfroBank } = await impersonateBanks();
// Attempt to draw ETH
const tx: Transaction = SnowfroBank.drawLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.MAX_CAPACITY);
});
it("Should prevent non-owners from drawing from loan", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Attempt to collect loan as Lender
const tx: Transaction = LenderOneBank.drawLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.NOT_OWNER);
});
it("Should prevent consecutive draws from a loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Draw 5ETH
await SnowfroBank.drawLoan(1);
// Attempt to redraw
const tx: Transaction = SnowfroBank.drawLoan(1);
await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.MAX_CAPACITY);
});
it("Should allow drawing loan after NFT seizure", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Seize NFT
await LenderOneBank.seizeNFT(1);
// Collect previous Snowfro balance
const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
// Draw 5ETH
const tx = await SnowfroBank.drawLoan(1);
const receipt = await waffle.provider.getTransactionReceipt(tx.hash);
// Collect after Snowfro balance
const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
const expectedAfterBalance: BigNumber = previousBalance
.sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
.add(ethers.utils.parseEther("5.0"));
// Expect increase in balance
expect(afterBalance).to.equal(expectedAfterBalance);
});
});
describe("Loan repayment", () => {
it("Should allow repaying loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Record Lender balance
const previousLenderBalance: BigNumber = await waffle.provider.getBalance(
ADDRESSES.LENDER_ONE
);
// Fast forward <1h
await network.provider.send("evm_increaseTime", [3500]);
await network.provider.send("evm_mine");
// Calculate repayment amount
const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);
// Repay loan
await SnowfroBank.repayLoan(1, { value: repaymentBuffer });
// Expect loan to be closed
const NewLoanOwner: string = (await SnowfroBank.pawnLoans(1)).tokenOwner;
expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);
// Expect NFT to be owned by original owner
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);
// Expect increase in Lender One balance by ~.243 ETH
const afterLenderBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);
expect(afterLenderBalance).to.be.gte(previousLenderBalance.add(repaymentAmount));
});
it("Should allow repaying someone elses loan", async () => {
const { LenderOneBank, LenderTwoBank } = await impersonateBanks();
// Back loan with 5 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("5.0"),
});
// Record Lender balance
const previousLenderBalance: BigNumber = await waffle.provider.getBalance(
ADDRESSES.LENDER_ONE
);
// Fast forward <1h
await network.provider.send("evm_increaseTime", [3500]);
await network.provider.send("evm_mine");
// Calculate repayment amount
const repaymentAmount: BigNumber = await LenderTwoBank.calculateRequiredRepayment(1, 0);
const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);
// Repay loan
await LenderTwoBank.repayLoan(1, { value: repaymentBuffer });
// Expect loan to be closed
const NewLoanOwner: string = (await LenderTwoBank.pawnLoans(1)).tokenOwner;
expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);
// Expect NFT to be owned by original owner
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);
// Expect increase in Lender One balance by ~.243 ETH
const afterLenderBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);
expect(afterLenderBalance).to.be.gte(previousLenderBalance.add(repaymentAmount));
});
it("Should prevent repaying loan w/ 0 bids", async () => {
const { SnowfroBank } = await impersonateBanks();
// Attempt to repay loan
const tx: Transaction = SnowfroBank.repayLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.NO_BIDS);
});
it("Should prevent repaying expired loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Attempt to repay loan
const tx: Transaction = SnowfroBank.repayLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.EXPIRED);
});
it("Should prevent repaying paid loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Fast forward <1h
await network.provider.send("evm_increaseTime", [3500]);
await network.provider.send("evm_mine");
// Calculate repayment amount
const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);
// Repay loan
await SnowfroBank.repayLoan(1, { value: repaymentBuffer });
// Attempt to re-repay loan
const tx: Transaction = SnowfroBank.repayLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.ALREADY_REPAID);
});
});
describe("Loan cancellation", () => {
it("Should allow cancelling loan with 0 bids", async () => {
const { SnowfroBank } = await impersonateBanks();
// Cancel loan
await SnowfroBank.cancelLoan(1);
// Collect details
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
const NewLoanOwner: string = (await SnowfroBank.pawnLoans(1)).tokenOwner;
// Verify that Snowfro holds NFT
expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);
// Verify that PawnLoan is nullifed w/ 0 address
expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);
});
it("Should prevent cancelling loan with >0 bids", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Attempt to cancel loan with 1.0 bid existing
const tx: Transaction = SnowfroBank.cancelLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.CANCEL.NON_ZERO_BIDS);
});
it("Should prevent cancelling loan if not owner", async () => {
const { LenderOneBank } = await impersonateBanks();
// Attempt to cancel loan as Lender One
const tx: Transaction = LenderOneBank.cancelLoan(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.CANCEL.NOT_OWNER);
});
});
describe("Loan seizing", () => {
it("Should allow lender to seize NFT", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Seize NFT
await LenderOneBank.seizeNFT(1);
// Collect details
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
// Verify that Lender One holds NFT
expect(SquiggleOwner).to.equal(ADDRESSES.LENDER_ONE);
});
it("Should allow anyone to seize NFT on behalf of lender", async () => {
const { LenderOneBank, LenderTwoBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Fast forward >1h
await network.provider.send("evm_increaseTime", [3601]);
await network.provider.send("evm_mine");
// Seize NFT
await LenderTwoBank.seizeNFT(1);
// Collect details
const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
// Verify that Lender One holds NFT
expect(SquiggleOwner).to.equal(ADDRESSES.LENDER_ONE);
});
it("Should prevent seizing NFT before loan expiration", async () => {
const { LenderOneBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Seize NFT
const tx: Transaction = LenderOneBank.seizeNFT(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.SEIZE.NOT_EXPIRED);
});
it("Should prevent seizing repaid loan", async () => {
const { LenderOneBank, SnowfroBank } = await impersonateBanks();
// Back loan with 1 ETH
await LenderOneBank.underwriteLoan(1, {
value: ethers.utils.parseEther("1.0"),
});
// Draw and repay loan
await SnowfroBank.drawLoan(1);
const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
await SnowfroBank.repayLoan(1, { value: repaymentAmount.mul(101).div(100) });
// Seize NFT
const tx: Transaction = LenderOneBank.seizeNFT(1);
// Expect revert
await expect(tx).revertedWith(ERROR_MESSAGES.SEIZE.ALREADY_REPAID);
});
});
});
Example #22
Source File: exchangeIssuanceZeroEx.spec.ts From index-coop-smart-contracts with Apache License 2.0 | 4 votes |
describe("ExchangeIssuanceZeroEx", async () => {
let owner: Account;
let user: Account;
let externalPositionModule: Account;
let setV2Setup: SetFixture;
let zeroExMock: ZeroExExchangeProxyMock;
let deployer: DeployHelper;
let setToken: SetToken;
let wbtc: StandardTokenMock;
let dai: StandardTokenMock;
let usdc: StandardTokenMock;
let weth: WETH9;
let usdcUnits: BigNumber;
let wbtcUnits: BigNumber;
cacheBeforeEach(async () => {
[owner, user, externalPositionModule] = await getAccounts();
deployer = new DeployHelper(owner.wallet);
setV2Setup = getSetFixture(owner.address);
await setV2Setup.initialize();
dai = setV2Setup.dai;
wbtc = setV2Setup.wbtc;
weth = setV2Setup.weth;
usdc = setV2Setup.usdc;
zeroExMock = await deployer.mocks.deployZeroExExchangeProxyMock();
usdcUnits = UnitsUtils.usdc(1.234567);
wbtcUnits = UnitsUtils.wbtc(1.2345678);
setToken = await setV2Setup.createSetToken(
[setV2Setup.usdc.address, setV2Setup.wbtc.address],
[usdcUnits, wbtcUnits],
[
setV2Setup.debtIssuanceModule.address,
setV2Setup.issuanceModule.address,
setV2Setup.streamingFeeModule.address,
],
);
await setV2Setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO);
await setV2Setup.debtIssuanceModule.initialize(
setToken.address,
ether(1),
ZERO,
ZERO,
owner.address,
ADDRESS_ZERO,
);
});
describe("#constructor", async () => {
let subjectWethAddress: Address;
let subjectControllerAddress: Address;
let subjectSwapTarget: Address;
cacheBeforeEach(async () => {
subjectWethAddress = weth.address;
subjectControllerAddress = setV2Setup.controller.address;
subjectSwapTarget = zeroExMock.address;
});
async function subject(): Promise<ExchangeIssuanceZeroEx> {
return await deployer.extensions.deployExchangeIssuanceZeroEx(
subjectWethAddress,
subjectControllerAddress,
subjectSwapTarget,
);
}
it("verify state set properly via constructor", async () => {
const exchangeIssuanceContract: ExchangeIssuanceZeroEx = await subject();
const expectedWethAddress = await exchangeIssuanceContract.WETH();
expect(expectedWethAddress).to.eq(subjectWethAddress);
const expectedControllerAddress = await exchangeIssuanceContract.setController();
expect(expectedControllerAddress).to.eq(subjectControllerAddress);
const swapTarget = await exchangeIssuanceContract.swapTarget();
expect(swapTarget).to.eq(subjectSwapTarget);
});
});
context("when exchange issuance is deployed", async () => {
let wethAddress: Address;
let controllerAddress: Address;
let exchangeIssuanceZeroEx: ExchangeIssuanceZeroEx;
let setTokenExternal: SetToken;
cacheBeforeEach(async () => {
setTokenExternal = await setV2Setup.createSetToken(
[setV2Setup.dai.address],
[ether(0.5)],
[setV2Setup.issuanceModule.address, setV2Setup.streamingFeeModule.address],
);
await setV2Setup.issuanceModule.initialize(setTokenExternal.address, ADDRESS_ZERO);
const controller = setV2Setup.controller;
await controller.addModule(externalPositionModule.address);
await setTokenExternal.addModule(externalPositionModule.address);
await setTokenExternal.connect(externalPositionModule.wallet).initializeModule();
await setTokenExternal
.connect(externalPositionModule.wallet)
.addExternalPositionModule(dai.address, externalPositionModule.address);
wethAddress = weth.address;
controllerAddress = setV2Setup.controller.address;
exchangeIssuanceZeroEx = await deployer.extensions.deployExchangeIssuanceZeroEx(
wethAddress,
controllerAddress,
zeroExMock.address,
);
});
describe("#withdrawTokens()", async () => {
let subjectTokens: Address[];
let erc20Amounts: BigNumber[];
let ethAmount: BigNumber;
let erc20Tokens: StandardTokenMock[];
let subjectReceiver: Address;
let caller: Account;
beforeEach(async () => {
erc20Tokens = [dai, wbtc, usdc];
erc20Amounts = await Promise.all(erc20Tokens.map(t => t.balanceOf(owner.address)));
await Promise.all(
erc20Tokens.map(t =>
t
.connect(owner.wallet)
.transfer(exchangeIssuanceZeroEx.address, erc20Amounts[erc20Tokens.indexOf(t)]),
),
);
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [weth.address],
});
const wethSigner = ethers.provider.getSigner(weth.address);
ethAmount = (await ethers.provider.getBalance(owner.address)).div(10);
await owner.wallet.sendTransaction({ to: weth.address, value: ethAmount });
wethSigner.sendTransaction({ to: exchangeIssuanceZeroEx.address, value: ethAmount });
subjectReceiver = user.address;
subjectTokens = [
await exchangeIssuanceZeroEx.ETH_ADDRESS(),
...erc20Tokens.map(t => t.address),
];
caller = owner;
});
async function subject() {
return exchangeIssuanceZeroEx
.connect(caller.wallet)
.withdrawTokens(subjectTokens, subjectReceiver);
}
it("should succeed", async () => {
await subject();
});
it("should send erc20 amounts to receiver", async () => {
const balancesBefore = await Promise.all(
erc20Tokens.map(token => token.balanceOf(subjectReceiver)),
);
await subject();
const balancesAfter = await Promise.all(
erc20Tokens.map(token => token.balanceOf(subjectReceiver)),
);
for (let i = 0; i < balancesBefore.length; i++) {
expect(balancesAfter[i]).to.eq(balancesBefore[i].add(erc20Amounts[i]));
}
});
it("should send ether to receiver", async () => {
const balanceBefore = await ethers.provider.getBalance(subjectReceiver);
await subject();
const balanceAfter = await ethers.provider.getBalance(subjectReceiver);
expect(balanceAfter).to.eq(balanceBefore.add(ethAmount));
});
context("when the caller is not the owner", async () => {
beforeEach(async () => {
caller = user;
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("Ownable: caller is not the owner");
});
});
});
["basicIssuanceModule", "debtIssuanceModule"].forEach((issuanceModuleName: string) => {
context(`when issuance module is ${issuanceModuleName}`, () => {
let issuanceModuleAddress: Address;
let issuanceModule: any;
before(() => {
issuanceModule =
issuanceModuleName === "basicIssuanceModule"
? setV2Setup.issuanceModule
: setV2Setup.debtIssuanceModule;
issuanceModuleAddress = issuanceModule.address;
});
describe("#approveSetToken", async () => {
let subjectSetToApprove: SetToken | StandardTokenMock;
beforeEach(async () => {
subjectSetToApprove = setToken;
});
async function subject(): Promise<ContractTransaction> {
return await exchangeIssuanceZeroEx.approveSetToken(
subjectSetToApprove.address,
issuanceModuleAddress,
);
}
it("should update the approvals correctly", async () => {
const tokens = [dai, dai];
const spenders = [issuanceModuleAddress];
await subject();
const finalAllowances = await getAllowances(
tokens,
exchangeIssuanceZeroEx.address,
spenders,
);
for (let i = 0; i < finalAllowances.length; i++) {
const actualAllowance = finalAllowances[i];
const expectedAllowance = MAX_UINT_96;
expect(actualAllowance).to.eq(expectedAllowance);
}
});
context("when the input token is not a set", async () => {
beforeEach(async () => {
subjectSetToApprove = dai;
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith(
"function selector was not recognized and there's no fallback function",
);
});
});
});
describe("#receive", async () => {
let subjectCaller: Account;
let subjectAmount: BigNumber;
beforeEach(async () => {
subjectCaller = user;
subjectAmount = ether(10);
});
async function subject(): Promise<String> {
return subjectCaller.wallet.call({
to: exchangeIssuanceZeroEx.address,
value: subjectAmount,
});
}
it("should revert when receiving ether not from the WETH contract", async () => {
await expect(subject()).to.be.revertedWith(
"ExchangeIssuance: Direct deposits not allowed",
);
});
});
describe("#approveTokens", async () => {
let subjectTokensToApprove: (StandardTokenMock | WETH9)[];
let subjectSpender: Address;
beforeEach(async () => {
subjectTokensToApprove = [setV2Setup.weth, setV2Setup.wbtc];
subjectSpender = issuanceModuleAddress;
});
async function subject() {
return await exchangeIssuanceZeroEx.approveTokens(
subjectTokensToApprove.map(token => token.address),
subjectSpender,
);
}
it("should update the approvals correctly", async () => {
const spenders = [zeroExMock.address, issuanceModuleAddress];
await subject();
const finalAllowances = await getAllowances(
subjectTokensToApprove,
exchangeIssuanceZeroEx.address,
spenders,
);
for (let i = 0; i < finalAllowances.length; i++) {
const actualAllowance = finalAllowances[i];
const expectedAllowance = MAX_UINT_96;
expect(actualAllowance).to.eq(expectedAllowance);
}
});
context("when the tokens are approved twice", async () => {
it("should update the approvals correctly", async () => {
const spenders = [zeroExMock.address, issuanceModuleAddress];
const tx = await subject();
await tx.wait();
await subject();
const finalAllowances = await getAllowances(
subjectTokensToApprove,
exchangeIssuanceZeroEx.address,
spenders,
);
for (let i = 0; i < finalAllowances.length; i++) {
const actualAllowance = finalAllowances[i];
const expectedAllowance = MAX_UINT_96;
expect(actualAllowance).to.eq(expectedAllowance);
}
});
});
context("when the spender address is not a whitelisted issuance module", async () => {
beforeEach(() => {
subjectSpender = user.address;
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith(
"ExchangeIssuance: INVALID ISSUANCE MODULE",
);
});
});
});
// Helper function to generate 0xAPI quote for UniswapV2
const getUniswapV2Quote = (
sellToken: Address,
sellAmount: BigNumber,
buyToken: Address,
minBuyAmount: BigNumber,
): string => {
const isSushi = false;
return zeroExMock.interface.encodeFunctionData("sellToUniswap", [
[sellToken, buyToken],
sellAmount,
minBuyAmount,
isSushi,
]);
};
describe("#issueExactSetFromToken", async () => {
let subjectCaller: Account;
let subjectSetToken: SetToken;
let subjectInputToken: StandardTokenMock | WETH9;
let subjectInputTokenAmount: BigNumber;
let subjectAmountSetToken: number;
let subjectAmountSetTokenWei: BigNumber;
let subjectPositionSwapQuotes: string[];
let subjectIssuanceModuleAddress: Address;
let subjectIsDebtIssuance: boolean;
let components: Address[];
let positions: BigNumber[];
const initializeSubjectVariables = async () => {
subjectCaller = user;
subjectSetToken = setToken;
subjectInputTokenAmount = ether(1000);
subjectInputToken = dai;
subjectAmountSetToken = 1.123456789123456;
subjectAmountSetTokenWei = UnitsUtils.ether(subjectAmountSetToken);
subjectIssuanceModuleAddress = issuanceModuleAddress;
subjectIsDebtIssuance = issuanceModuleAddress == setV2Setup.debtIssuanceModule.address;
[components, positions] = await exchangeIssuanceZeroEx.getRequiredIssuanceComponents(
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
subjectSetToken.address,
subjectAmountSetTokenWei,
);
subjectPositionSwapQuotes = positions.map((position: any, index: number) => {
return getUniswapV2Quote(
subjectInputToken.address,
subjectInputTokenAmount.div(2),
components[index],
position,
);
});
};
beforeEach(async () => {
initializeSubjectVariables();
await exchangeIssuanceZeroEx.approveSetToken(
subjectSetToken.address,
subjectIssuanceModuleAddress,
);
dai.connect(subjectCaller.wallet).approve(exchangeIssuanceZeroEx.address, MAX_UINT_256);
await dai.transfer(subjectCaller.address, subjectInputTokenAmount);
positions.forEach(async (position: any, index: number) => {
const component = components[index];
await usdc.attach(component).transfer(zeroExMock.address, position);
});
});
async function subject(): Promise<ContractTransaction> {
return await exchangeIssuanceZeroEx
.connect(subjectCaller.wallet)
.issueExactSetFromToken(
subjectSetToken.address,
subjectInputToken.address,
subjectAmountSetTokenWei,
subjectInputTokenAmount,
subjectPositionSwapQuotes,
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
);
}
it("should issue correct amount of set tokens", async () => {
const initialBalanceOfSet = await subjectSetToken.balanceOf(subjectCaller.address);
await subject();
const finalSetBalance = await subjectSetToken.balanceOf(subjectCaller.address);
const expectedSetBalance = initialBalanceOfSet.add(subjectAmountSetTokenWei);
expect(finalSetBalance).to.eq(expectedSetBalance);
});
it("should use correct amount of input tokens", async () => {
const initialBalanceOfInput = await subjectInputToken.balanceOf(subjectCaller.address);
await subject();
const finalInputBalance = await subjectInputToken.balanceOf(subjectCaller.address);
const expectedInputBalance = initialBalanceOfInput.sub(subjectInputTokenAmount);
expect(finalInputBalance).to.eq(expectedInputBalance);
});
context("when the input swap does not use all of the input token amount", async () => {
const sellMultiplier = 0.5;
beforeEach(async () => {
await zeroExMock.setSellMultiplier(subjectInputToken.address, ether(sellMultiplier));
});
it("should return surplus input token to user", async () => {
const inputTokenBalanceBefore = await subjectInputToken.balanceOf(
subjectCaller.address,
);
await subject();
const inputTokenBalanceAfter = await subjectInputToken.balanceOf(
subjectCaller.address,
);
const expectedInputTokenBalance = inputTokenBalanceBefore.sub(
subjectInputTokenAmount.div(1 / sellMultiplier),
);
expect(inputTokenBalanceAfter).to.equal(expectedInputTokenBalance);
});
});
context("when the input token is also a component", async () => {
beforeEach(async () => {
subjectInputToken = wbtc;
subjectAmountSetTokenWei = UnitsUtils.ether(1);
subjectInputTokenAmount = wbtcUnits.mul(2);
subjectPositionSwapQuotes = positions.map((position: any, index: number) => {
return getUniswapV2Quote(
subjectInputToken.address,
subjectInputTokenAmount.div(2),
components[index],
position,
);
});
await wbtc
.connect(user.wallet)
.approve(exchangeIssuanceZeroEx.address, subjectInputTokenAmount);
await wbtc.transfer(user.address, subjectInputTokenAmount);
});
it("should issue correct amount of set tokens", async () => {
const initialBalanceOfSet = await subjectSetToken.balanceOf(subjectCaller.address);
await subject();
const finalSetBalance = await subjectSetToken.balanceOf(subjectCaller.address);
const expectedSetBalance = initialBalanceOfSet.add(subjectAmountSetTokenWei);
expect(finalSetBalance).to.eq(expectedSetBalance);
});
it("should use correct amount of input tokens", async () => {
const initialBalanceOfInput = await subjectInputToken.balanceOf(
subjectCaller.address,
);
await subject();
const finalInputBalance = await subjectInputToken.balanceOf(subjectCaller.address);
const expectedInputBalance = initialBalanceOfInput.sub(subjectInputTokenAmount);
expect(finalInputBalance).to.eq(expectedInputBalance);
});
});
context("when an invalid issuance module address is provided", async () => {
beforeEach(async () => {
subjectIssuanceModuleAddress = subjectCaller.address;
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith(
"ExchangeIssuance: INVALID ISSUANCE MODULE",
);
});
});
context("when a position quote is missing", async () => {
beforeEach(async () => {
subjectPositionSwapQuotes = [subjectPositionSwapQuotes[0]];
});
it("should revert", async () => {
await expect(subject()).to.be.reverted;
});
});
context("when invalid set token amount is requested", async () => {
beforeEach(async () => {
subjectAmountSetTokenWei = ether(0);
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("Issue quantity must be > 0");
});
});
context("when a component swap spends too much input token", async () => {
beforeEach(async () => {
// Simulate left over weth balance left in contract
await subjectInputToken.transfer(
exchangeIssuanceZeroEx.address,
subjectInputTokenAmount,
);
await zeroExMock.setSellMultiplier(subjectInputToken.address, ether(2));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("ExchangeIssuance: OVERSPENT TOKEN");
});
});
context("When the zero ex router call fails with normal revert errror", async () => {
beforeEach(async () => {
await zeroExMock.setErrorMapping(subjectInputToken.address, 1);
});
it("should forward revert reason correctly", async () => {
const errorMessage = await zeroExMock.testRevertMessage();
await expect(subject()).to.be.revertedWith(errorMessage);
});
});
context("when a component swap yields insufficient component token", async () => {
beforeEach(async () => {
// Simulating left over component balance left in contract
await wbtc.transfer(exchangeIssuanceZeroEx.address, wbtcUnits);
await zeroExMock.setBuyMultiplier(wbtc.address, ether(0.5));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("ExchangeIssuance: UNDERBOUGHT COMPONENT");
});
});
context("when a swap call fails", async () => {
beforeEach(async () => {
// Trigger revertion in mock by trying to return more buy tokens than available in balance
await zeroExMock.setBuyMultiplier(wbtc.address, ether(100));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("ERC20: transfer amount exceeds balance");
});
});
});
describe("#issueExactSetFromETH", async () => {
let subjectCaller: Account;
let subjectSetToken: SetToken;
let subjectAmountETHInput: BigNumber;
let subjectAmountSetToken: number;
let subjectAmountSetTokenWei: BigNumber;
let subjectPositionSwapQuotes: string[];
let subjectIssuanceModuleAddress: Address;
let subjectIsDebtIssuance: boolean;
let components: Address[];
let positions: BigNumber[];
const initializeSubjectVariables = async () => {
subjectCaller = user;
subjectSetToken = setToken;
subjectAmountETHInput = ether(4);
subjectAmountSetToken = 1.23456789;
subjectAmountSetTokenWei = UnitsUtils.ether(subjectAmountSetToken);
subjectIssuanceModuleAddress = issuanceModuleAddress;
subjectIsDebtIssuance = issuanceModuleAddress == setV2Setup.debtIssuanceModule.address;
[
components,
positions,
] = await exchangeIssuanceZeroEx.callStatic.getRequiredIssuanceComponents(
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
subjectSetToken.address,
subjectAmountSetTokenWei,
);
subjectPositionSwapQuotes = positions.map((position: any, index: number) => {
return getUniswapV2Quote(
weth.address,
subjectAmountETHInput.div(2),
components[index],
position,
);
});
};
beforeEach(async () => {
initializeSubjectVariables();
await exchangeIssuanceZeroEx.approveSetToken(
subjectSetToken.address,
issuanceModuleAddress,
);
await weth.transfer(subjectCaller.address, subjectAmountETHInput);
positions.forEach(async (position: any, index: number) => {
const component = components[index];
await usdc.attach(component).transfer(zeroExMock.address, position);
});
});
async function subject(): Promise<ContractTransaction> {
return await exchangeIssuanceZeroEx
.connect(subjectCaller.wallet)
.issueExactSetFromETH(
subjectSetToken.address,
subjectAmountSetTokenWei,
subjectPositionSwapQuotes,
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
{ value: subjectAmountETHInput },
);
}
it("should issue the correct amount of Set to the caller", async () => {
const initialBalanceOfSet = await subjectSetToken.balanceOf(subjectCaller.address);
await subject();
const finalBalanceOfSet = await subjectSetToken.balanceOf(subjectCaller.address);
const expectedBalance = initialBalanceOfSet.add(subjectAmountSetTokenWei);
expect(finalBalanceOfSet).to.eq(expectedBalance);
});
it("should use the correct amount of ether from the caller", async () => {
const initialBalanceOfEth = await subjectCaller.wallet.getBalance();
const tx = await subject();
const transactionFee = await getTxFee(tx);
const finalEthBalance = await subjectCaller.wallet.getBalance();
const expectedEthBalance = initialBalanceOfEth
.sub(subjectAmountETHInput)
.sub(transactionFee);
expect(finalEthBalance).to.eq(expectedEthBalance);
});
it("emits an ExchangeIssue log", async () => {
await expect(subject())
.to.emit(exchangeIssuanceZeroEx, "ExchangeIssue")
.withArgs(
subjectCaller.address,
subjectSetToken.address,
ETH_ADDRESS,
subjectAmountETHInput,
subjectAmountSetTokenWei,
);
});
context("when not all eth is used up in the transaction", async () => {
const shareSpent = 0.5;
beforeEach(async () => {
await zeroExMock.setSellMultiplier(weth.address, ether(shareSpent));
});
it("should return excess eth to the caller", async () => {
const initialBalanceOfEth = await subjectCaller.wallet.getBalance();
const tx = await subject();
const transactionFee = await getTxFee(tx);
const finalEthBalance = await subjectCaller.wallet.getBalance();
const expectedEthBalance = initialBalanceOfEth
.sub(subjectAmountETHInput.div(1 / shareSpent))
.sub(transactionFee);
expect(finalEthBalance).to.eq(expectedEthBalance);
});
});
context("when too much eth is used", async () => {
beforeEach(async () => {
await weth.transfer(exchangeIssuanceZeroEx.address, subjectAmountETHInput);
await zeroExMock.setSellMultiplier(wbtc.address, ether(2));
await zeroExMock.setSellMultiplier(weth.address, ether(2));
await zeroExMock.setSellMultiplier(dai.address, ether(2));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("ExchangeIssuance: OVERSPENT ETH");
});
});
context("when wrong number of component quotes are used", async () => {
beforeEach(async () => {
subjectPositionSwapQuotes = [];
});
it("should revert", async () => {
await expect(subject()).to.be.reverted;
});
});
context("when input ether amount is 0", async () => {
beforeEach(async () => {
subjectAmountETHInput = ZERO;
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("ExchangeIssuance: NO ETH SENT");
});
});
context("when amount Set is 0", async () => {
beforeEach(async () => {
subjectAmountSetTokenWei = ZERO;
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("Issue quantity must be > 0");
});
});
context("when input ether amount is insufficient", async () => {
beforeEach(async () => {
subjectAmountETHInput = ONE;
zeroExMock.setBuyMultiplier(weth.address, ether(2));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("revert");
});
});
});
describe("#redeemExactSetForToken", async () => {
let subjectCaller: Account;
let subjectSetToken: SetToken;
let subjectOutputToken: StandardTokenMock | WETH9;
let subjectOutputTokenAmount: BigNumber;
let subjectAmountSetToken: number;
let subjectAmountSetTokenWei: BigNumber;
let subjectPositionSwapQuotes: string[];
let subjectIssuanceModuleAddress: Address;
let subjectIsDebtIssuance: boolean;
let components: Address[];
let positions: BigNumber[];
const initializeSubjectVariables = async () => {
subjectCaller = user;
subjectSetToken = setToken;
subjectOutputTokenAmount = ether(1000);
subjectOutputToken = dai;
subjectAmountSetToken = 1.234567891234;
subjectAmountSetTokenWei = UnitsUtils.ether(subjectAmountSetToken);
subjectIssuanceModuleAddress = issuanceModuleAddress;
subjectIsDebtIssuance = issuanceModuleAddress == setV2Setup.debtIssuanceModule.address;
[components, positions] = await exchangeIssuanceZeroEx.getRequiredRedemptionComponents(
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
subjectSetToken.address,
subjectAmountSetTokenWei,
);
subjectPositionSwapQuotes = positions.map((position: any, index: number) => {
return getUniswapV2Quote(
components[index],
position,
subjectOutputToken.address,
subjectOutputTokenAmount.div(2),
);
});
};
beforeEach(async () => {
await initializeSubjectVariables();
await exchangeIssuanceZeroEx.approveSetToken(
subjectSetToken.address,
subjectIssuanceModuleAddress,
);
await setV2Setup.approveAndIssueSetToken(
subjectSetToken,
subjectAmountSetTokenWei,
subjectCaller.address,
);
await setToken
.connect(subjectCaller.wallet)
.approve(exchangeIssuanceZeroEx.address, MAX_UINT_256);
await dai.transfer(zeroExMock.address, subjectOutputTokenAmount);
});
async function subject(): Promise<ContractTransaction> {
return await exchangeIssuanceZeroEx
.connect(subjectCaller.wallet)
.redeemExactSetForToken(
subjectSetToken.address,
subjectOutputToken.address,
subjectAmountSetTokenWei,
subjectOutputTokenAmount,
subjectPositionSwapQuotes,
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
);
}
it("should redeem the correct number of set tokens", async () => {
const initialBalanceOfSet = await subjectSetToken.balanceOf(subjectCaller.address);
await subject();
const finalSetBalance = await subjectSetToken.balanceOf(subjectCaller.address);
const expectedSetBalance = initialBalanceOfSet.sub(subjectAmountSetTokenWei);
expect(finalSetBalance).to.eq(expectedSetBalance);
});
it("should give the correct number of output tokens", async () => {
const initialDaiBalance = await dai.balanceOf(subjectCaller.address);
await subject();
const finalDaiBalance = await dai.balanceOf(subjectCaller.address);
const expectedDaiBalance = initialDaiBalance.add(subjectOutputTokenAmount);
expect(finalDaiBalance).to.eq(expectedDaiBalance);
});
context("when invalid set token amount is requested", async () => {
beforeEach(async () => {
subjectAmountSetTokenWei = ether(0);
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("Redeem quantity must be > 0");
});
});
context("when an invalid issuance module address is provided", async () => {
beforeEach(async () => {
subjectIssuanceModuleAddress = subjectCaller.address;
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith(
"ExchangeIssuance: INVALID ISSUANCE MODULE",
);
});
});
context("when a position quote is missing", async () => {
beforeEach(async () => {
subjectPositionSwapQuotes = [subjectPositionSwapQuotes[0]];
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith(
"ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT",
);
});
});
context("when the output swap yields insufficient DAI", async () => {
beforeEach(async () => {
await zeroExMock.setBuyMultiplier(dai.address, ether(0.5));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith(
"ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT",
);
});
});
context("when a swap call fails", async () => {
beforeEach(async () => {
// Trigger revertion in mock by trying to return more output token than available in balance
await zeroExMock.setBuyMultiplier(subjectOutputToken.address, ether(100));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("revert");
});
});
context("when the output token is also a component", async () => {
beforeEach(async () => {
subjectOutputToken = wbtc;
subjectAmountSetTokenWei = UnitsUtils.ether(1);
subjectOutputTokenAmount = wbtcUnits.mul(2);
[
components,
positions,
] = await exchangeIssuanceZeroEx.getRequiredRedemptionComponents(
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
subjectSetToken.address,
subjectAmountSetTokenWei,
);
subjectPositionSwapQuotes = positions.map((position: any, index: number) => {
return getUniswapV2Quote(
components[index],
position,
subjectOutputToken.address,
subjectOutputTokenAmount.div(2),
);
});
await wbtc.transfer(zeroExMock.address, subjectOutputTokenAmount);
});
it("should succeed", async () => {
await subject();
});
it("should redeem the correct number of set tokens", async () => {
const initialBalanceOfSet = await subjectSetToken.balanceOf(subjectCaller.address);
await subject();
const finalSetBalance = await subjectSetToken.balanceOf(subjectCaller.address);
const expectedSetBalance = initialBalanceOfSet.sub(subjectAmountSetTokenWei);
expect(finalSetBalance).to.eq(expectedSetBalance);
});
it("should give the correct number of output tokens", async () => {
const initialWbtcBalance = await wbtc.balanceOf(subjectCaller.address);
await subject();
const finalWbtcBalance = await wbtc.balanceOf(subjectCaller.address);
const expectedWbtcBalance = initialWbtcBalance.add(subjectOutputTokenAmount);
expect(finalWbtcBalance).to.eq(expectedWbtcBalance);
});
});
});
describe("#redeemExactSetForEth", async () => {
let subjectWethAmount: BigNumber;
let subjectAmountSetToken: number;
let subjectAmountSetTokenWei: BigNumber;
let subjectPositionSwapQuotes: string[];
let subjectSetToken: SetToken;
let subjectIssuanceModuleAddress: Address;
let subjectIsDebtIssuance: boolean;
let components: Address[];
let positions: BigNumber[];
const initializeSubjectVariables = async () => {
subjectWethAmount = ether(1);
subjectAmountSetToken = 1.234567891234;
subjectAmountSetTokenWei = UnitsUtils.ether(subjectAmountSetToken);
subjectSetToken = setToken;
subjectIssuanceModuleAddress = issuanceModuleAddress;
subjectIsDebtIssuance = issuanceModuleAddress == setV2Setup.debtIssuanceModule.address;
[
components,
positions,
] = await exchangeIssuanceZeroEx.callStatic.getRequiredRedemptionComponents(
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
subjectSetToken.address,
subjectAmountSetTokenWei,
);
subjectPositionSwapQuotes = positions.map((position: any, index: number) => {
return getUniswapV2Quote(
components[index],
position,
weth.address,
subjectWethAmount.div(2),
);
});
};
beforeEach(async () => {
await initializeSubjectVariables();
await exchangeIssuanceZeroEx.approveSetToken(setToken.address, issuanceModuleAddress);
await setV2Setup.approveAndIssueSetToken(
setToken,
subjectAmountSetTokenWei,
owner.address,
);
await setToken.approve(exchangeIssuanceZeroEx.address, MAX_UINT_256);
await weth.transfer(zeroExMock.address, subjectWethAmount);
});
async function subject(): Promise<ContractTransaction> {
return await exchangeIssuanceZeroEx.redeemExactSetForETH(
setToken.address,
subjectAmountSetTokenWei,
subjectWethAmount,
subjectPositionSwapQuotes,
subjectIssuanceModuleAddress,
subjectIsDebtIssuance,
);
}
it("should redeem the correct number of set tokens", async () => {
const initialBalanceOfSet = await setToken.balanceOf(owner.address);
await subject();
const finalSetBalance = await setToken.balanceOf(owner.address);
const expectedSetBalance = initialBalanceOfSet.sub(subjectAmountSetTokenWei);
expect(finalSetBalance).to.eq(expectedSetBalance);
});
it("should disperse the correct amount of eth", async () => {
const initialEthBalance = await owner.wallet.getBalance();
const tx = await subject();
const transactionFee = await getTxFee(tx);
const finalEthBalance = await owner.wallet.getBalance();
const expectedEthBalance = initialEthBalance.add(subjectWethAmount).sub(transactionFee);
expect(finalEthBalance).to.eq(expectedEthBalance);
});
context("when the swaps yield insufficient weth", async () => {
beforeEach(async () => {
await zeroExMock.setBuyMultiplier(weth.address, ether(0.5));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith(
"ExchangeIssuance: INSUFFICIENT WETH RECEIVED",
);
});
});
context("when the swaps yields excess weth", async () => {
const wethMultiplier = 2;
beforeEach(async () => {
await zeroExMock.setBuyMultiplier(weth.address, ether(wethMultiplier));
await weth.transfer(zeroExMock.address, subjectWethAmount.mul(wethMultiplier - 1));
});
it("should disperse the correct amount of eth", async () => {
const initialEthBalance = await owner.wallet.getBalance();
const tx = await subject();
const gasPrice = tx.gasPrice;
const receipt = await tx.wait();
const gasUsed = receipt.cumulativeGasUsed;
const transactionFee = gasPrice.mul(gasUsed);
const finalEthBalance = await owner.wallet.getBalance();
const expectedEthBalance = initialEthBalance
.add(subjectWethAmount.mul(wethMultiplier))
.sub(transactionFee);
expect(finalEthBalance).to.eq(expectedEthBalance);
});
});
context("when the swap consumes an excessive amount of component token", async () => {
const multiplier = 2;
beforeEach(async () => {
const componentAddress = components[1];
const position = positions[1];
await zeroExMock.setSellMultiplier(componentAddress, ether(multiplier));
await wbtc.transfer(exchangeIssuanceZeroEx.address, position.mul(multiplier - 1));
});
it("should revert", async () => {
await expect(subject()).to.be.revertedWith("ExchangeIssuance: OVERSOLD COMPONENT");
});
});
});
});
});
});
});
Example #23
Source File: grantXP_astegotchi.ts From aavegotchi-contracts with MIT License | 4 votes |
async function main() {
const diamondAddress = "0x86935F11C86623deC8a25696E1C19a8659CbF95d";
console.log(itemManager);
let signer;
const testing = ["hardhat", "localhost"].includes(network.name);
if (testing) {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [itemManager],
});
signer = await ethers.provider.getSigner(itemManager);
} else if (network.name === "matic") {
const accounts = await ethers.getSigners();
signer = accounts[0]; //new LedgerSigner(ethers.provider, "hid", "m/44'/60'/2'/0/0");
} else {
throw Error("Incorrect network selected");
}
const dao = (await ethers.getContractAt("DAOFacet", diamondAddress)).connect(
signer
);
//First order by score
const orderByScore = (a: MinigameResult, b: MinigameResult) => {
return b.score - a.score;
};
const finalResults = results.sort(orderByScore);
const xp15 = 100;
const xp10 = 400;
const xp5min = 10000;
//15xp (0,100)
let game15 = finalResults.slice(0, xp15);
if (game15.length !== xp15) throw new Error("XP 15 length incorrect");
console.log("game15:", game15.length);
//10xp (101,500)
let game10 = finalResults.slice(xp15 + 1, xp15 + 1 + xp10);
if (game10.length !== xp10) throw new Error("XP 15 length incorrect");
console.log("game 10:", game10.length);
//5xp (501 and above)
let game5 = finalResults.slice(xp15 + xp10 + 1, finalResults.length);
game5 = game5.filter((obj) => {
return obj.score >= xp5min;
});
console.log("game 5:", game5.length);
let groups: MinigameGroup[] = [
{ xpAmount: 15, aavegotchis: game15 },
{ xpAmount: 10, aavegotchis: game10 },
{ xpAmount: 5, aavegotchis: game5 },
];
const maxProcess = 500;
for (let index = 0; index < groups.length; index++) {
const group: MinigameGroup = groups[index];
console.log(
`Sending ${group.xpAmount} XP to ${group.aavegotchis.length} aavegotchis!`
);
// group the data
const txData = [];
let txGroup = [];
let tokenIdsNum = 0;
for (const obj of group.aavegotchis) {
const gotchiID = obj.tokenId;
if (maxProcess < tokenIdsNum + 1) {
txData.push(txGroup);
txGroup = [];
tokenIdsNum = 0;
}
txGroup.push(gotchiID);
tokenIdsNum += 1;
}
if (tokenIdsNum > 0) {
txData.push(txGroup);
txGroup = [];
tokenIdsNum = 0;
}
// send transactions
let addressIndex = 0;
for (const txGroup of txData) {
console.log("tx group:", txGroup);
const tokenIds = txGroup;
console.log(
`Sending ${group.xpAmount} XP to ${tokenIds.length} Aavegotchis `
);
const tx = await dao.grantExperience(
tokenIds,
Array(tokenIds.length).fill(group.xpAmount),
{ gasPrice: gasPrice }
);
let receipt = await tx.wait();
if (!receipt.status) {
throw Error(`Error:: ${tx.hash}`);
}
console.log(
"Airdropped XP to Aaavegotchis. Last address:",
tokenIds[tokenIds.length - 1]
);
console.log("A total of", tokenIds.length, "Aavegotchis");
console.log("Current address index:", addressIndex);
console.log("");
}
}
}
Example #24
Source File: epochTest5.ts From ghst-staking with MIT License | 4 votes |
describe("More checks", async function () {
const diamondAddress = maticStakingAddress;
before(async function () {
this.timeout(2000000000);
await upgrade();
stakingFacet = (await ethers.getContractAt(
"StakingFacet",
diamondAddress
)) as StakingFacet;
stakingFacet = (await impersonate(
testAddress,
stakingFacet,
ethers,
network
)) as StakingFacet;
});
it("Cannot stake into pool that does not exist in epoch", async function () {
const stakeAmount = ethers.utils.parseEther("100");
await expect(
stakingFacet.stakeIntoPool(
"0x86935F11C86623deC8a25696E1C19a8659CbF95d",
stakeAmount
)
).to.be.revertedWith("StakingFacet: Pool is not valid in this epoch");
});
it("Can bump user to latest epoch without changing FRENS", async function () {
let userEpoch = await stakingFacet.userEpoch(testAddress);
expect(userEpoch).to.equal(0);
const beforeFrens = ethers.utils.formatEther(
await stakingFacet.frens(testAddress)
);
stakingFacet = (await impersonate(
rateManager,
stakingFacet,
ethers,
network
)) as StakingFacet;
//Add a few epochs, then try using bump function
for (let index = 0; index < 3; index++) {
const currentEpoch = await stakingFacet.currentEpoch();
console.log("Updating rates, current index is:", index);
await stakingFacet.updateRates(currentEpoch, initPools);
}
let current = await stakingFacet.currentEpoch();
await expect(
stakingFacet.bumpEpoch(testAddress, current)
).to.be.revertedWith("StakingFacet: Can only bump migrated user");
await stakingFacet.migrateToV2([testAddress]);
//Add a few epochs, then try using bump function
for (let index = 0; index < 3; index++) {
console.log("Updating rates, current index is:", index);
const currentEpoch = await stakingFacet.currentEpoch();
await stakingFacet.updateRates(currentEpoch, initPools);
}
userEpoch = await stakingFacet.userEpoch(testAddress);
current = await stakingFacet.currentEpoch();
await stakingFacet.bumpEpoch(testAddress, current.sub(2));
let afterFrens = ethers.utils.formatEther(
await stakingFacet.frens(testAddress)
);
console.log("frens after first bump:", afterFrens);
current = await stakingFacet.currentEpoch();
await stakingFacet.bumpEpoch(testAddress, current);
userEpoch = await stakingFacet.userEpoch(testAddress);
expect(userEpoch).to.equal(current);
afterFrens = ethers.utils.formatEther(
await stakingFacet.frens(testAddress)
);
console.log("frens after second bump:", afterFrens);
expect(Number(afterFrens) - Number(beforeFrens)).to.lessThan(10);
});
it("Cannot bump user lower than their current epoch", async function () {
const current = await stakingFacet.userEpoch(testAddress);
await expect(
stakingFacet.bumpEpoch(testAddress, current.sub(1))
).to.be.revertedWith("StakingFacet: Cannot bump to lower epoch");
});
it("Cannot bump user higher than current epoch", async function () {
await expect(stakingFacet.bumpEpoch(testAddress, "100")).to.be.revertedWith(
"StakingFacet: Epoch must be lower than current epoch"
);
});
});
Example #25
Source File: gotchiLendingRefactorTest.ts From aavegotchi-contracts with MIT License | 4 votes |
describe("Testing Aavegotchi Lending Refactor", async function () {
this.timeout(300000);
const listedFilter = ethers.utils.formatBytes32String("listed");
const agreedFilter = ethers.utils.formatBytes32String("agreed");
const ghstAddress = "0x385Eeac5cB85A38A9a07A70c73e0a3271CfB54A7";
const collateral = "0x8df3aad3a84da6b69a4da8aec3ea40d9091b2ac4";
const revenueTokens: string[] = [ghstAddress, kekAddress];
const lenderAddress = "0x8FEebfA4aC7AF314d90a0c17C3F91C800cFdE44B";
const borrowerAddress = "0xb4473cfEeDC9a0E94612c6ce883677b63f830DB8"; // borrower should be GHST holder
const nonGhstHolderAddress = "0x725Fe4790fC6435B5161f88636C2A50e43247A4b"; // GHST holder balance should be 0
const ghstHolderAddress = "0x3721546e51258065bfdb9746b2e442c7671b0298";
const thirdPartyAddress = "0x382038b034fa8Ea64C74C81d680669bDaC4D0636";
const lendingOperatorAddress = "0x4E59235b35d504D1372ABf67a835031F98114d64"; // original pet operator should be MATIC holder
const lockedAavegotchiId = 22003;
const unlockedAavegotchiId = [21655, 11939];
let lendingFacet: GotchiLendingFacet;
let lendingGetterAndSetterFacet: LendingGetterAndSetterFacet;
let whitelistFacet: WhitelistFacet;
let aavegotchiFacet: AavegotchiFacet;
let erc721MarketplaceFacet: ERC721MarketplaceFacet;
let aavegotchiGameFacet: AavegotchiGameFacet;
let escrowFacet: EscrowFacet;
let ghstToken: ERC20Token;
let lender: Signer;
let borrower: Signer;
let lendingOperator: Signer;
let thirdParty: Signer;
let listingId: BigNumberish;
before(async function () {
await upgrade();
lendingFacet = (await ethers.getContractAt(
"GotchiLendingFacet",
aavegotchiDiamondAddressMatic
)) as GotchiLendingFacet;
whitelistFacet = (await ethers.getContractAt(
"WhitelistFacet",
aavegotchiDiamondAddressMatic
)) as WhitelistFacet;
lendingGetterAndSetterFacet = (await ethers.getContractAt(
"LendingGetterAndSetterFacet",
aavegotchiDiamondAddressMatic
)) as LendingGetterAndSetterFacet;
aavegotchiFacet = (await ethers.getContractAt(
"contracts/Aavegotchi/facets/AavegotchiFacet.sol:AavegotchiFacet",
aavegotchiDiamondAddressMatic
)) as AavegotchiFacet;
erc721MarketplaceFacet = (await ethers.getContractAt(
"ERC721MarketplaceFacet",
aavegotchiDiamondAddressMatic
)) as ERC721MarketplaceFacet;
aavegotchiGameFacet = (await ethers.getContractAt(
"AavegotchiGameFacet",
aavegotchiDiamondAddressMatic
)) as AavegotchiGameFacet;
escrowFacet = (await ethers.getContractAt(
"EscrowFacet",
aavegotchiDiamondAddressMatic
)) as EscrowFacet;
ghstToken = (await ethers.getContractAt(
"ERC20Token",
ghstAddress
)) as ERC20Token;
lender = await impersonateAndFundSigner(
network,
await aavegotchiFacet.ownerOf(lockedAavegotchiId)
);
borrower = await impersonateAndFundSigner(network, borrowerAddress);
lendingOperator = await impersonateAndFundSigner(
network,
lendingOperatorAddress
);
thirdParty = await impersonateAndFundSigner(network, thirdPartyAddress);
// set approval
await (
await aavegotchiFacet
.connect(lender)
.setApprovalForAll(aavegotchiDiamondAddressMatic, true)
).wait();
});
describe("Lending Facet Test (To compliment gotchiLendingFacetTest)", async () => {
describe("Setters and Getters", async () => {
// it("Borrower should not be borrowing", async () => {
// expect(await lendingGetterAndSetterFacet.isBorrowing(borrowerAddress))
// .to.be.false;
// });
it("Should set lending operator for a token", async () => {
await lendingGetterAndSetterFacet
.connect(lender)
.setLendingOperator(
await lendingOperator.getAddress(),
unlockedAavegotchiId[0],
true
);
expect(
await lendingGetterAndSetterFacet.isLendingOperator(
await lender.getAddress(),
await lendingOperator.getAddress(),
unlockedAavegotchiId[0]
)
).to.be.true;
});
it("Should revoke lending operator for a token", async () => {
await lendingGetterAndSetterFacet
.connect(lender)
.setLendingOperator(
await lendingOperator.getAddress(),
unlockedAavegotchiId[0],
false
);
expect(
await lendingGetterAndSetterFacet.isLendingOperator(
await lender.getAddress(),
await lendingOperator.getAddress(),
unlockedAavegotchiId[0]
)
).to.be.false;
});
it("Should be able to batch set lending operator", async () => {
await lendingGetterAndSetterFacet
.connect(lender)
.batchSetLendingOperator(await lendingOperator.getAddress(), [
{ _tokenId: unlockedAavegotchiId[0], _isLendingOperator: true },
{ _tokenId: unlockedAavegotchiId[1], _isLendingOperator: true },
]);
expect(
await lendingGetterAndSetterFacet.isLendingOperator(
await lender.getAddress(),
await lendingOperator.getAddress(),
unlockedAavegotchiId[0]
)
).to.be.true;
expect(
await lendingGetterAndSetterFacet.isLendingOperator(
await lender.getAddress(),
await lendingOperator.getAddress(),
unlockedAavegotchiId[1]
)
).to.be.true;
await lendingGetterAndSetterFacet
.connect(lender)
.batchSetLendingOperator(await lendingOperator.getAddress(), [
{ _tokenId: unlockedAavegotchiId[0], _isLendingOperator: false },
{ _tokenId: unlockedAavegotchiId[1], _isLendingOperator: false },
]);
});
it("Should not be able to set lending operator if not owner", async () => {
await expect(
lendingGetterAndSetterFacet
.connect(borrower)
.setLendingOperator(
await lendingOperator.getAddress(),
unlockedAavegotchiId[0],
true
)
).to.be.revertedWith(
"LibAppStorage: Only aavegotchi owner can call this function"
);
});
it("Should not be able to set lending operator if aavegotchi is locked", async () => {
await expect(
lendingGetterAndSetterFacet
.connect(lender)
.setLendingOperator(
await lendingOperator.getAddress(),
lockedAavegotchiId,
true
)
).to.be.revertedWith(
"LibAppStorage: Only callable on unlocked Aavegotchis"
);
});
});
describe("Add Gotchi Listing", async () => {
it("Should add listing", async () => {
const lastListingId =
await lendingGetterAndSetterFacet.getGotchiLendingsLength();
await lendingFacet.connect(lender).addGotchiListing({
tokenId: unlockedAavegotchiId[0],
initialCost: 0,
period: 10000,
revenueSplit: [50, 50, 0],
originalOwner: await lender.getAddress(),
thirdParty: await thirdParty.getAddress(),
whitelistId: 0,
revenueTokens: [],
});
const listingId =
await lendingGetterAndSetterFacet.getGotchiLendingsLength();
expect(listingId).to.equal(lastListingId.add(1));
const lending = await lendingGetterAndSetterFacet.getLendingListingInfo(
listingId
);
expect(lending.lender).to.equal(await lender.getAddress());
expect(lending.initialCost).to.equal(0);
expect(lending.period).to.equal(10000);
expect(lending.borrower).to.equal(ethers.constants.AddressZero);
expect(lending.listingId).to.equal(listingId);
expect(lending.erc721TokenId).to.equal(unlockedAavegotchiId[0]);
expect(lending.revenueSplit).to.deep.equal([50, 50, 0]);
expect(lending.originalOwner).to.equal(await lender.getAddress());
expect(lending.thirdParty).to.equal(await thirdParty.getAddress());
expect(lending.whitelistId).to.equal(0);
expect(lending.revenueTokens).to.deep.equal([]);
expect(lending.timeCreated).to.be.gt(0);
expect(lending.timeAgreed).to.equal(0);
expect(lending.canceled).to.be.false;
expect(lending.completed).to.be.false;
expect(lending.lastClaimed).to.equal(0);
await lendingFacet.connect(lender).cancelGotchiLending(listingId);
expect(
await lendingGetterAndSetterFacet.isAavegotchiListed(
unlockedAavegotchiId[0]
)
).to.be.false;
});
it("Should add batch listing", async () => {
const lastListingId =
await lendingGetterAndSetterFacet.getGotchiLendingsLength();
await lendingFacet.connect(lender).batchAddGotchiListing([
{
tokenId: unlockedAavegotchiId[0],
initialCost: 0,
period: 10000,
revenueSplit: [50, 50, 0],
originalOwner: await lender.getAddress(),
thirdParty: await thirdParty.getAddress(),
whitelistId: 0,
revenueTokens: [],
},
{
tokenId: unlockedAavegotchiId[1],
initialCost: 0,
period: 10000,
revenueSplit: [50, 50, 0],
originalOwner: await lender.getAddress(),
thirdParty: await thirdParty.getAddress(),
whitelistId: 0,
revenueTokens: [],
},
]);
await lendingFacet
.connect(lender)
.batchCancelGotchiLending([
lastListingId.add(1),
lastListingId.add(2),
]);
});
it("Should allow lending operator to add listing", async () => {
const lastListingId =
await lendingGetterAndSetterFacet.getGotchiLendingsLength();
await lendingGetterAndSetterFacet
.connect(lender)
.setLendingOperator(
await lendingOperator.getAddress(),
unlockedAavegotchiId[0],
true
);
await lendingFacet.connect(lendingOperator).addGotchiListing({
tokenId: unlockedAavegotchiId[0],
initialCost: 0,
period: 10000,
revenueSplit: [50, 50, 0],
originalOwner: await lender.getAddress(),
thirdParty: await thirdParty.getAddress(),
whitelistId: 0,
revenueTokens: [],
});
expect(lastListingId.add(1)).to.be.equal(
await lendingGetterAndSetterFacet.getGotchiLendingsLength()
);
await lendingFacet
.connect(lendingOperator)
.cancelGotchiLending(lastListingId.add(1));
});
});
describe("Agree Gotchi Listing", async () => {
// it("Should not be able to borrow a second gotchi", async () => {
// await lendingFacet.connect(lender).batchAddGotchiListing([
// {
// tokenId: unlockedAavegotchiId[0],
// initialCost: 0,
// period: 10000,
// revenueSplit: [50, 50, 0],
// originalOwner: await lender.getAddress(),
// thirdParty: await thirdParty.getAddress(),
// whitelistId: 0,
// revenueTokens: [],
// },
// {
// tokenId: unlockedAavegotchiId[1],
// initialCost: 0,
// period: 10000,
// revenueSplit: [50, 50, 0],
// originalOwner: await lender.getAddress(),
// thirdParty: await thirdParty.getAddress(),
// whitelistId: 0,
// revenueTokens: [],
// },
// ]);
// let lendingLength =
// await lendingGetterAndSetterFacet.getGotchiLendingsLength();
// const listingIds = [lendingLength.sub(1), lendingLength];
// await lendingFacet
// .connect(borrower)
// .agreeGotchiLending(
// listingIds[0],
// unlockedAavegotchiId[0],
// 0,
// 10000,
// [50, 50, 0]
// );
// expect(
// await lendingGetterAndSetterFacet.isBorrowing(
// await borrower.getAddress()
// )
// ).to.be.true;
// await expect(
// lendingFacet
// .connect(borrower)
// .agreeGotchiLending(
// listingIds[1],
// unlockedAavegotchiId[1],
// 0,
// 10000,
// [50, 50, 0]
// )
// ).to.be.revertedWith("LibGotchiLending: Borrower already has a token");
// });
// it("Should be able to borrow another gotchi after returning a gotchi early", async () => {
// await lendingFacet
// .connect(borrower)
// .claimAndEndGotchiLending(unlockedAavegotchiId[0]);
// expect(
// await lendingGetterAndSetterFacet.isBorrowing(
// await borrower.getAddress()
// )
// ).to.be.false;
// await lendingFacet
// .connect(borrower)
// .agreeGotchiLending(
// await lendingGetterAndSetterFacet.getGotchiLendingsLength(),
// unlockedAavegotchiId[1],
// 0,
// 10000,
// [50, 50, 0]
// );
// expect(
// await lendingGetterAndSetterFacet.isBorrowing(
// await borrower.getAddress()
// )
// ).to.be.true;
// await lendingFacet
// .connect(borrower)
// .claimAndEndGotchiLending(unlockedAavegotchiId[1]);
// expect(
// await lendingGetterAndSetterFacet.isBorrowing(
// await borrower.getAddress()
// )
// ).to.be.false;
// });
it("Should not allow non-whitelisted addresses to agree", async () => {
await whitelistFacet
.connect(lender)
.createWhitelist("ayoooo", [await lender.getAddress()]);
const whitelistId = await whitelistFacet.getWhitelistsLength();
await lendingFacet.connect(lender).addGotchiListing({
tokenId: unlockedAavegotchiId[0],
initialCost: 0,
period: 1,
revenueSplit: [50, 50, 0],
originalOwner: await lender.getAddress(),
thirdParty: await thirdParty.getAddress(),
whitelistId: whitelistId,
revenueTokens: [],
});
await expect(
lendingFacet
.connect(borrower)
.agreeGotchiLending(
await lendingGetterAndSetterFacet.getGotchiLendingsLength(),
unlockedAavegotchiId[0],
0,
1,
[50, 50, 0]
)
).to.be.revertedWith("LibGotchiLending: Not whitelisted address");
});
it("Should allow agree on whitelisted addresses", async () => {
await whitelistFacet
.connect(lender)
.updateWhitelist(await whitelistFacet.getWhitelistsLength(), [
await borrower.getAddress(),
]);
await lendingFacet
.connect(borrower)
.agreeGotchiLending(
await lendingGetterAndSetterFacet.getGotchiLendingsLength(),
unlockedAavegotchiId[0],
0,
1,
[50, 50, 0]
);
});
});
describe("Claim Gotchi Listing", async () => {
it("Should allow lending operator to claim listing", async () => {
await lendingFacet
.connect(lendingOperator)
.claimGotchiLending(unlockedAavegotchiId[0]);
});
it("Should allow batch claiming", async () => {
await lendingFacet
.connect(lendingOperator)
.batchClaimGotchiLending([unlockedAavegotchiId[0]]);
});
});
describe("Claim and End Gotchi Listing", async () => {
it("Should allow batch claim and end", async () => {
await lendingFacet
.connect(lendingOperator)
.batchClaimAndEndGotchiLending([unlockedAavegotchiId[0]]);
});
});
describe("Extend Gotchi Listing", async () => {
it("Should not allow anyone but lender or operator extend listing", async () => {
await lendingFacet.connect(lender).addGotchiListing({
tokenId: unlockedAavegotchiId[0],
initialCost: 0,
period: 10000,
revenueSplit: [50, 50, 0],
originalOwner: await lender.getAddress(),
thirdParty: await thirdParty.getAddress(),
whitelistId: 0,
revenueTokens: [],
});
await lendingFacet
.connect(borrower)
.agreeGotchiLending(
await lendingGetterAndSetterFacet.getGotchiLendingsLength(),
unlockedAavegotchiId[0],
0,
10000,
[50, 50, 0]
);
await expect(
lendingFacet
.connect(borrower)
.extendGotchiLending(unlockedAavegotchiId[0], 10000)
).to.be.revertedWith(
"GotchiLending: Only lender or lending operator can extend"
);
});
it("Should not allow extensions beyond the max period", async () => {
await expect(
lendingFacet
.connect(lender)
.extendGotchiLending(unlockedAavegotchiId[0], 30000000)
).to.be.revertedWith(
"GotchiLending: Cannot extend a listing beyond the maximum period"
);
});
it("Should not allow extensions if the listing is not borrowed", async () => {
await lendingFacet.connect(lender).addGotchiListing({
tokenId: unlockedAavegotchiId[1],
initialCost: 0,
period: 10000,
revenueSplit: [50, 50, 0],
originalOwner: await lender.getAddress(),
thirdParty: await thirdParty.getAddress(),
whitelistId: 0,
revenueTokens: [],
});
await expect(
lendingFacet
.connect(lender)
.extendGotchiLending(unlockedAavegotchiId[1], 10000)
).to.be.revertedWith(
"GotchiLending: Cannot extend a listing that has not been borrowed"
);
});
it("Should allow lender to extend listing", async () => {
await lendingFacet
.connect(lender)
.extendGotchiLending(unlockedAavegotchiId[0], 10000);
const lending =
await lendingGetterAndSetterFacet.getGotchiLendingFromToken(
unlockedAavegotchiId[0]
);
expect(lending.period).to.be.equal(20000);
});
it("Should allow lending operator to extend listing", async () => {
await lendingFacet
.connect(lendingOperator)
.extendGotchiLending(unlockedAavegotchiId[0], 10000);
const lending =
await lendingGetterAndSetterFacet.getGotchiLendingFromToken(
unlockedAavegotchiId[0]
);
expect(lending.period).to.be.equal(30000);
});
it("Should allow batch extensions", async () => {
await lendingFacet.connect(lendingOperator).batchExtendGotchiLending([
{
tokenId: unlockedAavegotchiId[0],
extension: 10000,
},
]);
const lending =
await lendingGetterAndSetterFacet.getGotchiLendingFromToken(
unlockedAavegotchiId[0]
);
expect(lending.period).to.be.equal(40000);
});
});
});
});
Example #26
Source File: batchTransferTickets.ts From ghst-staking with MIT License | 4 votes |
async function batchTicketTransfer() {
const accounts = await ethers.getSigners();
const itemManager = "0x8D46fd7160940d89dA026D59B2e819208E714E82";
let testing = ["hardhat"].includes(network.name);
let signer: Signer;
if (testing) {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [itemManager],
});
signer = await ethers.provider.getSigner(itemManager);
} else if (network.name === "matic") {
signer = accounts[0];
} else {
throw Error("Incorrect network selected");
}
//an array of the addresses to receive the tickets
const addresses: string[] = [
"0xE5561F7eADD55bEbdba7e06888804f6AD37DBcf9",
"0x511e675011856fa5C751de284F7A8046c5e7c78d",
"0x3837E3E1901CC309aaBAc1F610D440323DC840d2",
"0x9E78fb8009251aa4c16f7D4C518Baac58d893865",
"0xCcC8076Af1FE7fde7c638567081B82596e27cB02",
"0x1A42216475319C6Ed13992a30D2C4E763acF7d85",
"0x7d9489BBcC62e6b98d2A4374169952152EAf7DaE",
"0x8a724CB32348b7a7fe6C7aB7eD722ff9869C7743",
"0x9bfA3531738D37f2160850E14f1C4663dae03d37",
"0x8b2b6937213a40518C105829c5865A37e2e6c592",
"0x0c535F59104A9d296fe6bb24274ab5567AbAeFD4",
"0xf937a09d904D2bE9636AaA72980ef77BCF9233e1",
"0xf7b3E12Dd62F13eca44D2863f391a7f0263c07b3",
"0x90abde24d5de0f94f416c322f694602a644600e2",
"0x3AF09684f77Fa6Fb566dc9f8656b25493068bE29",
"0xa915023B3eD1e00E078789e63de079b4D6dCc348",
"0x92E216ac6Dcd7D67F138443f95fDc83bD7dd398f",
"0x59B5Be6db0753942ECcBA68A4c44576C02e8fC03",
"0x08cb3bDFe552FC12A1B682893A0d9FF6dbD3a52B",
"0x92E216ac6Dcd7D67F138443f95fDc83bD7dd398f",
"0x92E216ac6Dcd7D67F138443f95fDc83bD7dd398f",
"0x4D72fC4a30C7F50964BbbdC7463C9aD074B3719d",
"0xb436141073EFE6C21aC6BE9A5Bb0D1D74F0ce87C",
"0x0B7deA71cd3bb23d35dF0374c81467176A081693",
"0x24361DF8A930D0781C550ed6C18dA49eF8948988",
"0x99655CA16C742b46A4a05AFAf0f7798C336Fd279",
"0x1A4394ad3d5B6A40D0528d586f2eDb282a847399",
"0x6687fABfA33E2b342877E33bEc742C703968036f",
"0xB5a6D0F698E58C6E5bc1B9A7Ff402419F585F0E2",
"0x2879fd9866607223BF11545F4F252Db94c36c5e7",
"0x2879fd9866607223BF11545F4F252Db94c36c5e7",
"0x6653025323a6f7c97db8CaC9E6643F45e24EF318",
"0x24361DF8A930D0781C550ed6C18dA49eF8948988",
"0x697203445B51cc43664EeB8D87dB3117425b3F52",
"0xb55988515fa7e7699da8770a714722816e254966",
"0x1F67848e80FB9854cc0D16A184562467b0738BE5",
"0xa080C96dbaa0C49a19aDEB5AB0149638F3c493Af",
"0x3837E3E1901CC309aaBAc1F610D440323DC840d2",
"0x2CD41AA2CB6E901c2973d61952C1747b1e269a52",
"0xa257263e2f79b4c5F9641257387193e5E43ebca9",
"0xA6c37993267085450aDE3b63636A25b61Ec3925c",
"0xa915023b3ed1e00e078789e63de079b4d6dcc348",
"0xedb75bfcd48d2e55b96cc08baa6b930747506db8",
"0x815446DBC3f21D4710Cc504864C444837986a86C",
"0x00a3D4e0134Ff2046e9C9B83D3321625bA3DA1Bd",
"0xb7203c834b373a3c779db2c302C6b386D522E6e3",
"0x90c375B8acdf8ab6a8978E39ea8Cc280a6b8797e",
"0x8EA8721F27eFcAaBB3901Ed6756505Ab873F15a7",
"0xD400cB2c06bd59b1Cf15CB7794182AF4843C8341",
"0x88d985Df3e4a636f0B6663742fe7200Bb4956642",
"0xaB88533C1D07c777057ea130c9A167d674Db92FC",
"0x084d54bc4Ed9D856b9D8Fa79AcCC79A81eC9Ec94",
"0x80fb192D3f73c8AF8051ee4F59B9Eccf30A4DCf3",
"0xedb75bfcd48d2e55b96cc08baa6b930747506db8",
"0x35F87CF2c34137314D133E6B95A8CbD31DE82301",
"0xE21236307F8A1c4C97e05e62373791F6d2911fe7",
"0xffe7bcEaC39f5AbCBe2588CBF0407d2213f11105",
"0xedb75bfcd48d2e55b96cc08baa6b930747506db8",
"0xEdB75bfCD48d2E55B96CC08bAA6b930747506DB8",
"0x5A42c9Dde8b182ff69209B43C0Aed1750782A579",
"0x079e2fca2a993D64029c67DD6e05F07ffEc0B427",
"0xCfd5297778D979c2e24b35A3C96Fb77cFDF811e7",
"0xCfd5297778D979c2e24b35A3C96Fb77cFDF811e7",
"0x4bC9f77B776533FD8e1c977Ff5cF28b2B5955539",
"0xe2F8ffA704474259522Df3B49F4F053d2e47Bf98",
"0xAA3fCD37B7F61f97F18184dFCAa62106Da622eAF",
"0xCfd5297778D979c2e24b35A3C96Fb77cFDF811e7",
"0x2170199E6ef61E8e4f9CBD899a2428127dEBdEc8",
"0x35F87CF2c34137314D133E6B95A8CbD31DE82301",
"0x4cCFe436F73668084746750267D4BF06dB02A037",
"0x6365dB1621410Bf4BFAFB18fcdA74c172D7BDDC7",
"0x9c7ca7CEd42e10baCA84F17D7153b2f27Ce61DB7",
"0xf7b3E12Dd62F13eca44D2863f391a7f0263c07b3",
"0x8156f6097EF7df6dD34ef2b351B327F48093060B",
"0xe2F8ffA704474259522Df3B49F4F053d2e47Bf98",
"0x06743B3Df2b5bb0c8288aaCa4BaCd3301Ef8d170",
"0x80fb192D3f73c8AF8051ee4F59B9Eccf30A4DCf3",
"0x3f571BdEEfde53185f5AC1A7E63BdB31F97d0F0D",
"0xE29054172DfCBAdcBFA9FBB98edc84dBD4531FaF",
"0x477c8E224258affFa3Ca285EE53f424026b3Bc38",
"0xb7203c834b373a3c779db2c302C6b386D522E6e3",
"0x7209A9C945EAcFF92e758df0B9E194e692B5Ebea",
"0xe817a3df429c30D8418Efc23CCfcfE2c79962fDe",
"0x6687fABfA33E2b342877E33bEc742C703968036f",
"0x42cAe2ed6f5cC6EB4e92AE78A865bAa7EE2010fe",
"0x8EA8721F27eFcAaBB3901Ed6756505Ab873F15a7",
"0xBB363D5b6e091B16A93060EBb1DFC14c63a11DD9",
"0x3293dE4513A9b8f6683f46672c991E87dE6C5839",
"0xb436141073EFE6C21aC6BE9A5Bb0D1D74F0ce87C",
"0x2879fd9866607223BF11545F4F252Db94c36c5e7",
"0x1EF376Eb7D4e5BEb4119CE5BB1fF4B6E03E436B9",
"0xDda192059EC84e2Bb056Ada945297d768BB3276A",
"0x24361DF8A930D0781C550ed6C18dA49eF8948988",
"0xCFf501d840CFC644AE2B5071195005581E5A83f8",
"0x9FAa1ae9C60A800852498a6BE1C046F413292112",
"0x54D35105Dcb34Df8ae1648b3710380600E7558Ba",
"0xa499Df2Bdae854093e5576c26C9e53E1b30d25E5",
"0x37DE670aE6F6D247033fa84Cff1Cd4c81c8f1a5A",
"0xa3e0c05cdf9bbd82bf3b8413d7a2fa2d78a3b5ed",
"0x697203445B51cc43664EeB8D87dB3117425b3F52",
"0xF738db2E863D5a5be1DD25c63A995184e74FFbDA",
"0x35EDaC24f1656265B9E30e546617564b7a9D97fd",
"0x91e545f09a8519cc555672964b8fc38107409e65",
"0x04556062C1815f5707096112BDDd4A474bF434e4",
"0xA9BE254316BC272E90892Ca422280d487FFf74b3",
"0x65a43DD2a44356748e12c32F1Bb503DC2634Af40",
"0x2571e1b1C0Afbb4F3927615C38879de43B866081",
"0xedb75bfcd48d2e55b96cc08baa6b930747506db8",
"0xa257263e2f79b4c5f9641257387193e5e43ebca9",
"0x5A42c9Dde8b182ff69209B43C0Aed1750782A579",
"0x00a3D4e0134Ff2046e9C9B83D3321625bA3DA1Bd",
"0xa257263e2f79b4c5f9641257387193e5e43ebca9",
];
const ticketId = 6; //dropTicketId
const value = 1; //1 ticket per address
const ticketsI = (await ethers.getContractAt(
"TicketsFacet",
maticStakingAddress,
signer
)) as IERC1155;
for (let index = 0; index < addresses.length; index++) {
const address = addresses[index];
console.log(
`Transferring ticket to ${address}, index ${index + 1} of ${
addresses.length
}`
);
const tx = await ticketsI.safeTransferFrom(
itemManager,
address,
ticketId,
value,
[],
{
gasPrice: gasPrice,
}
);
console.log("hash:", tx.hash);
await tx.wait();
}
}