From 9555f867310b93c6a40e82f6373ff8a447095ee0 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:29:15 -0500 Subject: [PATCH 01/15] add PDA address to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6867183..0f59938 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ Mainnet-beta, testnet, devnet gateway program address: ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis ``` +The PDA account address (derived from seeds `b"meta"` and standup bump) is +``` +2f9SLuUNb7TNeM6gzBwT4ZjbL5ZyKzzHg1Ce9yiquEjj +``` + This repository hosts the smart contracts (program) on Solana network to support ZetaChain cross-chain functionality. Specifically, it consists of a single From 46cecb2def563ef991ac553871167209278ae46b Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:51:22 -0500 Subject: [PATCH 02/15] enhance pda validation --- programs/protocol-contracts-solana/src/lib.rs | 16 ++++++++-------- tests/protocol-contracts-solana.ts | 16 +++++++--------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index e8bb085..50061f1 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -34,7 +34,6 @@ declare_id!("ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis"); #[program] pub mod gateway { - use super::*; pub fn initialize( @@ -338,8 +337,9 @@ pub struct Deposit<'info> { #[account(mut)] pub signer: Signer<'info>, - #[account(mut)] + #[account(mut, seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, + pub system_program: Program<'info, System>, } @@ -348,8 +348,9 @@ pub struct DepositSplToken<'info> { #[account(mut)] pub signer: Signer<'info>, - #[account(mut, seeds = [b"meta"], bump)] + #[account(seeds=[b"meta"], bump)] pub pda: Account<'info, Pda>, + pub token_program: Program<'info, Token>, #[account(mut)] @@ -363,7 +364,7 @@ pub struct Withdraw<'info> { #[account(mut)] pub signer: Signer<'info>, - #[account(mut)] + #[account(mut, seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, /// CHECK: to account is not read so no need to check its owners; the program neither knows nor cares who the owner is. #[account(mut)] @@ -381,7 +382,6 @@ pub struct WithdrawSPLToken<'info> { #[account(mut)] pub pda_ata: Account<'info, TokenAccount>, // associated token address of PDA - #[account()] pub mint_account: Account<'info, Mint>, #[account(mut)] @@ -392,7 +392,7 @@ pub struct WithdrawSPLToken<'info> { #[derive(Accounts)] pub struct UpdateTss<'info> { - #[account(mut)] + #[account(mut, seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, #[account(mut)] pub signer: Signer<'info>, @@ -400,7 +400,7 @@ pub struct UpdateTss<'info> { #[derive(Accounts)] pub struct UpdateAuthority<'info> { - #[account(mut)] + #[account(mut, seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, #[account(mut)] pub signer: Signer<'info>, @@ -408,7 +408,7 @@ pub struct UpdateAuthority<'info> { #[derive(Accounts)] pub struct UpdatePaused<'info> { - #[account(mut)] + #[account(mut, seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, #[account(mut)] pub signer: Signer<'info>, diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 9511e50..fc6b2e8 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -307,7 +307,7 @@ describe("some tests", () => { }); it("deposit and withdraw 0.5 SOL from Gateway with ECDSA signature", async () => { - await gatewayProgram.methods.deposit(new anchor.BN(1_000_000_000), Array.from(address)).accounts({pda: pdaAccount}).rpc(); + await gatewayProgram.methods.deposit(new anchor.BN(1_000_000_000), Array.from(address)).accounts({}).rpc(); let bal1 = await conn.getBalance(pdaAccount); console.log("pda account balance", bal1); expect(bal1).to.be.gte(1_000_000_000); @@ -341,7 +341,6 @@ describe("some tests", () => { await gatewayProgram.methods.withdraw( amount, Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce) .accounts({ - pda: pdaAccount, to: to, }).rpc(); let bal2 = await conn.getBalance(pdaAccount); @@ -353,7 +352,7 @@ describe("some tests", () => { it("deposit and call", async () => { let bal1 = await conn.getBalance(pdaAccount); - const txsig = await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000_000), Array.from(address), Buffer.from("hello", "utf-8")).accounts({pda: pdaAccount}).rpc({commitment: 'confirmed'}); + const txsig = await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000_000), Array.from(address), Buffer.from("hello", "utf-8")).accounts({}).rpc({commitment: 'confirmed'}); const tx = await conn.getParsedTransaction(txsig, 'confirmed'); console.log("deposit and call parsed tx", tx); let bal2 = await conn.getBalance(pdaAccount); @@ -366,7 +365,7 @@ describe("some tests", () => { randomFillSync(newTss); // console.log("generated new TSS address", newTss); await gatewayProgram.methods.updateTss(Array.from(newTss)).accounts({ - pda: pdaAccount, + }).rpc(); const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); // console.log("updated TSS address", pdaAccountData.tssAddress); @@ -375,7 +374,6 @@ describe("some tests", () => { // only the authority stored in PDA can update the TSS address; the following should fail try { await gatewayProgram.methods.updateTss(Array.from(newTss)).accounts({ - pda: pdaAccount, signer: mint.publicKey, }).signers([mint]).rpc(); } catch (err) { @@ -389,12 +387,12 @@ describe("some tests", () => { randomFillSync(newTss); // console.log("generated new TSS address", newTss); await gatewayProgram.methods.setDepositPaused(true).accounts({ - pda: pdaAccount, + }).rpc(); // now try deposit, should fail try { - await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000), Array.from(address), Buffer.from('hi', 'utf-8')).accounts({pda: pdaAccount}).rpc(); + await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000), Array.from(address), Buffer.from('hi', 'utf-8')).accounts({}).rpc(); } catch (err) { console.log("Error message: ", err.message); expect(err).to.be.instanceof(anchor.AnchorError); @@ -405,7 +403,7 @@ describe("some tests", () => { it("update authority", async () => { const newAuthority = anchor.web3.Keypair.generate(); await gatewayProgram.methods.updateAuthority(newAuthority.publicKey).accounts({ - pda: pdaAccount, + }).rpc(); // const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); // expect(pdaAccountData.authority).to.be.eq(newAuthority.publicKey); @@ -413,7 +411,7 @@ describe("some tests", () => { // now the old authority cannot update TSS address and will fail try { await gatewayProgram.methods.updateTss(Array.from(new Uint8Array(20))).accounts({ - pda: pdaAccount, + }).rpc(); } catch (err) { console.log("Error message: ", err.message); From f1567237a4cea841f7416fffa2e137e74d5d6472 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:21:31 -0500 Subject: [PATCH 03/15] Update programs/protocol-contracts-solana/src/lib.rs Co-authored-by: skosito --- programs/protocol-contracts-solana/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 50061f1..1d4ab9f 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -348,7 +348,7 @@ pub struct DepositSplToken<'info> { #[account(mut)] pub signer: Signer<'info>, - #[account(seeds=[b"meta"], bump)] + #[account(seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, pub token_program: Program<'info, Token>, From 64e914f9933d967db6766b8ed4fc61ca0e870f27 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:21:39 -0500 Subject: [PATCH 04/15] Update README.md Co-authored-by: skosito --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f59938..8b849b4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mainnet-beta, testnet, devnet gateway program address: ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis ``` -The PDA account address (derived from seeds `b"meta"` and standup bump) is +The PDA account address (derived from seeds `b"meta"` and canonical bump) is ``` 2f9SLuUNb7TNeM6gzBwT4ZjbL5ZyKzzHg1Ce9yiquEjj ``` From b0d3184d2de0401740e62b891a3182c56ea62dc2 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:32:50 -0500 Subject: [PATCH 05/15] add pda_ata validation as constraints --- programs/protocol-contracts-solana/src/lib.rs | 2 +- tests/protocol-contracts-solana.ts | 38 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 1d4ab9f..0c0e8e8 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -379,7 +379,7 @@ pub struct WithdrawSPLToken<'info> { #[account(mut, seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, - #[account(mut)] + #[account(mut, token::mint = mint_account, token::authority = pda)] pub pda_ata: Account<'info, TokenAccount>, // associated token address of PDA pub mint_account: Account<'info, Mint>, diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index fc6b2e8..a50b3f7 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -169,7 +169,7 @@ describe("some tests", () => { await gatewayProgram.methods.depositSplToken(new anchor.BN(1_000_000), Array.from(address)).accounts({ from: tokenAccount.address, to: pda_ata.address, - }).rpc({commitment: 'confirmed'}); + }).rpc({commitment: 'processed'}); acct = await spl.getAccount(conn, pda_ata.address); let bal1 = acct.amount; expect(bal1-bal0).to.be.eq(1_000_000n); @@ -298,7 +298,7 @@ describe("some tests", () => { } catch (err) { expect(err).to.be.instanceof(anchor.AnchorError); console.log("Error message: ", err.message); - expect(err.message).to.include("SPLAtaAndMintAddressMismatch"); + expect(err.message).to.include("ConstraintTokenMint"); const account4 = await spl.getAccount(conn, pda_ata.address); console.log("After 2nd withdraw: Account balance:", account4.amount.toString()); expect(account4.amount).to.be.eq(2_500_000n); @@ -365,7 +365,7 @@ describe("some tests", () => { randomFillSync(newTss); // console.log("generated new TSS address", newTss); await gatewayProgram.methods.updateTss(Array.from(newTss)).accounts({ - + pda: pdaAccount, }).rpc(); const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); // console.log("updated TSS address", pdaAccountData.tssAddress); @@ -420,6 +420,38 @@ describe("some tests", () => { } }); + it("create an account owned by the gateway program", async () => { + const gateway_id =gatewayProgram.programId; + console.log("gateway program id", gateway_id.toString()); + const fake_pda = anchor.web3.Keypair.generate(); + const rentExemption = await conn.getMinimumBalanceForRentExemption(100); + const instr1 = anchor.web3.SystemProgram.createAccount( + { + fromPubkey: wallet.publicKey, + newAccountPubkey: fake_pda.publicKey, + lamports: rentExemption, + space: 100, + programId: gatewayProgram.programId, + } + ) + const tx = new anchor.web3.Transaction(); + tx.add(instr1, ); + await anchor.web3.sendAndConfirmTransaction(conn, tx, [wallet, fake_pda]); + + const newTss = new Uint8Array(20); + randomFillSync(newTss); + // console.log("generated new TSS address", newTss); + try { + await gatewayProgram.methods.updateTss(Array.from(newTss)).accounts({ + pda: fake_pda.publicKey, + }).rpc(); + } catch (err) { + console.log("Error message: ", err.message); + expect(err).to.be.instanceof(anchor.AnchorError); + expect(err.message).to.include("AccountDiscriminatorMismatch."); + } + }); + }); From 03d757c6e12e8b522c6ab587ab18c29146e7a97f Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:41:58 -0500 Subject: [PATCH 06/15] WIP: add whitelist func --- programs/protocol-contracts-solana/src/lib.rs | 36 +++++++++++++++++++ tests/protocol-contracts-solana.ts | 32 +++++++++++++++-- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 0c0e8e8..3f23307 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -88,6 +88,12 @@ pub mod gateway { Ok(()) } + // whitelisting SPL tokens + pub fn whitelist_spl_mint(ctx: Context) -> Result<()> { + + Ok(()) + } + // deposit SOL into this program and the `receiver` on ZetaChain zEVM // will get corresponding ZRC20 credit. // amount: amount of lamports (10^-9 SOL) to deposit @@ -414,6 +420,32 @@ pub struct UpdatePaused<'info> { pub signer: Signer<'info>, } +// whitelisting, dewhitelisting SPL token (mints) +#[derive(Accounts)] +// #[instruction(account_to_add: Pubkey)] +pub struct AddToWhitelist<'info> { + #[account( + init, + space=8, + payer=authority, + seeds=[ + b"whitelist", + whitelist_candidate.key().as_ref() + ], + bump + )] + pub whitelist_entry: Account<'info, WhitelistEntry>, + pub whitelist_candidate: Account<'info, Mint>, + + #[account(mut, seeds = [b"meta"], bump, has_one = authority)] + pub pda: Account<'info, Pda>, + #[account(mut)] + pub authority: Signer<'info>, + + pub system_program: Program<'info, System>, + +} + #[account] pub struct Pda { nonce: u64, // ensure that each signature can only be used once @@ -423,6 +455,10 @@ pub struct Pda { deposit_paused: bool, } +#[account] +pub struct WhitelistEntry { +} + #[cfg(test)] mod tests { use super::*; diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index a50b3f7..3490043 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -352,7 +352,7 @@ describe("some tests", () => { it("deposit and call", async () => { let bal1 = await conn.getBalance(pdaAccount); - const txsig = await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000_000), Array.from(address), Buffer.from("hello", "utf-8")).accounts({}).rpc({commitment: 'confirmed'}); + const txsig = await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000_000), Array.from(address), Buffer.from("hello", "utf-8")).accounts({}).rpc({commitment: 'processed'}); const tx = await conn.getParsedTransaction(txsig, 'confirmed'); console.log("deposit and call parsed tx", tx); let bal2 = await conn.getBalance(pdaAccount); @@ -400,8 +400,34 @@ describe("some tests", () => { } }); + it("add whitelist spl token", async () => { + await gatewayProgram.methods.whitelistSplMint().accounts({ + whitelistCandidate: mint.publicKey, + }).signers([]).rpc(); + + let seeds = [Buffer.from("whitelist", "utf-8"), mint.publicKey.toBuffer()]; + let [entryAddress] = anchor.web3.PublicKey.findProgramAddressSync( + seeds, + gatewayProgram.programId, + ); + let entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress) + console.log("whitelist entry", entry); + + try { + seeds = [Buffer.from("whitelist", "utf-8"), mint_fake.publicKey.toBuffer()]; + [entryAddress] = anchor.web3.PublicKey.findProgramAddressSync( + seeds, + gatewayProgram.programId, + ); + entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress); + console.log("whitelist entry", entry); + } catch(err) { + expect(err.message).to.include("Account does not exist or has no data"); + } + }); + + const newAuthority = anchor.web3.Keypair.generate(); it("update authority", async () => { - const newAuthority = anchor.web3.Keypair.generate(); await gatewayProgram.methods.updateAuthority(newAuthority.publicKey).accounts({ }).rpc(); @@ -442,6 +468,7 @@ describe("some tests", () => { randomFillSync(newTss); // console.log("generated new TSS address", newTss); try { + // @ts-ignore await gatewayProgram.methods.updateTss(Array.from(newTss)).accounts({ pda: fake_pda.publicKey, }).rpc(); @@ -453,6 +480,7 @@ describe("some tests", () => { }); + }); From b20cf35787ab275374902f10fc8fd7317450d4c8 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:22:25 -0500 Subject: [PATCH 07/15] WIP: add whitelist tag to DepositSplToken --- programs/protocol-contracts-solana/src/lib.rs | 5 + tests/protocol-contracts-solana.ts | 171 ++++++++++-------- 2 files changed, 101 insertions(+), 75 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 3f23307..2563788 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -357,6 +357,11 @@ pub struct DepositSplToken<'info> { #[account(seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, + #[account(seeds=[b"whitelist", mint_account.key().as_ref()], bump)] + pub whitelist_entry: Account<'info, WhitelistEntry>, + + pub mint_account: Account<'info, Mint>, + pub token_program: Program<'info, Token>, #[account(mut)] diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 3490043..cce5314 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -2,7 +2,6 @@ import * as anchor from "@coral-xyz/anchor"; import {Program, web3} from "@coral-xyz/anchor"; import {Gateway} from "../target/types/gateway"; import * as spl from "@solana/spl-token"; -import * as memo from "@solana/spl-memo"; import {randomFillSync} from 'crypto'; import { ec as EC } from 'elliptic'; import { keccak256 } from 'ethereumjs-util'; @@ -19,6 +18,29 @@ const keyPair = ec.keyFromPrivate('5b81cdf52ba0766983acf8dd0072904733d92afe4dd34 const usdcDecimals = 6; +async function mintSPLToken(conn: anchor.web3.Connection, wallet: anchor.web3.Keypair, mint: anchor.web3.Keypair) { + const mintRent = await spl.getMinimumBalanceForRentExemptMint(conn); + let tokenTransaction = new anchor.web3.Transaction(); + tokenTransaction.add( + anchor.web3.SystemProgram.createAccount({ + fromPubkey: wallet.publicKey, + newAccountPubkey: mint.publicKey, + lamports: mintRent, + space: spl.MINT_SIZE, + programId: spl.TOKEN_PROGRAM_ID + }), + spl.createInitializeMintInstruction( + mint.publicKey, + usdcDecimals, + wallet.publicKey, + null, + ) + ); + const txsig = await anchor.web3.sendAndConfirmTransaction(conn, tokenTransaction, [wallet, mint]); + console.log("mint account created!", mint.publicKey.toString()); + return txsig; +} + describe("some tests", () => { // Configure the client to use the local cluster. anchor.setProvider(anchor.AnchorProvider.env()); @@ -75,28 +97,12 @@ describe("some tests", () => { } }); + + it("Mint a SPL USDC token", async () => { // now deploying a fake USDC SPL Token // 1. create a mint account - const mintRent = await spl.getMinimumBalanceForRentExemptMint(conn); - let tokenTransaction = new anchor.web3.Transaction(); - tokenTransaction.add( - anchor.web3.SystemProgram.createAccount({ - fromPubkey: wallet.publicKey, - newAccountPubkey: mint.publicKey, - lamports: mintRent, - space: spl.MINT_SIZE, - programId: spl.TOKEN_PROGRAM_ID - }), - spl.createInitializeMintInstruction( - mint.publicKey, - usdcDecimals, - wallet.publicKey, - null, - ) - ); - await anchor.web3.sendAndConfirmTransaction(conn, tokenTransaction, [wallet, mint]); - console.log("mint account created!", mint.publicKey.toString()); + await mintSPLToken(conn, wallet, mint); // 2. create token account to receive mint tokenAccount = await spl.getOrCreateAssociatedTokenAccount( @@ -128,26 +134,36 @@ describe("some tests", () => { console.log(`wallet_ata: ${wallet_ata.toString()}`); // create a fake USDC token account - tokenTransaction = new anchor.web3.Transaction(); - tokenTransaction.add( - anchor.web3.SystemProgram.createAccount({ - fromPubkey: wallet.publicKey, - newAccountPubkey: mint_fake.publicKey, - lamports: mintRent, - space: spl.MINT_SIZE, - programId: spl.TOKEN_PROGRAM_ID - }), - spl.createInitializeMintInstruction( - mint_fake.publicKey, - usdcDecimals, - wallet.publicKey, - null, - ) - ); - await anchor.web3.sendAndConfirmTransaction(conn, tokenTransaction, [wallet, mint_fake]); + await mintSPLToken(conn, wallet, mint_fake); console.log("fake mint account created!", mint_fake.publicKey.toString()); }) + it("whitelist USDC spl token", async () => { + await gatewayProgram.methods.whitelistSplMint().accounts({ + whitelistCandidate: mint.publicKey, + }).signers([]).rpc(); + + let seeds = [Buffer.from("whitelist", "utf-8"), mint.publicKey.toBuffer()]; + let [entryAddress] = anchor.web3.PublicKey.findProgramAddressSync( + seeds, + gatewayProgram.programId, + ); + let entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress) + console.log("whitelist entry", entry); + + try { + seeds = [Buffer.from("whitelist", "utf-8"), mint_fake.publicKey.toBuffer()]; + [entryAddress] = anchor.web3.PublicKey.findProgramAddressSync( + seeds, + gatewayProgram.programId, + ); + entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress); + console.log("whitelist entry", entry); + } catch(err) { + expect(err.message).to.include("Account does not exist or has no data"); + } + }); + it("Deposit 1_000_000 USDC to Gateway", async () => { let seeds = [Buffer.from("meta", "utf-8")]; [pdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( @@ -169,6 +185,7 @@ describe("some tests", () => { await gatewayProgram.methods.depositSplToken(new anchor.BN(1_000_000), Array.from(address)).accounts({ from: tokenAccount.address, to: pda_ata.address, + mintAccount: mint.publicKey, }).rpc({commitment: 'processed'}); acct = await spl.getAccount(conn, pda_ata.address); let bal1 = acct.amount; @@ -180,6 +197,7 @@ describe("some tests", () => { { from: tokenAccount.address, to: wallet_ata, + mintAccount: mint.publicKey, } ).rpc(); throw new Error("Expected error not thrown"); @@ -195,22 +213,46 @@ describe("some tests", () => { await gatewayProgram.methods.depositSplTokenAndCall(new anchor.BN(2_000_000), Array.from(address), Buffer.from('hi', 'utf-8')).accounts({ from: tokenAccount.address, to: pda_ata.address, + mintAccount: mint.publicKey, }).rpc({commitment: 'confirmed'}); acct = await spl.getAccount(conn, pda_ata.address); bal1 = acct.amount; expect(bal1-bal0).to.be.eq(2_000_000n); + }); + + it("deposit non-whitelisted SPL tokens should fail", async () => { + let seeds = [Buffer.from("meta", "utf-8")]; + [pdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( + seeds, + gatewayProgram.programId, + ); + console.log("gateway pda account", pdaAccount.toString()); + let fake_pda_ata = await spl.getOrCreateAssociatedTokenAccount( + conn, + wallet, + mint_fake.publicKey, + pdaAccount, + true + ); + console.log("fake_mint fake_pda_ata address", fake_pda_ata.address.toString()); + + try { + await gatewayProgram.methods.depositSplToken(new anchor.BN(1_000_000), Array.from(address)).accounts({ + from: tokenAccount.address, + to: fake_pda_ata.address, + mintAccount: mint_fake.publicKey, + }).rpc({commitment: 'processed'}); + } catch (err) { + expect(err).to.be.instanceof(anchor.AnchorError); + expect(err.message).to.include("AccountNotInitialized"); + } - // try { - // await gatewayProgram.methods.depositSplTokenAndCall(new anchor.BN(1_000_000), Array.from(address), Buffer.from("hello", "utf-8")).accounts({ - // from: tokenAccount.address, - // to: pda_ata.address, - // }).rpc(); - // - // } }); it("Withdraw 500_000 USDC from Gateway with ECDSA signature", async () => { - const account2 = await spl.getAccount(conn, pda_ata.address); + let pda_ata = await spl.getAssociatedTokenAddress(mint.publicKey, pdaAccount, true); + + const account2 = await spl.getAccount(conn, pda_ata); // expect(account2.amount).to.be.eq(1_000_000n); console.log("B4 withdraw: Account balance:", account2.amount.toString()); @@ -245,19 +287,19 @@ describe("some tests", () => { await gatewayProgram.methods.withdrawSplToken(usdcDecimals,amount, Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce) .accounts({ - pdaAta: pda_ata.address, + pdaAta: pda_ata, mintAccount: mint.publicKey, to: wallet_ata, }).rpc(); - const account3 = await spl.getAccount(conn, pda_ata.address); + const account3 = await spl.getAccount(conn, pda_ata); expect(account3.amount-account2.amount).to.be.eq(-500_000n); try { (await gatewayProgram.methods.withdrawSplToken(usdcDecimals,new anchor.BN(500_000), Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce) .accounts({ - pdaAta: pda_ata.address, + pdaAta: pda_ata, mintAccount: mint.publicKey, to: wallet_ata, }).rpc()); @@ -265,7 +307,7 @@ describe("some tests", () => { } catch (err) { expect(err).to.be.instanceof(anchor.AnchorError); expect(err.message).to.include("NonceMismatch"); - const account4 = await spl.getAccount(conn, pda_ata.address); + const account4 = await spl.getAccount(conn, pda_ata); console.log("After 2nd withdraw: Account balance:", account4.amount.toString()); expect(account4.amount).to.be.eq(2_500_000n); } @@ -290,7 +332,7 @@ describe("some tests", () => { ]); await gatewayProgram.methods.withdrawSplToken(usdcDecimals,amount, Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce2 ) .accounts({ - pdaAta: pda_ata.address, + pdaAta: pda_ata, mintAccount: mint_fake.publicKey, to: wallet_ata, }).rpc(); @@ -299,7 +341,7 @@ describe("some tests", () => { expect(err).to.be.instanceof(anchor.AnchorError); console.log("Error message: ", err.message); expect(err.message).to.include("ConstraintTokenMint"); - const account4 = await spl.getAccount(conn, pda_ata.address); + const account4 = await spl.getAccount(conn, pda_ata); console.log("After 2nd withdraw: Account balance:", account4.amount.toString()); expect(account4.amount).to.be.eq(2_500_000n); } @@ -400,31 +442,7 @@ describe("some tests", () => { } }); - it("add whitelist spl token", async () => { - await gatewayProgram.methods.whitelistSplMint().accounts({ - whitelistCandidate: mint.publicKey, - }).signers([]).rpc(); - let seeds = [Buffer.from("whitelist", "utf-8"), mint.publicKey.toBuffer()]; - let [entryAddress] = anchor.web3.PublicKey.findProgramAddressSync( - seeds, - gatewayProgram.programId, - ); - let entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress) - console.log("whitelist entry", entry); - - try { - seeds = [Buffer.from("whitelist", "utf-8"), mint_fake.publicKey.toBuffer()]; - [entryAddress] = anchor.web3.PublicKey.findProgramAddressSync( - seeds, - gatewayProgram.programId, - ); - entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress); - console.log("whitelist entry", entry); - } catch(err) { - expect(err.message).to.include("Account does not exist or has no data"); - } - }); const newAuthority = anchor.web3.Keypair.generate(); it("update authority", async () => { @@ -484,3 +502,6 @@ describe("some tests", () => { }); + + + From b7a4d63c3c358d2c5800257c502e4a4cfd197864 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 22:26:09 -0500 Subject: [PATCH 08/15] add de-whitelist and its test --- programs/protocol-contracts-solana/src/lib.rs | 31 ++++- tests/protocol-contracts-solana.ts | 131 +++++++++--------- 2 files changed, 89 insertions(+), 73 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 2563788..f565ffd 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -89,10 +89,9 @@ pub mod gateway { } // whitelisting SPL tokens - pub fn whitelist_spl_mint(ctx: Context) -> Result<()> { + pub fn whitelist_spl_mint(_ctx: Context) -> Result<()> { Ok(()) } - Ok(()) - } + pub fn de_whitelist_spl_mint(_ctx: Context) -> Result<()> { Ok(()) } // deposit SOL into this program and the `receiver` on ZetaChain zEVM // will get corresponding ZRC20 credit. @@ -358,7 +357,7 @@ pub struct DepositSplToken<'info> { pub pda: Account<'info, Pda>, #[account(seeds=[b"whitelist", mint_account.key().as_ref()], bump)] - pub whitelist_entry: Account<'info, WhitelistEntry>, + pub whitelist_entry: Account<'info, WhitelistEntry>, // attach whitelist entry to show the mint_account is whitelisted pub mint_account: Account<'info, Mint>, @@ -425,9 +424,8 @@ pub struct UpdatePaused<'info> { pub signer: Signer<'info>, } -// whitelisting, dewhitelisting SPL token (mints) + #[derive(Accounts)] -// #[instruction(account_to_add: Pubkey)] pub struct AddToWhitelist<'info> { #[account( init, @@ -448,7 +446,28 @@ pub struct AddToWhitelist<'info> { pub authority: Signer<'info>, pub system_program: Program<'info, System>, +} +#[derive(Accounts)] +pub struct DeleteFromWhitelist<'info> { + #[account( + mut, + seeds=[ + b"whitelist", + whitelist_candidate.key().as_ref() + ], + bump, + close = authority, + )] + pub whitelist_entry: Account<'info, WhitelistEntry>, + pub whitelist_candidate: Account<'info, Mint>, + + #[account(mut, seeds = [b"meta"], bump, has_one = authority)] + pub pda: Account<'info, Pda>, + #[account(mut)] + pub authority: Signer<'info>, + + pub system_program: Program<'info, System>, } #[account] diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index cce5314..65faa5e 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -8,6 +8,7 @@ import { keccak256 } from 'ethereumjs-util'; import { bufferToHex } from 'ethereumjs-util'; import {expect} from 'chai'; import {ecdsaRecover} from 'secp256k1'; +import {getOrCreateAssociatedTokenAccount} from "@solana/spl-token"; @@ -41,6 +42,33 @@ async function mintSPLToken(conn: anchor.web3.Connection, wallet: anchor.web3.Ke return txsig; } +async function depositSplTokens(gatewayProgram: Program, conn: anchor.web3.Connection, wallet: anchor.web3.Keypair, mint: anchor.web3.Keypair, address: Buffer) { + let seeds = [Buffer.from("meta", "utf-8")]; + const [pdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( + seeds, + gatewayProgram.programId, + ); + console.log("gateway pda account", pdaAccount.toString()); + const pda_ata = await spl.getOrCreateAssociatedTokenAccount( + conn, + wallet, + mint.publicKey, + pdaAccount, + true + ); + console.log("pda_ata address", pda_ata.address.toString()); + + let tokenAccount = await spl.getOrCreateAssociatedTokenAccount( + conn,wallet, mint.publicKey, wallet.publicKey + ) + await gatewayProgram.methods.depositSplToken(new anchor.BN(1_000_000), Array.from(address)).accounts({ + from: tokenAccount.address, + to: pda_ata.address, + mintAccount: mint.publicKey, + }).rpc({commitment: 'processed'}); + return; +} + describe("some tests", () => { // Configure the client to use the local cluster. anchor.setProvider(anchor.AnchorProvider.env()); @@ -50,10 +78,10 @@ describe("some tests", () => { const mint = anchor.web3.Keypair.generate(); const mint_fake = anchor.web3.Keypair.generate(); // for testing purpose - let tokenAccount: spl.Account; + let wallet_ata: anchor.web3.PublicKey; let pdaAccount: anchor.web3.PublicKey; - let pda_ata: spl.Account; + const message_hash = keccak256(Buffer.from("hello world")); const signature = keyPair.sign(message_hash, 'hex'); const { r, s, recoveryParam } = signature; @@ -105,7 +133,7 @@ describe("some tests", () => { await mintSPLToken(conn, wallet, mint); // 2. create token account to receive mint - tokenAccount = await spl.getOrCreateAssociatedTokenAccount( + let tokenAccount = await spl.getOrCreateAssociatedTokenAccount( conn, wallet, mint.publicKey, @@ -165,33 +193,18 @@ describe("some tests", () => { }); it("Deposit 1_000_000 USDC to Gateway", async () => { - let seeds = [Buffer.from("meta", "utf-8")]; - [pdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( - seeds, - gatewayProgram.programId, - ); - console.log("gateway pda account", pdaAccount.toString()); - pda_ata = await spl.getOrCreateAssociatedTokenAccount( - conn, - wallet, - mint.publicKey, - pdaAccount, - true - ); - console.log("pda_ata address", pda_ata.address.toString()); - + let pda_ata = await getOrCreateAssociatedTokenAccount(conn, wallet, mint.publicKey, pdaAccount, true); let acct = await spl.getAccount(conn, pda_ata.address); let bal0 = acct.amount; - await gatewayProgram.methods.depositSplToken(new anchor.BN(1_000_000), Array.from(address)).accounts({ - from: tokenAccount.address, - to: pda_ata.address, - mintAccount: mint.publicKey, - }).rpc({commitment: 'processed'}); + await depositSplTokens(gatewayProgram, conn, wallet, mint, address); acct = await spl.getAccount(conn, pda_ata.address); let bal1 = acct.amount; expect(bal1-bal0).to.be.eq(1_000_000n); + let tokenAccount = await getOrCreateAssociatedTokenAccount( + conn,wallet, mint.publicKey, wallet.publicKey, + ) try { await gatewayProgram.methods.depositSplToken(new anchor.BN(1_000_000), Array.from(address)).accounts( { @@ -214,7 +227,7 @@ describe("some tests", () => { from: tokenAccount.address, to: pda_ata.address, mintAccount: mint.publicKey, - }).rpc({commitment: 'confirmed'}); + }).rpc({commitment: 'processed'}); acct = await spl.getAccount(conn, pda_ata.address); bal1 = acct.amount; expect(bal1-bal0).to.be.eq(2_000_000n); @@ -236,6 +249,9 @@ describe("some tests", () => { ); console.log("fake_mint fake_pda_ata address", fake_pda_ata.address.toString()); + let tokenAccount = await spl.getOrCreateAssociatedTokenAccount( + conn,wallet, mint_fake.publicKey, wallet.publicKey, true + ) try { await gatewayProgram.methods.depositSplToken(new anchor.BN(1_000_000), Array.from(address)).accounts({ from: tokenAccount.address, @@ -261,12 +277,7 @@ describe("some tests", () => { console.log(`pda account data: nonce ${pdaAccountData.nonce}`); const hexAddr = bufferToHex(Buffer.from(pdaAccountData.tssAddress)); console.log(`pda account data: tss address ${hexAddr}`); - // const message_hash = fromHexString( - // "0a1e2723bd7f1996832b7ed7406df8ad975deba1aa04020b5bfc3e6fe70ecc29" - // ); - // const signature = fromHexString( - // "58be181f57b2d56b0c252127c9874a8fbe5ebd04f7632fb3966935a3e9a765807813692cebcbf3416cb1053ad9c8c83af471ea828242cca22076dd04ddbcd253" - // ); + const amount = new anchor.BN(500_000); const nonce = pdaAccountData.nonce; const buffer = Buffer.concat([ @@ -364,7 +375,7 @@ describe("some tests", () => { // ); const nonce = pdaAccountData.nonce; const amount = new anchor.BN(500000000); - const to = pda_ata.address; + const to = await spl.getAssociatedTokenAddress(mint.publicKey, wallet.publicKey); const buffer = Buffer.concat([ Buffer.from("withdraw","utf-8"), chain_id_bn.toArrayLike(Buffer, 'be', 8), @@ -401,13 +412,33 @@ describe("some tests", () => { expect(bal2-bal1).to.be.gte(1_000_000_000); }) + it("de-whitelist SPL token and deposit should fail", async () => { + await gatewayProgram.methods.deWhitelistSplMint().accounts({ + whitelistCandidate: mint.publicKey, + }).rpc(); + + try { + await depositSplTokens(gatewayProgram, conn, wallet, mint, address) + } catch (err) { + expect(err).to.be.instanceof(anchor.AnchorError); + expect(err.message).to.include("AccountNotInitialized"); + } + }); + + it("re-whitelist SPL token and deposit should succeed", async () => { + await gatewayProgram.methods.whitelistSplMint().accounts({ + whitelistCandidate: mint.publicKey, + }).rpc(); + await depositSplTokens(gatewayProgram, conn, wallet, mint, address); + }); + it("update TSS address", async () => { const newTss = new Uint8Array(20); randomFillSync(newTss); // console.log("generated new TSS address", newTss); await gatewayProgram.methods.updateTss(Array.from(newTss)).accounts({ - pda: pdaAccount, + // pda: pdaAccount, }).rpc(); const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); // console.log("updated TSS address", pdaAccountData.tssAddress); @@ -444,6 +475,7 @@ describe("some tests", () => { + const newAuthority = anchor.web3.Keypair.generate(); it("update authority", async () => { await gatewayProgram.methods.updateAuthority(newAuthority.publicKey).accounts({ @@ -464,41 +496,6 @@ describe("some tests", () => { } }); - it("create an account owned by the gateway program", async () => { - const gateway_id =gatewayProgram.programId; - console.log("gateway program id", gateway_id.toString()); - const fake_pda = anchor.web3.Keypair.generate(); - const rentExemption = await conn.getMinimumBalanceForRentExemption(100); - const instr1 = anchor.web3.SystemProgram.createAccount( - { - fromPubkey: wallet.publicKey, - newAccountPubkey: fake_pda.publicKey, - lamports: rentExemption, - space: 100, - programId: gatewayProgram.programId, - } - ) - const tx = new anchor.web3.Transaction(); - tx.add(instr1, ); - await anchor.web3.sendAndConfirmTransaction(conn, tx, [wallet, fake_pda]); - - const newTss = new Uint8Array(20); - randomFillSync(newTss); - // console.log("generated new TSS address", newTss); - try { - // @ts-ignore - await gatewayProgram.methods.updateTss(Array.from(newTss)).accounts({ - pda: fake_pda.publicKey, - }).rpc(); - } catch (err) { - console.log("Error message: ", err.message); - expect(err).to.be.instanceof(anchor.AnchorError); - expect(err.message).to.include("AccountDiscriminatorMismatch."); - } - }); - - - }); From 1317d1c19bdfd1774374b52a7cf0d37bb3c58069 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 7 Oct 2024 22:38:30 -0500 Subject: [PATCH 09/15] fmt --- programs/protocol-contracts-solana/src/lib.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index f565ffd..2812b3d 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -89,9 +89,13 @@ pub mod gateway { } // whitelisting SPL tokens - pub fn whitelist_spl_mint(_ctx: Context) -> Result<()> { Ok(()) } + pub fn whitelist_spl_mint(_ctx: Context) -> Result<()> { + Ok(()) + } - pub fn de_whitelist_spl_mint(_ctx: Context) -> Result<()> { Ok(()) } + pub fn de_whitelist_spl_mint(_ctx: Context) -> Result<()> { + Ok(()) + } // deposit SOL into this program and the `receiver` on ZetaChain zEVM // will get corresponding ZRC20 credit. @@ -424,7 +428,6 @@ pub struct UpdatePaused<'info> { pub signer: Signer<'info>, } - #[derive(Accounts)] pub struct AddToWhitelist<'info> { #[account( @@ -480,8 +483,7 @@ pub struct Pda { } #[account] -pub struct WhitelistEntry { -} +pub struct WhitelistEntry {} #[cfg(test)] mod tests { From e905e678536ab80badbd9046137df10c02c15fcc Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:44:53 -0500 Subject: [PATCH 10/15] use whitelist/unwhitelist across the board --- programs/protocol-contracts-solana/src/lib.rs | 8 ++++---- tests/protocol-contracts-solana.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 2812b3d..6f062e7 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -89,11 +89,11 @@ pub mod gateway { } // whitelisting SPL tokens - pub fn whitelist_spl_mint(_ctx: Context) -> Result<()> { + pub fn whitelist_spl_mint(_ctx: Context) -> Result<()> { Ok(()) } - pub fn de_whitelist_spl_mint(_ctx: Context) -> Result<()> { + pub fn unwhitelist_spl_mint(_ctx: Context) -> Result<()> { Ok(()) } @@ -429,7 +429,7 @@ pub struct UpdatePaused<'info> { } #[derive(Accounts)] -pub struct AddToWhitelist<'info> { +pub struct Whitelist<'info> { #[account( init, space=8, @@ -452,7 +452,7 @@ pub struct AddToWhitelist<'info> { } #[derive(Accounts)] -pub struct DeleteFromWhitelist<'info> { +pub struct Unwhitelist<'info> { #[account( mut, seeds=[ diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 65faa5e..daec59b 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -10,7 +10,7 @@ import {expect} from 'chai'; import {ecdsaRecover} from 'secp256k1'; import {getOrCreateAssociatedTokenAccount} from "@solana/spl-token"; - +console.log = function() {} const ec = new EC('secp256k1'); // const keyPair = ec.genKeyPair(); @@ -412,8 +412,8 @@ describe("some tests", () => { expect(bal2-bal1).to.be.gte(1_000_000_000); }) - it("de-whitelist SPL token and deposit should fail", async () => { - await gatewayProgram.methods.deWhitelistSplMint().accounts({ + it("unwhitelist SPL token and deposit should fail", async () => { + await gatewayProgram.methods.unwhitelistSplMint().accounts({ whitelistCandidate: mint.publicKey, }).rpc(); From dba533d779c9283e4018abd076c5895130c862f3 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:10:13 -0500 Subject: [PATCH 11/15] refactor test --- tests/protocol-contracts-solana.ts | 66 +++++++++++++++--------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index daec59b..34d7be6 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -18,6 +18,8 @@ const ec = new EC('secp256k1'); const keyPair = ec.keyFromPrivate('5b81cdf52ba0766983acf8dd0072904733d92afe4dd3499e83e879b43ccb73e8'); const usdcDecimals = 6; +const chain_id = 111111; +const chain_id_bn = new anchor.BN(chain_id); async function mintSPLToken(conn: anchor.web3.Connection, wallet: anchor.web3.Keypair, mint: anchor.web3.Keypair) { const mintRent = await spl.getMinimumBalanceForRentExemptMint(conn); @@ -68,6 +70,30 @@ async function depositSplTokens(gatewayProgram: Program, conn: anchor.w }).rpc({commitment: 'processed'}); return; } +async function withdrawSplToken(mint, decimals, amount, nonce,from, to, tssKey, gatewayProgram) { + const buffer = Buffer.concat([ + Buffer.from("withdraw_spl_token","utf-8"), + chain_id_bn.toArrayLike(Buffer, 'be', 8), + nonce.toArrayLike(Buffer, 'be', 8), + amount.toArrayLike(Buffer, 'be', 8), + mint.publicKey.toBuffer(), + to.toBuffer(), + ]); + const message_hash = keccak256(buffer); + const signature = keyPair.sign(message_hash, 'hex'); + const { r, s, recoveryParam } = signature; + const signatureBuffer = Buffer.concat([ + r.toArrayLike(Buffer, 'be', 32), + s.toArrayLike(Buffer, 'be', 32), + ]); + return gatewayProgram.methods.withdrawSplToken(decimals, amount, Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce) + .accounts({ + pdaAta: from, + mintAccount: mint.publicKey, + to: to, + }).rpc(); +} + describe("some tests", () => { // Configure the client to use the local cluster. @@ -103,8 +129,7 @@ describe("some tests", () => { const tssAddress = Array.from(address); console.log("tss address", tssAddress); - const chain_id = 111111; - const chain_id_bn = new anchor.BN(chain_id); + let seeds = [Buffer.from("meta", "utf-8")]; [pdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( @@ -277,43 +302,16 @@ describe("some tests", () => { console.log(`pda account data: nonce ${pdaAccountData.nonce}`); const hexAddr = bufferToHex(Buffer.from(pdaAccountData.tssAddress)); console.log(`pda account data: tss address ${hexAddr}`); - const amount = new anchor.BN(500_000); const nonce = pdaAccountData.nonce; - const buffer = Buffer.concat([ - Buffer.from("withdraw_spl_token","utf-8"), - chain_id_bn.toArrayLike(Buffer, 'be', 8), - nonce.toArrayLike(Buffer, 'be', 8), - amount.toArrayLike(Buffer, 'be', 8), - mint.publicKey.toBuffer(), - wallet_ata.toBuffer(), - ]); - const message_hash = keccak256(buffer); - const signature = keyPair.sign(message_hash, 'hex'); - const { r, s, recoveryParam } = signature; - const signatureBuffer = Buffer.concat([ - r.toArrayLike(Buffer, 'be', 32), - s.toArrayLike(Buffer, 'be', 32), - ]); - - await gatewayProgram.methods.withdrawSplToken(usdcDecimals,amount, Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce) - .accounts({ - pdaAta: pda_ata, - mintAccount: mint.publicKey, - to: wallet_ata, - }).rpc(); - + await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, wallet_ata, keyPair, gatewayProgram); const account3 = await spl.getAccount(conn, pda_ata); expect(account3.amount-account2.amount).to.be.eq(-500_000n); + // should trigger nonce mismatch in withdraw try { - (await gatewayProgram.methods.withdrawSplToken(usdcDecimals,new anchor.BN(500_000), Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce) - .accounts({ - pdaAta: pda_ata, - mintAccount: mint.publicKey, - to: wallet_ata, - }).rpc()); + await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, wallet_ata, keyPair, gatewayProgram); throw new Error("Expected error not thrown"); // This line will make the test fail if no error is thrown } catch (err) { expect(err).to.be.instanceof(anchor.AnchorError); @@ -359,6 +357,10 @@ describe("some tests", () => { }); + it("withdraw SPL token to a non-existent account", async () => { + + }); + it("deposit and withdraw 0.5 SOL from Gateway with ECDSA signature", async () => { await gatewayProgram.methods.deposit(new anchor.BN(1_000_000_000), Array.from(address)).accounts({}).rpc(); let bal1 = await conn.getBalance(pdaAccount); From 2998d7407c23c5b5a082c4898e1ef1b12dcb9252 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:14:12 -0500 Subject: [PATCH 12/15] cleanup the console logs --- tests/protocol-contracts-solana.ts | 51 ++++++------------------------ 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 34d7be6..fc0282d 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -10,8 +10,6 @@ import {expect} from 'chai'; import {ecdsaRecover} from 'secp256k1'; import {getOrCreateAssociatedTokenAccount} from "@solana/spl-token"; -console.log = function() {} - const ec = new EC('secp256k1'); // const keyPair = ec.genKeyPair(); // read private key from hex dump @@ -40,7 +38,6 @@ async function mintSPLToken(conn: anchor.web3.Connection, wallet: anchor.web3.Ke ) ); const txsig = await anchor.web3.sendAndConfirmTransaction(conn, tokenTransaction, [wallet, mint]); - console.log("mint account created!", mint.publicKey.toString()); return txsig; } @@ -50,7 +47,6 @@ async function depositSplTokens(gatewayProgram: Program, conn: anchor.w seeds, gatewayProgram.programId, ); - console.log("gateway pda account", pdaAccount.toString()); const pda_ata = await spl.getOrCreateAssociatedTokenAccount( conn, wallet, @@ -58,7 +54,6 @@ async function depositSplTokens(gatewayProgram: Program, conn: anchor.w pdaAccount, true ); - console.log("pda_ata address", pda_ata.address.toString()); let tokenAccount = await spl.getOrCreateAssociatedTokenAccount( conn,wallet, mint.publicKey, wallet.publicKey @@ -111,23 +106,18 @@ describe("some tests", () => { const message_hash = keccak256(Buffer.from("hello world")); const signature = keyPair.sign(message_hash, 'hex'); const { r, s, recoveryParam } = signature; - console.log("r", recoveryParam); const signatureBuffer = Buffer.concat([ r.toArrayLike(Buffer, 'be', 32), s.toArrayLike(Buffer, 'be', 32), ]); const recoveredPubkey = ecdsaRecover(signatureBuffer, recoveryParam, message_hash, false); - console.log("recovered pubkey ", bufferToHex(Buffer.from(recoveredPubkey))); const publicKeyBuffer = Buffer.from(keyPair.getPublic(false, 'hex').slice(2), 'hex'); // Uncompressed form of public key, remove the '04' prefix - console.log("generated public key", bufferToHex(publicKeyBuffer)); + const addressBuffer = keccak256(publicKeyBuffer); // Skip the first byte (format indicator) const address = addressBuffer.slice(-20); - console.log("address", bufferToHex(address)); - // console.log("address", address); - // const tssAddress = [239, 36, 74, 232, 12, 58, 220, 53, 101, 185, 127, 45, 0, 144, 15, 163, 104, 163, 74, 178,]; const tssAddress = Array.from(address); - console.log("tss address", tssAddress); + @@ -146,7 +136,6 @@ describe("some tests", () => { throw new Error("Expected error not thrown"); // This line will make the test fail if no error is thrown } catch (err) { expect(err).to.be.not.null; - // console.log("Error message: ", err.message) } }); @@ -174,21 +163,16 @@ describe("some tests", () => { ) ); await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, mintToTransaction, [wallet]); - console.log("Minted 10 USDC to:", tokenAccount.address.toString()); const account = await spl.getAccount(conn, tokenAccount.address); - console.log("Account balance:", account.amount.toString()); - console.log("Account owner: ", account.owner.toString()); // OK; transfer some USDC SPL token to the gateway PDA wallet_ata = await spl.getAssociatedTokenAddress( mint.publicKey, wallet.publicKey, ); - console.log(`wallet_ata: ${wallet_ata.toString()}`); // create a fake USDC token account await mintSPLToken(conn, wallet, mint_fake); - console.log("fake mint account created!", mint_fake.publicKey.toString()); }) it("whitelist USDC spl token", async () => { @@ -202,7 +186,6 @@ describe("some tests", () => { gatewayProgram.programId, ); let entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress) - console.log("whitelist entry", entry); try { seeds = [Buffer.from("whitelist", "utf-8"), mint_fake.publicKey.toBuffer()]; @@ -211,7 +194,6 @@ describe("some tests", () => { gatewayProgram.programId, ); entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress); - console.log("whitelist entry", entry); } catch(err) { expect(err.message).to.include("Account does not exist or has no data"); } @@ -242,7 +224,6 @@ describe("some tests", () => { } catch (err) { expect(err).to.be.instanceof(anchor.AnchorError); expect(err.message).to.include("DepositToAddressMismatch"); - // console.log("Error message: ", err.message); } // test depositSplTokenAndCall @@ -264,7 +245,6 @@ describe("some tests", () => { seeds, gatewayProgram.programId, ); - console.log("gateway pda account", pdaAccount.toString()); let fake_pda_ata = await spl.getOrCreateAssociatedTokenAccount( conn, wallet, @@ -272,7 +252,6 @@ describe("some tests", () => { pdaAccount, true ); - console.log("fake_mint fake_pda_ata address", fake_pda_ata.address.toString()); let tokenAccount = await spl.getOrCreateAssociatedTokenAccount( conn,wallet, mint_fake.publicKey, wallet.publicKey, true @@ -292,16 +271,11 @@ describe("some tests", () => { it("Withdraw 500_000 USDC from Gateway with ECDSA signature", async () => { let pda_ata = await spl.getAssociatedTokenAddress(mint.publicKey, pdaAccount, true); - const account2 = await spl.getAccount(conn, pda_ata); // expect(account2.amount).to.be.eq(1_000_000n); - console.log("B4 withdraw: Account balance:", account2.amount.toString()); - const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); - console.log(`pda account data: nonce ${pdaAccountData.nonce}`); const hexAddr = bufferToHex(Buffer.from(pdaAccountData.tssAddress)); - console.log(`pda account data: tss address ${hexAddr}`); const amount = new anchor.BN(500_000); const nonce = pdaAccountData.nonce; await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, wallet_ata, keyPair, gatewayProgram); @@ -317,7 +291,6 @@ describe("some tests", () => { expect(err).to.be.instanceof(anchor.AnchorError); expect(err.message).to.include("NonceMismatch"); const account4 = await spl.getAccount(conn, pda_ata); - console.log("After 2nd withdraw: Account balance:", account4.amount.toString()); expect(account4.amount).to.be.eq(2_500_000n); } @@ -348,27 +321,30 @@ describe("some tests", () => { throw new Error("Expected error not thrown"); // This line will make the test fail if no error is thrown } catch (err) { expect(err).to.be.instanceof(anchor.AnchorError); - console.log("Error message: ", err.message); expect(err.message).to.include("ConstraintTokenMint"); const account4 = await spl.getAccount(conn, pda_ata); - console.log("After 2nd withdraw: Account balance:", account4.amount.toString()); expect(account4.amount).to.be.eq(2_500_000n); } }); it("withdraw SPL token to a non-existent account", async () => { + let pda_ata = await spl.getAssociatedTokenAddress(mint.publicKey, pdaAccount, true); + const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); + const hexAddr = bufferToHex(Buffer.from(pdaAccountData.tssAddress)); + const amount = new anchor.BN(500_000); + const nonce = pdaAccountData.nonce; + const wallet2 = anchor.web3.Keypair.generate(); + const to = await spl.getAssociatedTokenAddress(mint.publicKey, wallet2.publicKey); + await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, to, keyPair, gatewayProgram); }); it("deposit and withdraw 0.5 SOL from Gateway with ECDSA signature", async () => { await gatewayProgram.methods.deposit(new anchor.BN(1_000_000_000), Array.from(address)).accounts({}).rpc(); let bal1 = await conn.getBalance(pdaAccount); - console.log("pda account balance", bal1); expect(bal1).to.be.gte(1_000_000_000); - const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); - console.log(`pda account data: nonce ${pdaAccountData.nonce}`); // const message_hash = fromHexString( // "0a1e2723bd7f1996832b7ed7406df8ad975deba1aa04020b5bfc3e6fe70ecc29" // ); @@ -399,7 +375,6 @@ describe("some tests", () => { to: to, }).rpc(); let bal2 = await conn.getBalance(pdaAccount); - console.log("pda account balance", bal2); expect(bal2).to.be.eq(bal1 - 500_000_000); let bal3 = await conn.getBalance(to); expect(bal3).to.be.gte(500_000_000); @@ -409,7 +384,6 @@ describe("some tests", () => { let bal1 = await conn.getBalance(pdaAccount); const txsig = await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000_000), Array.from(address), Buffer.from("hello", "utf-8")).accounts({}).rpc({commitment: 'processed'}); const tx = await conn.getParsedTransaction(txsig, 'confirmed'); - console.log("deposit and call parsed tx", tx); let bal2 = await conn.getBalance(pdaAccount); expect(bal2-bal1).to.be.gte(1_000_000_000); }) @@ -438,12 +412,10 @@ describe("some tests", () => { it("update TSS address", async () => { const newTss = new Uint8Array(20); randomFillSync(newTss); - // console.log("generated new TSS address", newTss); await gatewayProgram.methods.updateTss(Array.from(newTss)).accounts({ // pda: pdaAccount, }).rpc(); const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); - // console.log("updated TSS address", pdaAccountData.tssAddress); expect(pdaAccountData.tssAddress).to.be.deep.eq(Array.from(newTss)); // only the authority stored in PDA can update the TSS address; the following should fail @@ -460,7 +432,6 @@ describe("some tests", () => { it("pause deposit and deposit should fail", async () => { const newTss = new Uint8Array(20); randomFillSync(newTss); - // console.log("generated new TSS address", newTss); await gatewayProgram.methods.setDepositPaused(true).accounts({ }).rpc(); @@ -469,7 +440,6 @@ describe("some tests", () => { try { await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000), Array.from(address), Buffer.from('hi', 'utf-8')).accounts({}).rpc(); } catch (err) { - console.log("Error message: ", err.message); expect(err).to.be.instanceof(anchor.AnchorError); expect(err.message).to.include("DepositPaused"); } @@ -492,7 +462,6 @@ describe("some tests", () => { }).rpc(); } catch (err) { - console.log("Error message: ", err.message); expect(err).to.be.instanceof(anchor.AnchorError); expect(err.message).to.include("SignerIsNotAuthority"); } From da4ca30a661b39748682192e677574dd87d9f2e9 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:36:18 -0500 Subject: [PATCH 13/15] WIP: allow withdraw SPL token to non-existent token account by creating the account first. Note: signer of the tx pays the rent for the account creation. May need to reconsider. --- programs/protocol-contracts-solana/Cargo.toml | 2 +- programs/protocol-contracts-solana/src/lib.rs | 20 +++++++++++++------ tests/protocol-contracts-solana.ts | 14 +++++++------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/programs/protocol-contracts-solana/Cargo.toml b/programs/protocol-contracts-solana/Cargo.toml index f22c7b5..bff2427 100644 --- a/programs/protocol-contracts-solana/Cargo.toml +++ b/programs/protocol-contracts-solana/Cargo.toml @@ -17,7 +17,7 @@ no-log-ix-name = [] idl-build = ["anchor-lang/idl-build"] [dependencies] -anchor-lang = { version = "=0.30.0" } +anchor-lang = { version = "=0.30.0", features = ["init-if-needed"] } anchor-spl = { version = "=0.30.0" , features = ["idl-build"]} anchor-syn = "=0.30.0" spl-associated-token-account = "3.0.2" diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 6f062e7..3640666 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; use anchor_lang::system_program; -use anchor_spl::associated_token::get_associated_token_address; +use anchor_spl::associated_token::{AssociatedToken, get_associated_token_address}; use anchor_spl::token::{transfer, transfer_checked, Mint, Token, TokenAccount}; use solana_program::keccak::hash; use solana_program::secp256k1_recover::secp256k1_recover; @@ -267,7 +267,7 @@ pub mod gateway { concatenated_buffer.extend_from_slice(&nonce.to_be_bytes()); concatenated_buffer.extend_from_slice(&amount.to_be_bytes()); concatenated_buffer.extend_from_slice(&ctx.accounts.mint_account.key().to_bytes()); - concatenated_buffer.extend_from_slice(&ctx.accounts.to.key().to_bytes()); + concatenated_buffer.extend_from_slice(&ctx.accounts.recipient_ata.key().to_bytes()); require!( message_hash == hash(&concatenated_buffer[..]).to_bytes(), Errors::MessageHashMismatch @@ -297,7 +297,7 @@ pub mod gateway { anchor_spl::token::TransferChecked { from: ctx.accounts.pda_ata.to_account_info(), mint: ctx.accounts.mint_account.to_account_info(), - to: ctx.accounts.to.to_account_info(), + to: ctx.accounts.recipient_ata.to_account_info(), authority: pda.to_account_info(), }, signer_seeds, @@ -393,15 +393,23 @@ pub struct WithdrawSPLToken<'info> { #[account(mut, seeds = [b"meta"], bump)] pub pda: Account<'info, Pda>, - #[account(mut, token::mint = mint_account, token::authority = pda)] + #[account(mut, associated_token::mint = mint_account, associated_token::authority = pda)] pub pda_ata: Account<'info, TokenAccount>, // associated token address of PDA pub mint_account: Account<'info, Mint>, - #[account(mut)] - pub to: Account<'info, TokenAccount>, + pub recipient: SystemAccount<'info>, + #[account( + init_if_needed, + payer = signer, + associated_token::mint = mint_account, + associated_token::authority = recipient, + )] + pub recipient_ata: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, } #[derive(Accounts)] diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index fc0282d..808fac5 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -65,7 +65,7 @@ async function depositSplTokens(gatewayProgram: Program, conn: anchor.w }).rpc({commitment: 'processed'}); return; } -async function withdrawSplToken(mint, decimals, amount, nonce,from, to, tssKey, gatewayProgram) { +async function withdrawSplToken(mint, decimals, amount, nonce,from, to, to_owner, tssKey, gatewayProgram) { const buffer = Buffer.concat([ Buffer.from("withdraw_spl_token","utf-8"), chain_id_bn.toArrayLike(Buffer, 'be', 8), @@ -85,7 +85,8 @@ async function withdrawSplToken(mint, decimals, amount, nonce,from, to, tssKey, .accounts({ pdaAta: from, mintAccount: mint.publicKey, - to: to, + recipientAta: to, + recipient: to_owner, }).rpc(); } @@ -278,14 +279,14 @@ describe("some tests", () => { const hexAddr = bufferToHex(Buffer.from(pdaAccountData.tssAddress)); const amount = new anchor.BN(500_000); const nonce = pdaAccountData.nonce; - await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, wallet_ata, keyPair, gatewayProgram); + await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, wallet_ata, wallet.publicKey, keyPair, gatewayProgram); const account3 = await spl.getAccount(conn, pda_ata); expect(account3.amount-account2.amount).to.be.eq(-500_000n); // should trigger nonce mismatch in withdraw try { - await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, wallet_ata, keyPair, gatewayProgram); + await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, wallet_ata, wallet.publicKey, keyPair, gatewayProgram); throw new Error("Expected error not thrown"); // This line will make the test fail if no error is thrown } catch (err) { expect(err).to.be.instanceof(anchor.AnchorError); @@ -316,7 +317,8 @@ describe("some tests", () => { .accounts({ pdaAta: pda_ata, mintAccount: mint_fake.publicKey, - to: wallet_ata, + recipientAta: wallet_ata, + recipient: wallet.publicKey, }).rpc(); throw new Error("Expected error not thrown"); // This line will make the test fail if no error is thrown } catch (err) { @@ -336,7 +338,7 @@ describe("some tests", () => { const nonce = pdaAccountData.nonce; const wallet2 = anchor.web3.Keypair.generate(); const to = await spl.getAssociatedTokenAddress(mint.publicKey, wallet2.publicKey); - await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, to, keyPair, gatewayProgram); + await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, to, wallet2.publicKey, keyPair, gatewayProgram); }); From 68f5fefd6133084c3a5f0a5499fa6c5c49fd2eee Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:03:05 -0500 Subject: [PATCH 14/15] create recipient ata account if non-existent; refund rent to signer from program rent_payer_pda --- programs/protocol-contracts-solana/src/lib.rs | 103 ++++++++++++++++-- tests/protocol-contracts-solana.ts | 40 +++++-- 2 files changed, 124 insertions(+), 19 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 3640666..edd3280 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -1,9 +1,12 @@ use anchor_lang::prelude::*; use anchor_lang::system_program; -use anchor_spl::associated_token::{AssociatedToken, get_associated_token_address}; +use anchor_spl::associated_token::{get_associated_token_address, AssociatedToken}; use anchor_spl::token::{transfer, transfer_checked, Mint, Token, TokenAccount}; use solana_program::keccak::hash; +use solana_program::program::invoke; use solana_program::secp256k1_recover::secp256k1_recover; +use solana_program::sysvar::{rent::Rent, Sysvar}; +use spl_associated_token_account::instruction::create_associated_token_account; use std::mem::size_of; #[error_code] @@ -97,6 +100,10 @@ pub mod gateway { Ok(()) } + pub fn initialize_rent_payer(_ctx: Context) -> Result<()> { + Ok(()) + } + // deposit SOL into this program and the `receiver` on ZetaChain zEVM // will get corresponding ZRC20 credit. // amount: amount of lamports (10^-9 SOL) to deposit @@ -286,11 +293,78 @@ pub mod gateway { let pda_ata = get_associated_token_address(&pda.key(), &ctx.accounts.mint_account.key()); require!( pda_ata == ctx.accounts.pda_ata.to_account_info().key(), - Errors::SPLAtaAndMintAddressMismatch + Errors::SPLAtaAndMintAddressMismatch, ); let token = &ctx.accounts.token_program; let signer_seeds: &[&[&[u8]]] = &[&[b"meta", &[ctx.bumps.pda]]]; + // make sure that ctx.accounts.recipient_ata is ATA (PDA account of token program) + let recipient_ata = get_associated_token_address( + &ctx.accounts.recipient.key(), + &ctx.accounts.mint_account.key(), + ); + require!( + recipient_ata == ctx.accounts.recipient_ata.to_account_info().key(), + Errors::SPLAtaAndMintAddressMismatch, + ); + // DEBUG + let lamports = ctx.accounts.recipient.to_account_info().lamports(); + msg!( + "recipient {:?} has lamports {:?}", + ctx.accounts.recipient.to_account_info().key(), + lamports, + ); + // END DEBUG + + // test whether the recipient_ata is created or not; if not, create it + let recipient_ata_account = ctx.accounts.recipient_ata.to_account_info(); + if recipient_ata_account.lamports() == 0 + || *recipient_ata_account.owner == ctx.accounts.system_program.key() + { + // if lamports of recipient_ata_account is 0 or its owner being system program then it's not created + msg!( + "Creating associated token account {:?} for recipient {:?}...", + recipient_ata_account.key(), + ctx.accounts.recipient.key(), + ); + let signer_info = &ctx.accounts.signer.to_account_info(); + let bal0 = signer_info.lamports(); + invoke( + &create_associated_token_account( + ctx.accounts.signer.to_account_info().key, + ctx.accounts.recipient.to_account_info().key, + ctx.accounts.mint_account.to_account_info().key, + ctx.accounts.token_program.key, + ), + &[ + ctx.accounts.mint_account.to_account_info().clone(), + ctx.accounts.recipient_ata.clone(), + ctx.accounts.recipient.to_account_info().clone(), + ctx.accounts.signer.to_account_info().clone(), + ctx.accounts.system_program.to_account_info().clone(), + ctx.accounts.token_program.to_account_info().clone(), + ctx.accounts + .associated_token_program + .to_account_info() + .clone(), + ], + )?; + let bal1 = signer_info.lamports(); + + msg!("Associated token account for recipient created!"); + msg!( + "Refunding the rent paid by the signer {:?}", + ctx.accounts.signer.to_account_info().key + ); + + let rent_payer_info = ctx.accounts.rent_payer_pda.to_account_info(); + rent_payer_info.sub_lamports(bal0 - bal1)?; + signer_info.add_lamports(bal0 - bal1)?; + msg!( + "Signer refunded the ATA account creation rent amount {:?} lamports", + bal0 - bal1 + ); + } let xfer_ctx = CpiContext::new_with_signer( token.to_account_info(), @@ -399,14 +473,13 @@ pub struct WithdrawSPLToken<'info> { pub mint_account: Account<'info, Mint>, pub recipient: SystemAccount<'info>, - #[account( - init_if_needed, - payer = signer, - associated_token::mint = mint_account, - associated_token::authority = recipient, - )] - pub recipient_ata: Account<'info, TokenAccount>, + /// CHECK: recipient_ata might not have been created; avoid checking its content. + /// the validation will be done in the instruction processor. + #[account(mut)] + pub recipient_ata: AccountInfo<'info>, + #[account(mut, seeds = [b"rent-payer"], bump)] + pub rent_payer_pda: Account<'info, RentPayerPda>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, @@ -481,6 +554,15 @@ pub struct Unwhitelist<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct InitializeRentPayer<'info> { + #[account(init, payer = authority, space = 8, seeds = [b"rent-payer"], bump)] + pub rent_payer_pda: Account<'info, RentPayerPda>, + #[account(mut)] + pub authority: Signer<'info>, + pub system_program: Program<'info, System>, +} + #[account] pub struct Pda { nonce: u64, // ensure that each signature can only be used once @@ -493,6 +575,9 @@ pub struct Pda { #[account] pub struct WhitelistEntry {} +#[account] +pub struct RentPayerPda {} + #[cfg(test)] mod tests { use super::*; diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 808fac5..d8d849f 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -65,7 +65,7 @@ async function depositSplTokens(gatewayProgram: Program, conn: anchor.w }).rpc({commitment: 'processed'}); return; } -async function withdrawSplToken(mint, decimals, amount, nonce,from, to, to_owner, tssKey, gatewayProgram) { +async function withdrawSplToken( mint, decimals, amount, nonce,from, to, to_owner, tssKey, gatewayProgram: Program) { const buffer = Buffer.concat([ Buffer.from("withdraw_spl_token","utf-8"), chain_id_bn.toArrayLike(Buffer, 'be', 8), @@ -87,7 +87,8 @@ async function withdrawSplToken(mint, decimals, amount, nonce,from, to, to_owner mintAccount: mint.publicKey, recipientAta: to, recipient: to_owner, - }).rpc(); + + }).rpc({commitment: 'processed'}); } @@ -114,20 +115,22 @@ describe("some tests", () => { const recoveredPubkey = ecdsaRecover(signatureBuffer, recoveryParam, message_hash, false); const publicKeyBuffer = Buffer.from(keyPair.getPublic(false, 'hex').slice(2), 'hex'); // Uncompressed form of public key, remove the '04' prefix - const addressBuffer = keccak256(publicKeyBuffer); // Skip the first byte (format indicator) const address = addressBuffer.slice(-20); const tssAddress = Array.from(address); - - - let seeds = [Buffer.from("meta", "utf-8")]; [pdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( seeds, gatewayProgram.programId, ); + let rentPayerSeeds = [Buffer.from("rent-payer", "utf-8")]; + let [rentPayerPdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( + rentPayerSeeds, + gatewayProgram.programId, + ); + it("Initializes the program", async () => { await gatewayProgram.methods.initialize(tssAddress, chain_id_bn).rpc(); @@ -139,7 +142,17 @@ describe("some tests", () => { expect(err).to.be.not.null; } }); - + it("intialize the rent payer PDA",async() => { + await gatewayProgram.methods.initializeRentPayer().rpc(); + let instr = web3.SystemProgram.transfer({ + fromPubkey: wallet.publicKey, + toPubkey: rentPayerPdaAccount, + lamports: 100000000, + }); + let tx = new web3.Transaction(); + tx.add(instr); + await web3.sendAndConfirmTransaction(conn,tx,[wallet]); + }); it("Mint a SPL USDC token", async () => { @@ -323,7 +336,7 @@ describe("some tests", () => { throw new Error("Expected error not thrown"); // This line will make the test fail if no error is thrown } catch (err) { expect(err).to.be.instanceof(anchor.AnchorError); - expect(err.message).to.include("ConstraintTokenMint"); + expect(err.message).to.include("ConstraintAssociated"); const account4 = await spl.getAccount(conn, pda_ata); expect(account4.amount).to.be.eq(2_500_000n); } @@ -337,9 +350,16 @@ describe("some tests", () => { const amount = new anchor.BN(500_000); const nonce = pdaAccountData.nonce; const wallet2 = anchor.web3.Keypair.generate(); - const to = await spl.getAssociatedTokenAddress(mint.publicKey, wallet2.publicKey); - await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, to, wallet2.publicKey, keyPair, gatewayProgram); + { // fund the wallet2, otherwise the wallet2 is considered non-existent + let sig = await conn.requestAirdrop(wallet2.publicKey, 100000000); + await conn.confirmTransaction(sig); + } + + const to = await spl.getAssociatedTokenAddress(mint.publicKey, wallet2.publicKey); + console.log("wallet2 ata: ", to.toBase58()); + const txsig = await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, to, wallet2.publicKey, keyPair, gatewayProgram); + const tx = await conn.getParsedTransaction(txsig, 'confirmed'); }); it("deposit and withdraw 0.5 SOL from Gateway with ECDSA signature", async () => { From 1d5b9913b3fc50dea528febfdf9234c71f236037 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:07:01 -0500 Subject: [PATCH 15/15] cleanup --- programs/protocol-contracts-solana/Cargo.toml | 4 ++-- programs/protocol-contracts-solana/src/lib.rs | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/programs/protocol-contracts-solana/Cargo.toml b/programs/protocol-contracts-solana/Cargo.toml index bff2427..4462e31 100644 --- a/programs/protocol-contracts-solana/Cargo.toml +++ b/programs/protocol-contracts-solana/Cargo.toml @@ -17,8 +17,8 @@ no-log-ix-name = [] idl-build = ["anchor-lang/idl-build"] [dependencies] -anchor-lang = { version = "=0.30.0", features = ["init-if-needed"] } -anchor-spl = { version = "=0.30.0" , features = ["idl-build"]} +anchor-lang = { version = "=0.30.0" } +anchor-spl = { version = "=0.30.0", features = ["idl-build"] } anchor-syn = "=0.30.0" spl-associated-token-account = "3.0.2" solana-program = "=1.18.15" diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index edd3280..bbc4c09 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -307,14 +307,6 @@ pub mod gateway { recipient_ata == ctx.accounts.recipient_ata.to_account_info().key(), Errors::SPLAtaAndMintAddressMismatch, ); - // DEBUG - let lamports = ctx.accounts.recipient.to_account_info().lamports(); - msg!( - "recipient {:?} has lamports {:?}", - ctx.accounts.recipient.to_account_info().key(), - lamports, - ); - // END DEBUG // test whether the recipient_ata is created or not; if not, create it let recipient_ata_account = ctx.accounts.recipient_ata.to_account_info();