604 lines
18 KiB
JavaScript
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;
|
|
}
|
|
});
|