Change to spl_token transfer

This commit is contained in:
Karl Kempe 2022-06-02 15:16:34 +00:00
parent f83a8142bb
commit bc16d7d509
8 changed files with 79 additions and 27 deletions

View File

@ -138,6 +138,7 @@ dependencies = [
"num", "num",
"num-derive", "num-derive",
"num-traits", "num-traits",
"spl-token",
] ]
[[package]] [[package]]

View File

@ -22,6 +22,7 @@ overflow-checks = true
[dependencies] [dependencies]
anchor-lang = { version= "0.24.2", features = ["init-if-needed"]} anchor-lang = { version= "0.24.2", features = ["init-if-needed"]}
anchor-spl = "0.24.2" anchor-spl = "0.24.2"
spl-token = "3.3.0"
num-traits = "0.2" num-traits = "0.2"
num-derive = "0.3" num-derive = "0.3"
borsh = "0.9.3" borsh = "0.9.3"

View File

@ -41,12 +41,15 @@ pub enum SaleError {
#[msg("SaleEnded")] #[msg("SaleEnded")]
SaleEnded, SaleEnded,
#[msg("SaleNotFinished")]
SaleNotFinished,
#[msg("SaleNotAborted")] #[msg("SaleNotAborted")]
SaleNotAborted, SaleNotAborted,
#[msg("SaleNotAttestable")]
SaleNotAttestable,
#[msg("SaleNotFinished")]
SaleNotFinished,
#[msg("SaleNotSealed")] #[msg("SaleNotSealed")]
SaleNotSealed, SaleNotSealed,

View File

@ -24,6 +24,7 @@ pub mod anchor_contributor {
use super::*; use super::*;
use anchor_spl::*; use anchor_spl::*;
use spl_token;
pub fn create_custodian(ctx: Context<CreateCustodian>) -> Result<()> { pub fn create_custodian(ctx: Context<CreateCustodian>) -> Result<()> {
let custodian = &mut ctx.accounts.custodian; let custodian = &mut ctx.accounts.custodian;
@ -69,14 +70,15 @@ pub mod anchor_contributor {
let owner = &ctx.accounts.owner; let owner = &ctx.accounts.owner;
//let ata_seeds: &'a [&[u8]] = &[&owner.key().as_ref(), &token::ID.as_ref(), &mint.as_ref()]; //let ata_seeds: &'a [&[u8]] = &[&owner.key().as_ref(), &token::ID.as_ref(), &mint.as_ref()];
/*
let (ata, bump) = Pubkey::find_program_address( let (ata, bump) = Pubkey::find_program_address(
&[&owner.key().as_ref(), &token::ID.as_ref(), &mint.as_ref()], &[&owner.key().as_ref(), &token::ID.as_ref(), &mint.as_ref()],
&associated_token::AssociatedToken::id(), &associated_token::AssociatedToken::id(),
); );
msg!("ata: {:?}, bump: {:?}", ata, bump); msg!("ata: {:?}, bump: {:?}", ata, bump);
*/
// spl transfer contribution // spl transfer contribution
/*
let ix = spl_token::instruction::transfer( let ix = spl_token::instruction::transfer(
&token::ID, &token::ID,
&ctx.accounts.buyer_ata.key(), &ctx.accounts.buyer_ata.key(),
@ -95,8 +97,8 @@ pub mod anchor_contributor {
ctx.accounts.token_program.to_account_info(), ctx.accounts.token_program.to_account_info(),
], ],
)?; )?;
*/
/*
token::transfer( token::transfer(
CpiContext::new_with_signer( CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(), ctx.accounts.token_program.to_account_info(),
@ -109,6 +111,7 @@ pub mod anchor_contributor {
), ),
amount, amount,
)?; )?;
*/
/* /*
let custodian_bump = ctx.bumps["custodian"]; let custodian_bump = ctx.bumps["custodian"];
@ -253,7 +256,7 @@ pub mod anchor_contributor {
let refunds = ctx.accounts.buyer.claim_refunds(&sale.totals)?; let refunds = ctx.accounts.buyer.claim_refunds(&sale.totals)?;
let atas = &ctx.remaining_accounts; let atas = &ctx.remaining_accounts;
require!( require!(
atas.len() == sale.totals.len(), atas.len() == 2 * sale.totals.len(),
SaleError::InvalidRemainingAccounts SaleError::InvalidRemainingAccounts
); );

View File

@ -191,7 +191,7 @@ impl Sale {
} }
pub fn serialize_contributions(&self, block_time: i64) -> Result<Vec<u8>> { pub fn serialize_contributions(&self, block_time: i64) -> Result<Vec<u8>> {
require!(self.is_attestable(block_time), SaleError::SaleNotFinished); require!(self.is_attestable(block_time), SaleError::SaleNotAttestable);
let totals = &self.totals; let totals = &self.totals;
let mut attested: Vec<u8> = Vec::with_capacity( let mut attested: Vec<u8> = Vec::with_capacity(
@ -211,6 +211,7 @@ impl Sale {
} }
pub fn parse_sale_sealed(&mut self, payload: &[u8]) -> Result<()> { pub fn parse_sale_sealed(&mut self, payload: &[u8]) -> Result<()> {
require!(!self.has_ended(), SaleError::SaleEnded);
// check that the payload has at least the number of bytes // check that the payload has at least the number of bytes
// required to define the number of allocations // required to define the number of allocations
require!( require!(
@ -296,8 +297,7 @@ impl Sale {
} }
pub fn has_ended(&self) -> bool { pub fn has_ended(&self) -> bool {
return self.initialized && self.status == SaleStatus::Sealed return self.initialized && self.status != SaleStatus::Active;
|| self.status == SaleStatus::Aborted;
} }
pub fn is_sealed(&self) -> bool { pub fn is_sealed(&self) -> bool {

View File

@ -133,7 +133,6 @@ describe("anchor-contributor", () => {
}); });
}); });
/*
describe("Conduct Successful Sale", () => { describe("Conduct Successful Sale", () => {
// global contributions for test // global contributions for test
const contributions = new Map<web3.PublicKey, string[]>(); const contributions = new Map<web3.PublicKey, string[]>();
@ -194,6 +193,7 @@ describe("anchor-contributor", () => {
let caughtError = false; let caughtError = false;
try { try {
const tx = await contributor.initSale(orchestrator, dummyConductor.initSaleVaa); const tx = await contributor.initSale(orchestrator, dummyConductor.initSaleVaa);
console.log("should not happen", tx);
} catch (e) { } catch (e) {
// pda init should fail // pda init should fail
caughtError = "programErrorStack" in e; caughtError = "programErrorStack" in e;
@ -212,9 +212,9 @@ describe("anchor-contributor", () => {
try { try {
const mint = hexToPublicKey(dummyConductor.acceptedTokens[0].address); const mint = hexToPublicKey(dummyConductor.acceptedTokens[0].address);
const tx = await contributor.contribute(buyer, saleId, mint, new BN(amount)); const tx = await contributor.contribute(buyer, saleId, mint, new BN(amount));
console.log("should not happen", tx);
} catch (e) { } catch (e) {
//console.log("too early", e); caughtError = verifyErrorMsg(e, "ContributionTooEarly");
caughtError = e.msg == "ContributionTooEarly";
} }
if (!caughtError) { if (!caughtError) {
@ -328,10 +328,13 @@ describe("anchor-contributor", () => {
let caughtError = false; let caughtError = false;
try { try {
const tx = await contributor.attestContributions(orchestrator, saleId); const tx = await contributor.attestContributions(orchestrator, saleId);
console.log(tx); console.log("should not happen", tx);
} catch (e) { } catch (e) {
console.log(e.error.errorCode.code); caughtError = verifyErrorMsg(e, "SaleNotAttestable");
caughtError = e.error.errorCode.code == "ContributionTooEarly"; }
if (!caughtError) {
throw Error("did not catch expected error");
} }
}); });
@ -353,7 +356,19 @@ describe("anchor-contributor", () => {
// TODO // TODO
it("Orchestrator Cannot Attest Contributions Again", async () => { it("Orchestrator Cannot Attest Contributions Again", async () => {
expect(false).to.be.true; const saleId = dummyConductor.getSaleId();
let caughtError = false;
try {
const tx = await contributor.attestContributions(orchestrator, saleId);
console.log("should not happen", tx);
} catch (e) {
caughtError = verifyErrorMsg(e, "SaleNotAttestable");
}
if (!caughtError) {
throw Error("did not catch expected error");
}
}); });
it("User Cannot Contribute After Sale Ended", async () => { it("User Cannot Contribute After Sale Ended", async () => {
@ -364,8 +379,9 @@ describe("anchor-contributor", () => {
try { try {
const mint = hexToPublicKey(dummyConductor.acceptedTokens[0].address); const mint = hexToPublicKey(dummyConductor.acceptedTokens[0].address);
const tx = await contributor.contribute(buyer, saleId, mint, amount); const tx = await contributor.contribute(buyer, saleId, mint, amount);
console.log("should not happen", tx);
} catch (e) { } catch (e) {
caughtError = e.msg == "SaleEnded"; caughtError = verifyErrorMsg(e, "SaleEnded");
} }
if (!caughtError) { if (!caughtError) {
@ -398,9 +414,9 @@ describe("anchor-contributor", () => {
let caughtError = false; let caughtError = false;
try { try {
const tx = await contributor.sealSale(orchestrator, saleSealedVaa); const tx = await contributor.sealSale(orchestrator, saleSealedVaa);
console.log("should not happen", tx);
} catch (e) { } catch (e) {
//caughtError = e.error.errorCode.code == "SaleEnded"; caughtError = verifyErrorMsg(e, "SaleEnded");
console.log(e.error.errorCode.code);
} }
if (!caughtError) { if (!caughtError) {
@ -425,7 +441,7 @@ describe("anchor-contributor", () => {
expect(false).to.be.true; expect(false).to.be.true;
}); });
}); });
*/
describe("Conduct Aborted Sale", () => { describe("Conduct Aborted Sale", () => {
// global contributions for test // global contributions for test
const contributions = new Map<web3.PublicKey, string[]>(); const contributions = new Map<web3.PublicKey, string[]>();
@ -530,8 +546,9 @@ describe("anchor-contributor", () => {
let caughtError = false; let caughtError = false;
try { try {
const tx = await contributor.abortSale(orchestrator, saleAbortedVaa); const tx = await contributor.abortSale(orchestrator, saleAbortedVaa);
console.log("should not happen", tx);
} catch (e) { } catch (e) {
caughtError = e.error.errorCode.code == "SaleEnded"; caughtError = verifyErrorMsg(e, "SaleEnded");
} }
if (!caughtError) { if (!caughtError) {
@ -559,9 +576,9 @@ describe("anchor-contributor", () => {
let caughtError = false; let caughtError = false;
try { try {
//const tx = await contributor.claimRefund(saleId, buyer, acceptedMints); //const tx = await contributor.claimRefund(saleId, buyer, acceptedMints);
//console.log("should not happen", tx);
} catch (e) { } catch (e) {
console.log(e); caughtError = verifyErrorMsg(e, "BuyerInactive");
caughtError = e.error.errorCode.code == "SaleEnded";
} }
if (!caughtError) { if (!caughtError) {
@ -570,3 +587,22 @@ describe("anchor-contributor", () => {
}); });
}); });
}); });
function verifyErrorMsg(e: any, msg: string): boolean {
if (e.msg) {
const result = e.msg == msg;
if (!result) {
console.error(e);
}
return result;
} else if (e.error.errorMessage) {
const result = e.error.errorMessage == msg;
if (!result) {
console.error(e);
}
return result;
}
console.error(e);
throw Error("unknown error");
}

View File

@ -11,7 +11,7 @@ import { getAccount, getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from "@solana
import { findBuyerAccount, findCustodianAccount, findSaleAccount, findSignedVaaAccount, KeyBump } from "./accounts"; import { findBuyerAccount, findCustodianAccount, findSaleAccount, findSignedVaaAccount, KeyBump } from "./accounts";
import { getBuyerState, getCustodianState, getSaleState } from "./fetch"; import { getBuyerState, getCustodianState, getSaleState } from "./fetch";
import { postVaa } from "./wormhole"; import { postVaa } from "./wormhole";
import { getPdaAssociatedTokenAddress } from "./utils"; import { getPdaAssociatedTokenAddress, makeWritableAccountMeta } from "./utils";
import { ASSOCIATED_PROGRAM_ID } from "@project-serum/anchor/dist/cjs/utils/token"; import { ASSOCIATED_PROGRAM_ID } from "@project-serum/anchor/dist/cjs/utils/token";
export class IccoContributor { export class IccoContributor {
@ -173,10 +173,10 @@ export class IccoContributor {
const buyerAccount = findBuyerAccount(program.programId, saleId, payer.publicKey); const buyerAccount = findBuyerAccount(program.programId, saleId, payer.publicKey);
const saleAccount = findSaleAccount(program.programId, saleId); const saleAccount = findSaleAccount(program.programId, saleId);
const remainingAccounts = []; const remainingAccounts: web3.AccountMeta[] = [];
for (const mint of acceptedMints) { for (const mint of acceptedMints) {
remainingAccounts.push(await getAssociatedTokenAddress(mint, payer.publicKey)); remainingAccounts.push(makeWritableAccountMeta(await getAssociatedTokenAddress(mint, payer.publicKey)));
remainingAccounts.push(await getPdaAssociatedTokenAddress(mint, custodian)); remainingAccounts.push(makeWritableAccountMeta(await getPdaAssociatedTokenAddress(mint, custodian)));
} }
return program.methods return program.methods

View File

@ -38,3 +38,11 @@ export function hexToPublicKey(hexlified: string): web3.PublicKey {
export async function getPdaAssociatedTokenAddress(mint: web3.PublicKey, pda: web3.PublicKey): Promise<web3.PublicKey> { export async function getPdaAssociatedTokenAddress(mint: web3.PublicKey, pda: web3.PublicKey): Promise<web3.PublicKey> {
return getAssociatedTokenAddress(mint, pda, true); return getAssociatedTokenAddress(mint, pda, true);
} }
export function makeWritableAccountMeta(pubkey: web3.PublicKey): web3.AccountMeta {
return {
pubkey,
isWritable: true,
isSigner: false,
};
}