Add spl mints; fix accepted token max; fix tsconfig; add buyer keypair
This commit is contained in:
parent
e327eedc41
commit
cbf5b061d3
|
@ -8,7 +8,7 @@ url = "https://anchor.projectserum.com"
|
||||||
|
|
||||||
[provider]
|
[provider]
|
||||||
cluster = "localnet"
|
cluster = "localnet"
|
||||||
wallet = "./tests/test_keypair.json"
|
wallet = "./tests/test_orchestrator_keypair.json"
|
||||||
|
|
||||||
[scripts]
|
[scripts]
|
||||||
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
||||||
|
|
|
@ -26,7 +26,10 @@ pub const INIT_INDEX_SALE_END: usize = 100;
|
||||||
pub const INIT_INDEX_ACCEPTED_TOKENS_START: usize = 132;
|
pub const INIT_INDEX_ACCEPTED_TOKENS_START: usize = 132;
|
||||||
|
|
||||||
pub const ACCEPTED_TOKENS_N_BYTES: usize = 33;
|
pub const ACCEPTED_TOKENS_N_BYTES: usize = 33;
|
||||||
pub const ACCEPTED_TOKENS_MAX: usize = 10;
|
pub const ACCEPTED_TOKENS_MAX: usize = 8;
|
||||||
|
pub const ACCEPTED_TOKENS_INDEX_TOKEN_INDEX: usize = 0;
|
||||||
|
pub const ACCEPTED_TOKENS_INDEX_TOKEN_ADDRESS: usize = 1;
|
||||||
|
pub const ACCEPTED_TOKENS_INDEX_END: usize = 33;
|
||||||
|
|
||||||
// for attest contributions
|
// for attest contributions
|
||||||
pub const ATTEST_CONTRIBUTIONS_ELEMENT_LEN: usize = 33; // token index + amount
|
pub const ATTEST_CONTRIBUTIONS_ELEMENT_LEN: usize = 33; // token index + amount
|
||||||
|
|
|
@ -65,10 +65,15 @@ impl AssetTotal {
|
||||||
pub const MAXIMUM_SIZE: usize = 1 + 32 + 8 + 8 + 8;
|
pub const MAXIMUM_SIZE: usize = 1 + 32 + 8 + 8 + 8;
|
||||||
|
|
||||||
pub fn make_from_slice(bytes: &[u8]) -> Result<Self> {
|
pub fn make_from_slice(bytes: &[u8]) -> Result<Self> {
|
||||||
require!(bytes.len() == 33, SaleError::InvalidAcceptedTokenPayload);
|
require!(
|
||||||
|
bytes.len() == ACCEPTED_TOKENS_INDEX_END,
|
||||||
|
SaleError::InvalidAcceptedTokenPayload
|
||||||
|
);
|
||||||
Ok(AssetTotal {
|
Ok(AssetTotal {
|
||||||
token_index: bytes[0],
|
token_index: bytes[ACCEPTED_TOKENS_INDEX_TOKEN_INDEX],
|
||||||
mint: Pubkey::new(&bytes[1..33]),
|
mint: Pubkey::new(
|
||||||
|
&bytes[ACCEPTED_TOKENS_INDEX_TOKEN_ADDRESS..ACCEPTED_TOKENS_INDEX_END],
|
||||||
|
),
|
||||||
contributions: 0,
|
contributions: 0,
|
||||||
allocations: 0,
|
allocations: 0,
|
||||||
excess_contributions: 0,
|
excess_contributions: 0,
|
||||||
|
@ -84,6 +89,7 @@ impl Sale {
|
||||||
+ (8 + 8)
|
+ (8 + 8)
|
||||||
+ 32
|
+ 32
|
||||||
+ 1
|
+ 1
|
||||||
|
+ 1
|
||||||
+ (4 + AssetTotal::MAXIMUM_SIZE * ACCEPTED_TOKENS_MAX)
|
+ (4 + AssetTotal::MAXIMUM_SIZE * ACCEPTED_TOKENS_MAX)
|
||||||
+ 1;
|
+ 1;
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
import { AnchorProvider, workspace, web3, Program, setProvider } from "@project-serum/anchor";
|
import { AnchorProvider, workspace, web3, Program, setProvider, BN } from "@project-serum/anchor";
|
||||||
import { AnchorContributor } from "../target/types/anchor_contributor";
|
import { AnchorContributor } from "../target/types/anchor_contributor";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { CHAIN_ID_SOLANA, setDefaultWasm, tryHexToNativeString, tryNativeToHexString } from "@certusone/wormhole-sdk";
|
import { CHAIN_ID_SOLANA, setDefaultWasm, tryHexToNativeString, tryNativeToHexString } from "@certusone/wormhole-sdk";
|
||||||
import { createAssociatedTokenAccount } from "@solana/spl-token";
|
import {
|
||||||
|
getOrCreateAssociatedTokenAccount,
|
||||||
|
getAssociatedTokenAddress,
|
||||||
|
createAssociatedTokenAccountInstruction,
|
||||||
|
createSyncNativeInstruction,
|
||||||
|
getAccount,
|
||||||
|
createMint,
|
||||||
|
getMint,
|
||||||
|
mintTo,
|
||||||
|
Account as AssociatedTokenAccount,
|
||||||
|
} from "@solana/spl-token";
|
||||||
|
|
||||||
import { DummyConductor } from "./helpers/conductor";
|
import { DummyConductor, MAX_ACCEPTED_TOKENS } from "./helpers/conductor";
|
||||||
import { CONDUCTOR_ADDRESS, CONDUCTOR_CHAIN } from "./helpers/consts";
|
|
||||||
import { IccoContributor } from "./helpers/contributor";
|
import { IccoContributor } from "./helpers/contributor";
|
||||||
import { getBlockTime, wait } from "./helpers/utils";
|
import { getBlockTime, getSplBalance, hexToPublicKey, wait } from "./helpers/utils";
|
||||||
import { BN } from "bn.js";
|
|
||||||
|
|
||||||
setDefaultWasm("node");
|
setDefaultWasm("node");
|
||||||
|
|
||||||
|
@ -20,8 +28,11 @@ describe("anchor-contributor", () => {
|
||||||
const program = workspace.AnchorContributor as Program<AnchorContributor>;
|
const program = workspace.AnchorContributor as Program<AnchorContributor>;
|
||||||
const connection = program.provider.connection;
|
const connection = program.provider.connection;
|
||||||
|
|
||||||
const owner = web3.Keypair.fromSecretKey(
|
const orchestrator = web3.Keypair.fromSecretKey(
|
||||||
Uint8Array.from(JSON.parse(readFileSync("./tests/test_keypair.json").toString()))
|
Uint8Array.from(JSON.parse(readFileSync("./tests/test_orchestrator_keypair.json").toString()))
|
||||||
|
);
|
||||||
|
const buyer = web3.Keypair.fromSecretKey(
|
||||||
|
Uint8Array.from(JSON.parse(readFileSync("./tests/test_buyer_keypair.json").toString()))
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: we need other wallets for buyers
|
// TODO: we need other wallets for buyers
|
||||||
|
@ -32,6 +43,39 @@ describe("anchor-contributor", () => {
|
||||||
// our contributor
|
// our contributor
|
||||||
const contributor = new IccoContributor(program);
|
const contributor = new IccoContributor(program);
|
||||||
|
|
||||||
|
describe("Test Preparation", () => {
|
||||||
|
it("Create Dummy Sale Token", async () => {
|
||||||
|
// mint 8 unique tokens
|
||||||
|
const mint = await createMint(connection, orchestrator, orchestrator.publicKey, orchestrator.publicKey, 9);
|
||||||
|
|
||||||
|
// we need to simulate attesting the sale token on Solana.
|
||||||
|
// this allows us to "redeem" the sale token prior to sealing the sale
|
||||||
|
// (which in the case of this test means minting it on the contributor program's ATA)
|
||||||
|
await dummyConductor.attestSaleToken(connection, orchestrator);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Mint Accepted SPL Tokens to Buyer", async () => {
|
||||||
|
// first create them and add them to the accepted tokens list
|
||||||
|
const acceptedTokens = await dummyConductor.createAcceptedTokens(connection, orchestrator);
|
||||||
|
|
||||||
|
for (const token of acceptedTokens) {
|
||||||
|
const mint = new web3.PublicKey(tryHexToNativeString(token.address, CHAIN_ID_SOLANA));
|
||||||
|
const tokenAccount = await getOrCreateAssociatedTokenAccount(connection, orchestrator, mint, buyer.publicKey);
|
||||||
|
await mintTo(
|
||||||
|
connection,
|
||||||
|
orchestrator,
|
||||||
|
mint,
|
||||||
|
tokenAccount.address,
|
||||||
|
orchestrator,
|
||||||
|
20000000000n // 20,000,000,000 lamports
|
||||||
|
);
|
||||||
|
|
||||||
|
const balance = await getSplBalance(connection, mint, buyer.publicKey);
|
||||||
|
expect(balance).to.equal(20000000000n);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Sanity Checks", () => {
|
describe("Sanity Checks", () => {
|
||||||
it("Cannot Contribute to Non-Existent Sale", async () => {
|
it("Cannot Contribute to Non-Existent Sale", async () => {
|
||||||
{
|
{
|
||||||
|
@ -41,7 +85,7 @@ describe("anchor-contributor", () => {
|
||||||
|
|
||||||
let caughtError = false;
|
let caughtError = false;
|
||||||
try {
|
try {
|
||||||
const tx = await contributor.contribute(owner, saleId, tokenIndex, amount);
|
const tx = await contributor.contribute(orchestrator, saleId, tokenIndex, amount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
caughtError = e.error.errorCode.code == "AccountNotInitialized";
|
caughtError = e.error.errorCode.code == "AccountNotInitialized";
|
||||||
}
|
}
|
||||||
|
@ -55,24 +99,32 @@ describe("anchor-contributor", () => {
|
||||||
|
|
||||||
describe("Conduct Successful Sale", () => {
|
describe("Conduct Successful Sale", () => {
|
||||||
// contributor info
|
// contributor info
|
||||||
const contributionTokenIndex = 2;
|
const contributions = new Map<number, string[]>();
|
||||||
const contributionAmounts = ["2000000000", "3000000000"];
|
contributions.set(2, ["1200000000", "3400000000"]);
|
||||||
const totalContributionAmount = contributionAmounts.map((x) => new BN(x)).reduce((prev, curr) => prev.add(curr));
|
contributions.set(8, ["5600000000", "7800000000"]);
|
||||||
|
|
||||||
|
const totalContributions: BN[] = [];
|
||||||
|
contributions.forEach((amounts) => {
|
||||||
|
totalContributions.push(amounts.map((x) => new BN(x)).reduce((prev, curr) => prev.add(curr)));
|
||||||
|
});
|
||||||
|
|
||||||
|
// squirrel away associated sale token account
|
||||||
|
let saleTokenAccount: AssociatedTokenAccount;
|
||||||
|
|
||||||
|
it("Create ATA for Sale Token if Non-Existent", async () => {
|
||||||
|
saleTokenAccount = await getOrCreateAssociatedTokenAccount(
|
||||||
|
connection,
|
||||||
|
orchestrator,
|
||||||
|
dummyConductor.getSaleTokenOnSolana(),
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("Orchestrator Initialize Sale with Signed VAA", async () => {
|
it("Orchestrator Initialize Sale with Signed VAA", async () => {
|
||||||
const tokenAccountKey = tryHexToNativeString(
|
const startTime = 10 + (await getBlockTime(connection));
|
||||||
"00000000000000000000000083752ecafebf4707258dedffbd9c7443148169db",
|
|
||||||
CHAIN_ID_SOLANA
|
|
||||||
); // placeholder
|
|
||||||
|
|
||||||
const duration = 5; // seconds
|
const duration = 5; // seconds
|
||||||
const initSaleVaa = dummyConductor.createSale(
|
const initSaleVaa = dummyConductor.createSale(startTime, duration, saleTokenAccount.address);
|
||||||
await getBlockTime(connection),
|
const tx = await contributor.initSale(orchestrator, initSaleVaa);
|
||||||
duration,
|
|
||||||
new web3.PublicKey(tokenAccountKey)
|
|
||||||
);
|
|
||||||
const tx = await contributor.initSale(owner, initSaleVaa);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// get the first sale state
|
// get the first sale state
|
||||||
|
@ -110,7 +162,7 @@ describe("anchor-contributor", () => {
|
||||||
it("Orchestrator Cannot Initialize Sale Again with Signed VAA", async () => {
|
it("Orchestrator Cannot Initialize Sale Again with Signed VAA", async () => {
|
||||||
let caughtError = false;
|
let caughtError = false;
|
||||||
try {
|
try {
|
||||||
const tx = await contributor.initSale(owner, dummyConductor.initSaleVaa);
|
const tx = await contributor.initSale(orchestrator, dummyConductor.initSaleVaa);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// pda init should fail
|
// pda init should fail
|
||||||
caughtError = "programErrorStack" in e;
|
caughtError = "programErrorStack" in e;
|
||||||
|
@ -121,21 +173,6 @@ describe("anchor-contributor", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Create Associated Token Accounts for Token Custodian", async () => {
|
|
||||||
// TODO: need to do sale token, too
|
|
||||||
|
|
||||||
const tokens = dummyConductor.acceptedTokens.map((token) => {
|
|
||||||
return tryHexToNativeString(token.address, CHAIN_ID_SOLANA)
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
for(let addr of tokens) {
|
|
||||||
await createAssociatedTokenAccount(connection, owner, addr, program.programId);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("User Cannot Contribute Too Early", async () => {
|
it("User Cannot Contribute Too Early", async () => {
|
||||||
const saleId = dummyConductor.getSaleId();
|
const saleId = dummyConductor.getSaleId();
|
||||||
const tokenIndex = 2;
|
const tokenIndex = 2;
|
||||||
|
@ -143,7 +180,7 @@ describe("anchor-contributor", () => {
|
||||||
|
|
||||||
let caughtError = false;
|
let caughtError = false;
|
||||||
try {
|
try {
|
||||||
const tx = await contributor.contribute(owner, saleId, tokenIndex, amount);
|
const tx = await contributor.contribute(orchestrator, saleId, tokenIndex, amount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
caughtError = e.error.errorCode.code == "ContributionTooEarly";
|
caughtError = e.error.errorCode.code == "ContributionTooEarly";
|
||||||
}
|
}
|
||||||
|
@ -151,8 +188,6 @@ describe("anchor-contributor", () => {
|
||||||
if (!caughtError) {
|
if (!caughtError) {
|
||||||
throw Error("did not catch expected error");
|
throw Error("did not catch expected error");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check balances on contract and buyer
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("User Contributes to Sale", async () => {
|
it("User Contributes to Sale", async () => {
|
||||||
|
@ -163,40 +198,65 @@ describe("anchor-contributor", () => {
|
||||||
await wait(saleStart - blockTime + 1);
|
await wait(saleStart - blockTime + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now go about your business
|
const acceptedTokens = dummyConductor.acceptedTokens;
|
||||||
|
const startingBalanceBuyer = await acceptedTokens.map(async (token) => {
|
||||||
|
const mint = hexToPublicKey(token.address);
|
||||||
|
return getSplBalance(connection, mint, buyer.publicKey);
|
||||||
|
});
|
||||||
|
const startingBalanceContributor = await acceptedTokens.map(async (token) => {
|
||||||
|
const mint = hexToPublicKey(token.address);
|
||||||
|
return getSplBalance(connection, mint, program.programId);
|
||||||
|
});
|
||||||
|
|
||||||
// contribute twice
|
// now go about your business
|
||||||
|
// contribute multiple times
|
||||||
const saleId = dummyConductor.getSaleId();
|
const saleId = dummyConductor.getSaleId();
|
||||||
|
for (const [tokenIndex, contributionAmounts] of contributions) {
|
||||||
for (const amount of contributionAmounts) {
|
for (const amount of contributionAmounts) {
|
||||||
const tx = await contributor.contribute(owner, saleId, contributionTokenIndex, new BN(amount));
|
const tx = await contributor.contribute(orchestrator, saleId, tokenIndex, new BN(amount));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check buyer state
|
|
||||||
{
|
{
|
||||||
const saleId = dummyConductor.getSaleId();
|
const saleId = dummyConductor.getSaleId();
|
||||||
const buyerState = await contributor.getBuyer(saleId, owner.publicKey);
|
|
||||||
expect(buyerState.status).has.key("active");
|
|
||||||
|
|
||||||
const expectedContributedValues = [
|
const expectedContributedValues = [
|
||||||
totalContributionAmount,
|
totalContributions[0],
|
||||||
new BN(0),
|
|
||||||
new BN(0),
|
|
||||||
new BN(0),
|
|
||||||
new BN(0),
|
|
||||||
new BN(0),
|
new BN(0),
|
||||||
new BN(0),
|
new BN(0),
|
||||||
|
totalContributions[1],
|
||||||
new BN(0),
|
new BN(0),
|
||||||
new BN(0),
|
new BN(0),
|
||||||
new BN(0),
|
new BN(0),
|
||||||
new BN(0),
|
new BN(0),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// check buyer state
|
||||||
|
{
|
||||||
|
const buyerState = await contributor.getBuyer(saleId, orchestrator.publicKey);
|
||||||
|
expect(buyerState.status).has.key("active");
|
||||||
|
|
||||||
const contributed = buyerState.contributed;
|
const contributed = buyerState.contributed;
|
||||||
expect(contributed.length).to.equal(10);
|
for (let i = 0; i < expectedContributedValues.length; ++i) {
|
||||||
for (let i = 0; i < 10; ++i) {
|
|
||||||
expect(contributed[i].toString()).to.equal(expectedContributedValues[i].toString());
|
expect(contributed[i].toString()).to.equal(expectedContributedValues[i].toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check sale state
|
||||||
|
{
|
||||||
|
const saleState = await contributor.getSale(saleId);
|
||||||
|
|
||||||
|
// check totals
|
||||||
|
const totals: any = saleState.totals;
|
||||||
|
|
||||||
|
for (let i = 0; i < expectedContributedValues.length; ++i) {
|
||||||
|
const total = totals[i];
|
||||||
|
expect(total.contributions.toString()).to.equal(expectedContributedValues[i].toString());
|
||||||
|
expect(total.allocations.toString()).to.equal("0");
|
||||||
|
expect(total.excessContributions.toString()).to.equal("0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: check balances on contract and buyer
|
// TODO: check balances on contract and buyer
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -207,7 +267,7 @@ describe("anchor-contributor", () => {
|
||||||
|
|
||||||
let caughtError = false;
|
let caughtError = false;
|
||||||
try {
|
try {
|
||||||
const tx = await contributor.contribute(owner, saleId, tokenIndex, amount);
|
const tx = await contributor.contribute(orchestrator, saleId, tokenIndex, amount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
caughtError = e.error.errorCode.code == "InvalidTokenIndex";
|
caughtError = e.error.errorCode.code == "InvalidTokenIndex";
|
||||||
}
|
}
|
||||||
|
@ -247,7 +307,7 @@ describe("anchor-contributor", () => {
|
||||||
|
|
||||||
let caughtError = false;
|
let caughtError = false;
|
||||||
try {
|
try {
|
||||||
const tx = await contributor.contribute(owner, saleId, tokenIndex, amount);
|
const tx = await contributor.contribute(orchestrator, saleId, tokenIndex, amount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
caughtError = e.error.errorCode.code == "SaleEnded";
|
caughtError = e.error.errorCode.code == "SaleEnded";
|
||||||
}
|
}
|
||||||
|
@ -255,18 +315,12 @@ describe("anchor-contributor", () => {
|
||||||
if (!caughtError) {
|
if (!caughtError) {
|
||||||
throw Error("did not catch expected error");
|
throw Error("did not catch expected error");
|
||||||
}
|
}
|
||||||
// check buyer state
|
|
||||||
{
|
|
||||||
const saleId = dummyConductor.getSaleId();
|
|
||||||
//const buyerState = await contributor.getBuyer(saleId, owner.publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check balances on contract and buyer
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
it("Orchestrator Seals Sale with Signed VAA", async () => {
|
it("Orchestrator Seals Sale with Signed VAA", async () => {
|
||||||
expect(false).to.be.true;
|
//const saleSealedVaa = dummyConductor.sealSale();
|
||||||
|
//const tx = await contributor.sealSale(orchestrator, saleSealedVaa);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -292,24 +346,32 @@ describe("anchor-contributor", () => {
|
||||||
|
|
||||||
describe("Conduct Aborted Sale", () => {
|
describe("Conduct Aborted Sale", () => {
|
||||||
// contributor info
|
// contributor info
|
||||||
const contributionTokenIndex = 2;
|
const contributions = new Map<number, string[]>();
|
||||||
const contributionAmounts = ["2000000000", "3000000000"];
|
contributions.set(2, ["8700000000", "6500000000"]);
|
||||||
const totalContributionAmount = contributionAmounts.map((x) => new BN(x)).reduce((prev, curr) => prev.add(curr));
|
contributions.set(8, ["4300000000", "2100000000"]);
|
||||||
|
|
||||||
|
const totalContributions: BN[] = [];
|
||||||
|
contributions.forEach((amounts) => {
|
||||||
|
totalContributions.push(amounts.map((x) => new BN(x)).reduce((prev, curr) => prev.add(curr)));
|
||||||
|
});
|
||||||
|
|
||||||
|
// squirrel away associated sale token account
|
||||||
|
let saleTokenAccount: AssociatedTokenAccount;
|
||||||
|
|
||||||
|
it("Create ATA for Sale Token if Non-Existent", async () => {
|
||||||
|
saleTokenAccount = await getOrCreateAssociatedTokenAccount(
|
||||||
|
connection,
|
||||||
|
orchestrator,
|
||||||
|
dummyConductor.getSaleTokenOnSolana(),
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("Orchestrator Initialize Sale with Signed VAA", async () => {
|
it("Orchestrator Initialize Sale with Signed VAA", async () => {
|
||||||
// set up saleInit vaa
|
const startTime = 10 + (await getBlockTime(connection));
|
||||||
const tokenAccountKey = tryHexToNativeString(
|
|
||||||
"00000000000000000000000083752ecafebf4707258dedffbd9c7443148169db",
|
|
||||||
CHAIN_ID_SOLANA
|
|
||||||
); // placeholder
|
|
||||||
|
|
||||||
const duration = 5; // seconds
|
const duration = 5; // seconds
|
||||||
const initSaleVaa = dummyConductor.createSale(
|
const initSaleVaa = dummyConductor.createSale(startTime, duration, saleTokenAccount.address);
|
||||||
await getBlockTime(connection),
|
const tx = await contributor.initSale(orchestrator, initSaleVaa);
|
||||||
duration,
|
|
||||||
new web3.PublicKey(tokenAccountKey)
|
|
||||||
);
|
|
||||||
const tx = await contributor.initSale(owner, initSaleVaa);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const saleId = dummyConductor.getSaleId();
|
const saleId = dummyConductor.getSaleId();
|
||||||
|
@ -352,16 +414,19 @@ describe("anchor-contributor", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now go about your business
|
// now go about your business
|
||||||
|
// contribute multiple times
|
||||||
const saleId = dummyConductor.getSaleId();
|
const saleId = dummyConductor.getSaleId();
|
||||||
|
for (const [tokenIndex, contributionAmounts] of contributions) {
|
||||||
for (const amount of contributionAmounts) {
|
for (const amount of contributionAmounts) {
|
||||||
const tx = await contributor.contribute(owner, saleId, contributionTokenIndex, new BN(amount));
|
const tx = await contributor.contribute(orchestrator, saleId, tokenIndex, new BN(amount));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Orchestrator Aborts Sale with Signed VAA", async () => {
|
it("Orchestrator Aborts Sale with Signed VAA", async () => {
|
||||||
// TODO: need to abort sale
|
// TODO: need to abort sale
|
||||||
const saleAbortedVaa = dummyConductor.abortSale(await getBlockTime(connection));
|
const saleAbortedVaa = dummyConductor.abortSale(await getBlockTime(connection));
|
||||||
const tx = await contributor.abortSale(owner, saleAbortedVaa);
|
const tx = await contributor.abortSale(orchestrator, saleAbortedVaa);
|
||||||
|
|
||||||
{
|
{
|
||||||
const saleId = dummyConductor.getSaleId();
|
const saleId = dummyConductor.getSaleId();
|
||||||
|
@ -376,7 +441,7 @@ describe("anchor-contributor", () => {
|
||||||
|
|
||||||
let caughtError = false;
|
let caughtError = false;
|
||||||
try {
|
try {
|
||||||
const tx = await contributor.abortSale(owner, saleAbortedVaa);
|
const tx = await contributor.abortSale(orchestrator, saleAbortedVaa);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
caughtError = e.error.errorCode.code == "SaleEnded";
|
caughtError = e.error.errorCode.code == "SaleEnded";
|
||||||
}
|
}
|
||||||
|
@ -401,23 +466,4 @@ describe("anchor-contributor", () => {
|
||||||
expect(false).to.be.true;
|
expect(false).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
/*
|
|
||||||
|
|
||||||
it("creates custody accounts for given token", async () => {
|
|
||||||
setDefaultWasm("node");
|
|
||||||
const { parse_vaa } = await importCoreWasm();
|
|
||||||
const parsedVaa = parse_vaa(initSaleVaa);
|
|
||||||
|
|
||||||
const parsedPayload = await parseSaleInit(parsedVaa.payload);
|
|
||||||
console.log(parsedPayload);
|
|
||||||
|
|
||||||
//Iterate through all accepted tokens on Solana
|
|
||||||
let solanaTokenAddresses = [];
|
|
||||||
|
|
||||||
for(let addr of solanaTokenAddresses) {
|
|
||||||
await createAssociatedTokenAccount(connection, owner, addr, program.programId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import { web3 } from "@project-serum/anchor";
|
||||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA, tryNativeToHexString } from "@certusone/wormhole-sdk";
|
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA, tryNativeToHexString } from "@certusone/wormhole-sdk";
|
||||||
|
import { createMint, getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token";
|
||||||
import { BigNumber } from "ethers";
|
import { BigNumber } from "ethers";
|
||||||
|
|
||||||
import { toBigNumberHex } from "./utils";
|
import { toBigNumberHex } from "./utils";
|
||||||
import { CONDUCTOR_ADDRESS, CONDUCTOR_CHAIN } from "./consts";
|
import { CONDUCTOR_ADDRESS, CONDUCTOR_CHAIN } from "./consts";
|
||||||
import { signAndEncodeVaa } from "./wormhole";
|
import { signAndEncodeVaa } from "./wormhole";
|
||||||
import { web3 } from "@project-serum/anchor";
|
|
||||||
|
|
||||||
// sale struct info
|
// sale struct info
|
||||||
|
export const MAX_ACCEPTED_TOKENS = 8;
|
||||||
const NUM_BYTES_ACCEPTED_TOKEN = 33;
|
const NUM_BYTES_ACCEPTED_TOKEN = 33;
|
||||||
const NUM_BYTES_ALLOCATION = 65;
|
const NUM_BYTES_ALLOCATION = 65;
|
||||||
|
|
||||||
|
@ -18,6 +21,9 @@ export class DummyConductor {
|
||||||
|
|
||||||
initSaleVaa: Buffer;
|
initSaleVaa: Buffer;
|
||||||
|
|
||||||
|
saleTokenOnSolana: string;
|
||||||
|
acceptedTokens: AcceptedToken[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.saleId = 0;
|
this.saleId = 0;
|
||||||
this.wormholeSequence = 0;
|
this.wormholeSequence = 0;
|
||||||
|
@ -26,30 +32,50 @@ export class DummyConductor {
|
||||||
this.saleEnd = 0;
|
this.saleEnd = 0;
|
||||||
|
|
||||||
this.acceptedTokens = [];
|
this.acceptedTokens = [];
|
||||||
this.acceptedTokens.push(makeAcceptedToken(2, "So11111111111111111111111111111111111111112"));
|
}
|
||||||
|
|
||||||
|
async attestSaleToken(connection: web3.Connection, payer: web3.Keypair): Promise<void> {
|
||||||
|
const mint = await createMint(connection, payer, payer.publicKey, payer.publicKey, 9);
|
||||||
|
this.saleTokenOnSolana = mint.toBase58();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSaleTokenOnSolana(): web3.PublicKey {
|
||||||
|
return new web3.PublicKey(this.saleTokenOnSolana);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createAcceptedTokens(connection: web3.Connection, payer: web3.Keypair): Promise<AcceptedToken[]> {
|
||||||
|
const tokenIndices = [2, 3, 5, 8, 13, 21, 34, 55];
|
||||||
|
for (let i = 0; i < MAX_ACCEPTED_TOKENS; ++i) {
|
||||||
|
// just make everything the same number of decimals (9)
|
||||||
|
const mint = await createMint(connection, payer, payer.publicKey, payer.publicKey, 9);
|
||||||
|
const acceptedToken = makeAcceptedToken(tokenIndices[i], mint.toBase58());
|
||||||
|
this.acceptedTokens.push(acceptedToken);
|
||||||
|
}
|
||||||
|
return this.acceptedTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSaleId(): Buffer {
|
getSaleId(): Buffer {
|
||||||
return Buffer.from(toBigNumberHex(this.saleId, 32), "hex");
|
return Buffer.from(toBigNumberHex(this.saleId, 32), "hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
createSale(blockTime: number, duration: number, associatedTokenAddress: web3.PublicKey): Buffer {
|
createSale(startTime: number, duration: number, associatedSaleTokenAddress: web3.PublicKey): Buffer {
|
||||||
// uptick saleId for every new sale
|
// uptick saleId for every new sale
|
||||||
++this.saleId;
|
++this.saleId;
|
||||||
|
|
||||||
// set up sale time based on block time
|
// set up sale time based on block time
|
||||||
this.saleStart = blockTime + 5;
|
this.saleStart = startTime;
|
||||||
this.saleEnd = this.saleStart + duration;
|
this.saleEnd = this.saleStart + duration;
|
||||||
|
|
||||||
this.initSaleVaa = signAndEncodeVaa(
|
this.initSaleVaa = signAndEncodeVaa(
|
||||||
blockTime,
|
startTime,
|
||||||
this.nonce,
|
this.nonce,
|
||||||
CONDUCTOR_CHAIN,
|
CONDUCTOR_CHAIN,
|
||||||
Buffer.from(CONDUCTOR_ADDRESS).toString("hex"),
|
Buffer.from(CONDUCTOR_ADDRESS).toString("hex"),
|
||||||
this.wormholeSequence,
|
this.wormholeSequence,
|
||||||
encodeSaleInit(
|
encodeSaleInit(
|
||||||
this.saleId,
|
this.saleId,
|
||||||
tryNativeToHexString(associatedTokenAddress.toString(), CHAIN_ID_SOLANA),
|
tryNativeToHexString(associatedSaleTokenAddress.toString(), CHAIN_ID_SOLANA),
|
||||||
this.tokenChain,
|
this.tokenChain,
|
||||||
this.tokenDecimals,
|
this.tokenDecimals,
|
||||||
this.saleStart,
|
this.saleStart,
|
||||||
|
@ -76,7 +102,6 @@ export class DummyConductor {
|
||||||
//associatedTokenAddress = "00000000000000000000000083752ecafebf4707258dedffbd9c7443148169db";
|
//associatedTokenAddress = "00000000000000000000000083752ecafebf4707258dedffbd9c7443148169db";
|
||||||
tokenChain = CHAIN_ID_ETH as number;
|
tokenChain = CHAIN_ID_ETH as number;
|
||||||
tokenDecimals = 18;
|
tokenDecimals = 18;
|
||||||
acceptedTokens: AcceptedToken[];
|
|
||||||
recipient = tryNativeToHexString("0x22d491bde2303f2f43325b2108d26f1eaba1e32b", CHAIN_ID_ETH);
|
recipient = tryNativeToHexString("0x22d491bde2303f2f43325b2108d26f1eaba1e32b", CHAIN_ID_ETH);
|
||||||
|
|
||||||
// wormhole nonce
|
// wormhole nonce
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { web3 } from "@project-serum/anchor";
|
import { web3 } from "@project-serum/anchor";
|
||||||
|
import { getAssociatedTokenAddress, getAccount } from "@solana/spl-token";
|
||||||
|
import { tryHexToNativeString, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
||||||
import { BigNumber, BigNumberish } from "ethers";
|
import { BigNumber, BigNumberish } from "ethers";
|
||||||
|
|
||||||
export function toBigNumberHex(value: BigNumberish, numBytes: number): string {
|
export function toBigNumberHex(value: BigNumberish, numBytes: number): string {
|
||||||
|
@ -16,3 +18,13 @@ export async function getBlockTime(connection: web3.Connection): Promise<number>
|
||||||
const slot = await connection.getSlot();
|
const slot = await connection.getSlot();
|
||||||
return connection.getBlockTime(slot);
|
return connection.getBlockTime(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getSplBalance(connection: web3.Connection, mint: web3.PublicKey, owner: web3.PublicKey) {
|
||||||
|
const tokenAccount = await getAssociatedTokenAddress(mint, owner);
|
||||||
|
const account = await getAccount(connection, tokenAccount);
|
||||||
|
return account.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hexToPublicKey(hexlified: string): web3.PublicKey {
|
||||||
|
return new web3.PublicKey(tryHexToNativeString(hexlified, CHAIN_ID_SOLANA));
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
[156,58,190,217,217,182,164,165,16,94,2,148,52,60,124,179,124,246,51,210,65,41,197,4,69,101,18,51,144,149,178,85,170,87,2,76,226,26,32,176,106,152,191,126,156,67,26,119,102,204,192,42,216,85,108,243,227,56,255,208,45,59,124,205]
|
|
@ -2,9 +2,9 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["mocha", "chai"],
|
"types": ["mocha", "chai"],
|
||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
"lib": ["es2015"],
|
"lib": ["es2020"],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es2020",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "node"
|
"moduleResolution": "node"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue