anchor/tests/ido-pool/tests/ido-pool.js

604 lines
18 KiB
JavaScript

const anchor = require("@coral-xyz/anchor");
const { assert } = require("chai");
const {
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
Token,
} = require("@solana/spl-token");
const {
sleep,
getTokenAccount,
createMint,
createTokenAccount,
} = require("./utils");
const { token } = require("@coral-xyz/anchor/dist/cjs/utils");
describe("ido-pool", () => {
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);
const program = anchor.workspace.IdoPool;
// All mints default to 6 decimal places.
const watermelonIdoAmount = new anchor.BN(5000000);
// These are all of the variables we assume exist in the world already and
// are available to the client.
let usdcMintAccount = null;
let usdcMint = null;
let watermelonMintAccount = null;
let watermelonMint = null;
let idoAuthorityUsdc = null;
let idoAuthorityWatermelon = null;
it("Initializes the state-of-the-world", async () => {
usdcMintAccount = await createMint(provider);
watermelonMintAccount = await createMint(provider);
usdcMint = usdcMintAccount.publicKey;
watermelonMint = watermelonMintAccount.publicKey;
idoAuthorityUsdc = await createTokenAccount(
provider,
usdcMint,
provider.wallet.publicKey
);
idoAuthorityWatermelon = await createTokenAccount(
provider,
watermelonMint,
provider.wallet.publicKey
);
// Mint Watermelon tokens that will be distributed from the IDO pool.
await watermelonMintAccount.mintTo(
idoAuthorityWatermelon,
provider.wallet.publicKey,
[],
watermelonIdoAmount.toString()
);
idoAuthority_watermelon_account = await getTokenAccount(
provider,
idoAuthorityWatermelon
);
assert.isTrue(
idoAuthority_watermelon_account.amount.eq(watermelonIdoAmount)
);
});
// These are all variables the client will need to create in order to
// initialize the IDO pool
let idoTimes;
let idoName = "test_ido";
it("Initializes the IDO pool", async () => {
let bumps = new PoolBumps();
const [idoAccount, idoAccountBump] =
await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName)],
program.programId
);
bumps.idoAccount = idoAccountBump;
const [redeemableMint, redeemableMintBump] =
await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("redeemable_mint")],
program.programId
);
bumps.redeemableMint = redeemableMintBump;
const [poolWatermelon, poolWatermelonBump] =
await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("pool_watermelon")],
program.programId
);
bumps.poolWatermelon = poolWatermelonBump;
const [poolUsdc, poolUsdcBump] =
await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("pool_usdc")],
program.programId
);
bumps.poolUsdc = poolUsdcBump;
idoTimes = new IdoTimes();
const nowBn = new anchor.BN(Date.now() / 1000);
idoTimes.startIdo = nowBn.add(new anchor.BN(5));
idoTimes.endDeposits = nowBn.add(new anchor.BN(10));
idoTimes.endIdo = nowBn.add(new anchor.BN(15));
idoTimes.endEscrow = nowBn.add(new anchor.BN(16));
await program.rpc.initializePool(
idoName,
bumps,
watermelonIdoAmount,
idoTimes,
{
accounts: {
idoAuthority: provider.wallet.publicKey,
idoAuthorityWatermelon,
idoAccount,
watermelonMint,
usdcMint,
redeemableMint,
poolWatermelon,
poolUsdc,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
},
}
);
idoAuthorityWatermelonAccount = await getTokenAccount(
provider,
idoAuthorityWatermelon
);
assert.isTrue(idoAuthorityWatermelonAccount.amount.eq(new anchor.BN(0)));
});
// We're going to need to start using the associated program account for creating token accounts
// if not in testing, then definitely in production.
let userUsdc = null;
// 10 usdc
const firstDeposit = new anchor.BN(10_000_349);
it("Exchanges user USDC for redeemable tokens", async () => {
// Wait until the IDO has opened.
if (Date.now() < idoTimes.startIdo.toNumber() * 1000) {
await sleep(idoTimes.startIdo.toNumber() * 1000 - Date.now() + 2000);
}
const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName)],
program.programId
);
const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("redeemable_mint")],
program.programId
);
const [poolUsdc] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("pool_usdc")],
program.programId
);
userUsdc = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
usdcMint,
program.provider.wallet.publicKey
);
// Get the instructions to add to the RPC call
let createUserUsdcInstr = Token.createAssociatedTokenAccountInstruction(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
usdcMint,
userUsdc,
program.provider.wallet.publicKey,
program.provider.wallet.publicKey
);
let createUserUsdcTrns = new anchor.web3.Transaction().add(
createUserUsdcInstr
);
await provider.sendAndConfirm(createUserUsdcTrns);
await usdcMintAccount.mintTo(
userUsdc,
provider.wallet.publicKey,
[],
firstDeposit.toString()
);
// Check if we inited correctly
userUsdcAccount = await getTokenAccount(provider, userUsdc);
assert.isTrue(userUsdcAccount.amount.eq(firstDeposit));
const [userRedeemable] = await anchor.web3.PublicKey.findProgramAddress(
[
provider.wallet.publicKey.toBuffer(),
Buffer.from(idoName),
Buffer.from("user_redeemable"),
],
program.programId
);
try {
const tx = await program.rpc.exchangeUsdcForRedeemable(firstDeposit, {
accounts: {
userAuthority: provider.wallet.publicKey,
userUsdc,
userRedeemable,
idoAccount,
usdcMint,
redeemableMint,
watermelonMint,
poolUsdc,
tokenProgram: TOKEN_PROGRAM_ID,
},
instructions: [
program.instruction.initUserRedeemable({
accounts: {
userAuthority: provider.wallet.publicKey,
userRedeemable,
idoAccount,
redeemableMint,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
},
}),
],
});
} catch (err) {
console.log("This is the error message", err.toString());
}
poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
assert.isTrue(poolUsdcAccount.amount.eq(firstDeposit));
userRedeemableAccount = await getTokenAccount(provider, userRedeemable);
assert.isTrue(userRedeemableAccount.amount.eq(firstDeposit));
});
// 23 usdc
const secondDeposit = new anchor.BN(23_000_672);
let totalPoolUsdc, secondUserKeypair, secondUserUsdc;
it("Exchanges a second users USDC for redeemable tokens", async () => {
const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName)],
program.programId
);
const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("redeemable_mint")],
program.programId
);
const [poolUsdc] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("pool_usdc")],
program.programId
);
secondUserKeypair = anchor.web3.Keypair.generate();
transferSolInstr = anchor.web3.SystemProgram.transfer({
fromPubkey: provider.wallet.publicKey,
lamports: 100_000_000_000, // 100 sol
toPubkey: secondUserKeypair.publicKey,
});
secondUserUsdc = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
usdcMint,
secondUserKeypair.publicKey
);
createSecondUserUsdcInstr = Token.createAssociatedTokenAccountInstruction(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
usdcMint,
secondUserUsdc,
secondUserKeypair.publicKey,
provider.wallet.publicKey
);
let createSecondUserUsdcTrns = new anchor.web3.Transaction();
createSecondUserUsdcTrns.add(transferSolInstr);
createSecondUserUsdcTrns.add(createSecondUserUsdcInstr);
await provider.sendAndConfirm(createSecondUserUsdcTrns);
await usdcMintAccount.mintTo(
secondUserUsdc,
provider.wallet.publicKey,
[],
secondDeposit.toString()
);
// Checking the transfer went through
secondUserUsdcAccount = await getTokenAccount(provider, secondUserUsdc);
assert.isTrue(secondUserUsdcAccount.amount.eq(secondDeposit));
const [secondUserRedeemable] =
await anchor.web3.PublicKey.findProgramAddress(
[
secondUserKeypair.publicKey.toBuffer(),
Buffer.from(idoName),
Buffer.from("user_redeemable"),
],
program.programId
);
await program.rpc.exchangeUsdcForRedeemable(secondDeposit, {
accounts: {
userAuthority: secondUserKeypair.publicKey,
userUsdc: secondUserUsdc,
userRedeemable: secondUserRedeemable,
idoAccount,
usdcMint,
redeemableMint,
watermelonMint,
poolUsdc,
tokenProgram: TOKEN_PROGRAM_ID,
},
instructions: [
program.instruction.initUserRedeemable({
accounts: {
userAuthority: secondUserKeypair.publicKey,
userRedeemable: secondUserRedeemable,
idoAccount,
redeemableMint,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
},
}),
],
signers: [secondUserKeypair],
});
secondUserRedeemableAccount = await getTokenAccount(
provider,
secondUserRedeemable
);
assert.isTrue(secondUserRedeemableAccount.amount.eq(secondDeposit));
totalPoolUsdc = firstDeposit.add(secondDeposit);
poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
assert.isTrue(poolUsdcAccount.amount.eq(totalPoolUsdc));
});
const firstWithdrawal = new anchor.BN(2_000_000);
it("Exchanges user Redeemable tokens for USDC", async () => {
const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName)],
program.programId
);
const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("redeemable_mint")],
program.programId
);
const [poolUsdc] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("pool_usdc")],
program.programId
);
const [userRedeemable] = await anchor.web3.PublicKey.findProgramAddress(
[
provider.wallet.publicKey.toBuffer(),
Buffer.from(idoName),
Buffer.from("user_redeemable"),
],
program.programId
);
const [escrowUsdc] = await anchor.web3.PublicKey.findProgramAddress(
[
provider.wallet.publicKey.toBuffer(),
Buffer.from(idoName),
Buffer.from("escrow_usdc"),
],
program.programId
);
await program.rpc.exchangeRedeemableForUsdc(firstWithdrawal, {
accounts: {
userAuthority: provider.wallet.publicKey,
escrowUsdc,
userRedeemable,
idoAccount,
usdcMint,
redeemableMint,
watermelonMint,
poolUsdc,
tokenProgram: TOKEN_PROGRAM_ID,
},
instructions: [
program.instruction.initEscrowUsdc({
accounts: {
userAuthority: provider.wallet.publicKey,
escrowUsdc,
idoAccount,
usdcMint,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
},
}),
],
});
totalPoolUsdc = totalPoolUsdc.sub(firstWithdrawal);
poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
assert.isTrue(poolUsdcAccount.amount.eq(totalPoolUsdc));
escrowUsdcAccount = await getTokenAccount(provider, escrowUsdc);
assert.isTrue(escrowUsdcAccount.amount.eq(firstWithdrawal));
});
it("Exchanges user Redeemable tokens for watermelon", async () => {
// Wait until the IDO has ended.
if (Date.now() < idoTimes.endIdo.toNumber() * 1000) {
await sleep(idoTimes.endIdo.toNumber() * 1000 - Date.now() + 3000);
}
const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName)],
program.programId
);
const [poolWatermelon] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("pool_watermelon")],
program.programId
);
const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("redeemable_mint")],
program.programId
);
const [userRedeemable] = await anchor.web3.PublicKey.findProgramAddress(
[
provider.wallet.publicKey.toBuffer(),
Buffer.from(idoName),
Buffer.from("user_redeemable"),
],
program.programId
);
let firstUserRedeemable = firstDeposit.sub(firstWithdrawal);
// TODO we've been lazy here and not used an ATA as we did with USDC
userWatermelon = await createTokenAccount(
provider,
watermelonMint,
provider.wallet.publicKey
);
await program.rpc.exchangeRedeemableForWatermelon(firstUserRedeemable, {
accounts: {
payer: provider.wallet.publicKey,
userAuthority: provider.wallet.publicKey,
userWatermelon,
userRedeemable,
idoAccount,
watermelonMint,
redeemableMint,
poolWatermelon,
tokenProgram: TOKEN_PROGRAM_ID,
},
});
poolWatermelonAccount = await getTokenAccount(provider, poolWatermelon);
let redeemedWatermelon = firstUserRedeemable
.mul(watermelonIdoAmount)
.div(totalPoolUsdc);
let remainingWatermelon = watermelonIdoAmount.sub(redeemedWatermelon);
assert.isTrue(poolWatermelonAccount.amount.eq(remainingWatermelon));
userWatermelonAccount = await getTokenAccount(provider, userWatermelon);
assert.isTrue(userWatermelonAccount.amount.eq(redeemedWatermelon));
});
it("Exchanges second user's Redeemable tokens for watermelon", async () => {
const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName)],
program.programId
);
const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("redeemable_mint")],
program.programId
);
const [secondUserRedeemable] =
await anchor.web3.PublicKey.findProgramAddress(
[
secondUserKeypair.publicKey.toBuffer(),
Buffer.from(idoName),
Buffer.from("user_redeemable"),
],
program.programId
);
const [poolWatermelon] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("pool_watermelon")],
program.programId
);
secondUserWatermelon = await createTokenAccount(
provider,
watermelonMint,
secondUserKeypair.publicKey
);
await program.rpc.exchangeRedeemableForWatermelon(secondDeposit, {
accounts: {
payer: provider.wallet.publicKey,
userAuthority: secondUserKeypair.publicKey,
userWatermelon: secondUserWatermelon,
userRedeemable: secondUserRedeemable,
idoAccount,
watermelonMint,
redeemableMint,
poolWatermelon,
tokenProgram: TOKEN_PROGRAM_ID,
},
});
poolWatermelonAccount = await getTokenAccount(provider, poolWatermelon);
assert.isTrue(poolWatermelonAccount.amount.eq(new anchor.BN(0)));
});
it("Withdraws total USDC from pool account", async () => {
const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName)],
program.programId
);
const [poolUsdc] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName), Buffer.from("pool_usdc")],
program.programId
);
await program.rpc.withdrawPoolUsdc({
accounts: {
idoAuthority: provider.wallet.publicKey,
idoAuthorityUsdc,
idoAccount,
usdcMint,
watermelonMint,
poolUsdc,
tokenProgram: TOKEN_PROGRAM_ID,
},
});
poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
assert.isTrue(poolUsdcAccount.amount.eq(new anchor.BN(0)));
idoAuthorityUsdcAccount = await getTokenAccount(provider, idoAuthorityUsdc);
assert.isTrue(idoAuthorityUsdcAccount.amount.eq(totalPoolUsdc));
});
it("Withdraws USDC from the escrow account after waiting period is over", async () => {
// Wait until the escrow period is over.
if (Date.now() < idoTimes.endEscrow.toNumber() * 1000 + 1000) {
await sleep(idoTimes.endEscrow.toNumber() * 1000 - Date.now() + 4000);
}
const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(idoName)],
program.programId
);
const [escrowUsdc] = await anchor.web3.PublicKey.findProgramAddress(
[
provider.wallet.publicKey.toBuffer(),
Buffer.from(idoName),
Buffer.from("escrow_usdc"),
],
program.programId
);
await program.rpc.withdrawFromEscrow(firstWithdrawal, {
accounts: {
payer: provider.wallet.publicKey,
userAuthority: provider.wallet.publicKey,
userUsdc,
escrowUsdc,
idoAccount,
usdcMint,
tokenProgram: TOKEN_PROGRAM_ID,
},
});
userUsdcAccount = await getTokenAccount(provider, userUsdc);
assert.isTrue(userUsdcAccount.amount.eq(firstWithdrawal));
});
function PoolBumps() {
this.idoAccount;
this.redeemableMint;
this.poolWatermelon;
this.poolUsdc;
}
function IdoTimes() {
this.startIdo;
this.endDeposts;
this.endIdo;
this.endEscrow;
}
});