From 705831d361ce88ca83da5dd014eecdc6b24677e2 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:14:03 -0500 Subject: [PATCH 1/3] feat: whitelist and unwhitelist SPL tokens (#41) --- README.md | 4 +- programs/protocol-contracts-solana/src/lib.rs | 62 ++++ tests/protocol-contracts-solana.ts | 272 ++++++++++-------- 3 files changed, 222 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 69a1603..8fdd5c8 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,8 @@ The PDA account address (derived from seeds `b"meta"` and canonical bump) is 2f9SLuUNb7TNeM6gzBwT4ZjbL5ZyKzzHg1Ce9yiquEjj ``` - # Introduction - This repository hosts the smart contract (program) deployed on the Solana network to enable ZetaChain's cross-chain functionality. It consists of a single program that supports the following actions: 1. Users on the Solana network can send SOL to the program to deposit into ZetaChain, with the option to invoke a ZetaChain EVM contract. @@ -112,4 +110,4 @@ brew install gnu-tar # Put this in ~/.zshrc export PATH="/usr/local/opt/gnu-tar/libexec/gnubin:$PATH" ``` -see https://solana.stackexchange.com/questions/4499/blockstore-error-when-starting-solana-test-validator-on-macos-13-0-1/16319#16319 \ No newline at end of file +see https://solana.stackexchange.com/questions/4499/blockstore-error-when-starting-solana-test-validator-on-macos-13-0-1/16319#16319 diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 0c0e8e8..6f062e7 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -88,6 +88,15 @@ pub mod gateway { Ok(()) } + // whitelisting SPL tokens + pub fn whitelist_spl_mint(_ctx: Context) -> Result<()> { + Ok(()) + } + + pub fn unwhitelist_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 @@ -351,6 +360,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>, // attach whitelist entry to show the mint_account is whitelisted + + pub mint_account: Account<'info, Mint>, + pub token_program: Program<'info, Token>, #[account(mut)] @@ -414,6 +428,51 @@ pub struct UpdatePaused<'info> { pub signer: Signer<'info>, } +#[derive(Accounts)] +pub struct Whitelist<'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>, +} + +#[derive(Accounts)] +pub struct Unwhitelist<'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] pub struct Pda { nonce: u64, // ensure that each signature can only be used once @@ -423,6 +482,9 @@ 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..daec59b 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -2,15 +2,15 @@ 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'; import { bufferToHex } from 'ethereumjs-util'; 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(); @@ -19,6 +19,56 @@ 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; +} + +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()); @@ -28,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; @@ -75,31 +125,15 @@ 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( + let tokenAccount = await spl.getOrCreateAssociatedTokenAccount( conn, wallet, mint.publicKey, @@ -128,58 +162,55 @@ 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("Deposit 1_000_000 USDC to Gateway", async () => { - let seeds = [Buffer.from("meta", "utf-8")]; - [pdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( + 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, ); - 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 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 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, - }).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( { from: tokenAccount.address, to: wallet_ata, + mintAccount: mint.publicKey, } ).rpc(); throw new Error("Expected error not thrown"); @@ -195,22 +226,49 @@ 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, - }).rpc({commitment: 'confirmed'}); + mintAccount: mint.publicKey, + }).rpc({commitment: 'processed'}); 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()); + + 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, + 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()); @@ -219,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([ @@ -245,19 +298,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 +318,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 +343,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 +352,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); } @@ -322,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), @@ -352,20 +405,40 @@ 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); expect(bal2-bal1).to.be.gte(1_000_000_000); }) + it("unwhitelist SPL token and deposit should fail", async () => { + await gatewayProgram.methods.unwhitelistSplMint().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); @@ -400,8 +473,11 @@ describe("some tests", () => { } }); + + + + const newAuthority = anchor.web3.Keypair.generate(); it("update authority", async () => { - const newAuthority = anchor.web3.Keypair.generate(); await gatewayProgram.methods.updateAuthority(newAuthority.publicKey).accounts({ }).rpc(); @@ -420,39 +496,9 @@ 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 362a59f365279345c17050847e86edadfdfa7f81 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:37:21 -0500 Subject: [PATCH 2/3] feat: withdraw_spl_token: create recipient ATA account if non-existent (#55) * squash commits from branch wthdraw-spl-to-new-account * remove unnecessary funding wallet * test more state changes regarding the rent payer * clean up unused imports * fmt * Update programs/protocol-contracts-solana/src/lib.rs Co-authored-by: Lucas Bertrand * Update programs/protocol-contracts-solana/src/lib.rs Co-authored-by: Lucas Bertrand * Update programs/protocol-contracts-solana/src/lib.rs Co-authored-by: Lucas Bertrand * Update programs/protocol-contracts-solana/src/lib.rs Co-authored-by: Lucas Bertrand * fix fmt and names * fmt * add address of rent payer pda to README * remove redundant program msg!() --------- Co-authored-by: Lucas Bertrand --- README.md | 8 +- programs/protocol-contracts-solana/Cargo.toml | 2 +- programs/protocol-contracts-solana/src/lib.rs | 104 +++++++++-- tests/protocol-contracts-solana.ts | 165 +++++++++--------- 4 files changed, 180 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 8fdd5c8..51f61a8 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ The PDA account address (derived from seeds `b"meta"` and canonical bump) is 2f9SLuUNb7TNeM6gzBwT4ZjbL5ZyKzzHg1Ce9yiquEjj ``` +The PDA account address (derived from seeds `b"rent-payer") +``` +Am1aA3XQciu3vMG6E9yLa2Y9TcTf2XB3D3akLtjVzu3L +``` + + # Introduction This repository hosts the smart contract (program) deployed on the Solana network to enable ZetaChain's cross-chain functionality. It consists of a single program that supports the following actions: @@ -110,4 +116,4 @@ brew install gnu-tar # Put this in ~/.zshrc export PATH="/usr/local/opt/gnu-tar/libexec/gnubin:$PATH" ``` -see https://solana.stackexchange.com/questions/4499/blockstore-error-when-starting-solana-test-validator-on-macos-13-0-1/16319#16319 +see https://solana.stackexchange.com/questions/4499/blockstore-error-when-starting-solana-test-validator-on-macos-13-0-1/16319#16319 \ No newline at end of file diff --git a/programs/protocol-contracts-solana/Cargo.toml b/programs/protocol-contracts-solana/Cargo.toml index f22c7b5..4462e31 100644 --- a/programs/protocol-contracts-solana/Cargo.toml +++ b/programs/protocol-contracts-solana/Cargo.toml @@ -18,7 +18,7 @@ idl-build = ["anchor-lang/idl-build"] [dependencies] anchor-lang = { version = "=0.30.0" } -anchor-spl = { version = "=0.30.0" , features = ["idl-build"]} +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 6f062e7..c0b87d0 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -1,9 +1,11 @@ use anchor_lang::prelude::*; use anchor_lang::system_program; -use anchor_spl::associated_token::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 spl_associated_token_account::instruction::create_associated_token_account; use std::mem::size_of; #[error_code] @@ -97,6 +99,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 @@ -158,10 +164,7 @@ pub mod gateway { let pda = &mut ctx.accounts.pda; require!(!pda.deposit_paused, Errors::DepositPaused); - let pda_ata = spl_associated_token_account::get_associated_token_address( - &ctx.accounts.pda.key(), - &from.mint, - ); + let pda_ata = get_associated_token_address(&ctx.accounts.pda.key(), &from.mint); // must deposit to the ATA from PDA in order to receive credit require!( pda_ata == ctx.accounts.to.to_account_info().key(), @@ -228,7 +231,6 @@ pub mod gateway { ); let address = recover_eth_address(&message_hash, recovery_id, &signature)?; // ethereum address is the last 20 Bytes of the hashed pubkey - msg!("recovered address {:?}", address); if address != pda.tss_address { msg!("ECDSA signature error"); return err!(Errors::TSSAuthenticationFailed); @@ -267,7 +269,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 @@ -286,18 +288,79 @@ 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, + ); + + // 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 bal_before = 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 bal_after = 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(); + let cost = bal_before - bal_after; + rent_payer_info.sub_lamports(cost)?; + signer_info.add_lamports(cost)?; + msg!( + "Signer refunded the ATA account creation rent amount {:?} lamports", + cost, + ); + } + let xfer_ctx = CpiContext::new_with_signer( token.to_account_info(), 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 +456,22 @@ 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>, + pub recipient: SystemAccount<'info>, + /// CHECK: recipient_ata might not have been created; avoid checking its content. + /// the validation will be done in the instruction processor. #[account(mut)] - pub to: Account<'info, TokenAccount>, + 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>, } #[derive(Accounts)] @@ -473,6 +543,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 @@ -485,6 +564,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 daec59b..898cae9 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -10,14 +10,14 @@ 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 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); @@ -38,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; } @@ -48,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, @@ -56,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 @@ -68,6 +65,32 @@ 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: Program) { + 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, + recipientAta: to, + recipient: to_owner, + + }).rpc({commitment: 'processed'}); +} + describe("some tests", () => { // Configure the client to use the local cluster. @@ -85,26 +108,16 @@ 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); - - 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( @@ -112,6 +125,12 @@ describe("some tests", () => { 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(); @@ -121,10 +140,19 @@ 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) } }); - + it("initialize 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 () => { @@ -149,21 +177,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 () => { @@ -177,7 +200,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()]; @@ -186,7 +208,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"); } @@ -217,7 +238,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 @@ -239,7 +259,6 @@ describe("some tests", () => { seeds, gatewayProgram.programId, ); - console.log("gateway pda account", pdaAccount.toString()); let fake_pda_ata = await spl.getOrCreateAssociatedTokenAccount( conn, wallet, @@ -247,7 +266,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 @@ -267,59 +285,26 @@ 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; - 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, 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 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, 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); 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); } @@ -345,34 +330,52 @@ 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) { expect(err).to.be.instanceof(anchor.AnchorError); - console.log("Error message: ", err.message); - expect(err.message).to.include("ConstraintTokenMint"); + expect(err.message).to.include("ConstraintAssociated"); 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 should succeed by creating it", async () => { + let seeds = [Buffer.from("rent-payer", "utf-8")]; + const [rentPayerPda] = anchor.web3.PublicKey.findProgramAddressSync( + seeds, + gatewayProgram.programId, + ); + let rentPayerPdaBal0 = await conn.getBalance(rentPayerPda); + 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); + + let to_ata_bal = await conn.getBalance(to); + expect(to_ata_bal).to.be.eq(0); // the new ata account (owned by wallet2) should be non-existent; + const txsig = await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, to, wallet2.publicKey, keyPair, gatewayProgram); + to_ata_bal = await conn.getBalance(to); + expect(to_ata_bal).to.be.gt(2_000_000); // the new ata account (owned by wallet2) should be created + + // rent_payer_pda should have reduced balance + + let rentPayerPdaBal1 = await conn.getBalance(rentPayerPda); + expect(rentPayerPdaBal0-rentPayerPdaBal1).to.be.eq(to_ata_bal); // rentPayer pays rent + }); + 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" - // ); - // const signature = fromHexString( - // "58be181f57b2d56b0c252127c9874a8fbe5ebd04f7632fb3966935a3e9a765807813692cebcbf3416cb1053ad9c8c83af471ea828242cca22076dd04ddbcd253" - // ); const nonce = pdaAccountData.nonce; const amount = new anchor.BN(500000000); const to = await spl.getAssociatedTokenAddress(mint.publicKey, wallet.publicKey); @@ -397,7 +400,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); @@ -407,7 +409,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); }) @@ -436,12 +437,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 @@ -458,7 +457,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(); @@ -467,7 +465,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"); } @@ -481,16 +478,12 @@ describe("some tests", () => { await gatewayProgram.methods.updateAuthority(newAuthority.publicKey).accounts({ }).rpc(); - // const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount); - // expect(pdaAccountData.authority).to.be.eq(newAuthority.publicKey); - // now the old authority cannot update TSS address and will fail try { await gatewayProgram.methods.updateTss(Array.from(new Uint8Array(20))).accounts({ }).rpc(); } catch (err) { - console.log("Error message: ", err.message); expect(err).to.be.instanceof(anchor.AnchorError); expect(err.message).to.include("SignerIsNotAuthority"); } From 636707332576d578cf3d51d1268047199c6f2f65 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Mon, 21 Oct 2024 09:40:00 +0300 Subject: [PATCH 3/3] ci: publish to npm fix (#57) --- .github/workflows/publish-npm.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index d1cc4f0..a8b9745 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -22,12 +22,11 @@ jobs: solana-cli-version: '1.18.15' node-version: '21.0.0' - - name: Setup Node.js for NPM Publish + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '21.0.0' - registry-url: 'https://registry.npmjs.org' - auth-token: ${{ secrets.NPM_TOKEN }} + node-version: "21" + registry-url: "https://registry.npmjs.org" - name: Build run: anchor build @@ -38,18 +37,19 @@ jobs: - name: Determine NPM Tag id: determine-npm-tag run: | - VERSION_TAG=${{ github.ref_name }} + VERSION_TAG=${GITHUB_REF#refs/tags/v} if [[ $VERSION_TAG == *"-"* ]]; then - NPM_TAG=${VERSION_TAG#*-} + echo ::set-output name=NPM_TAG::${VERSION_TAG#*-} else - NPM_TAG=latest + echo ::set-output name=NPM_TAG::latest fi - echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT" + env: + GITHUB_REF: ${{ github.ref }} - name: Publish to NPM run: | - yarn publish \ - --access public \ - --new-version "${{ github.ref_name }}" \ - --tag "${{ steps.determine-npm-tag.outputs.NPM_TAG }}" \ - --no-git-tag-version + yarn publish --access public --new-version ${GITHUB_REF#refs/tags/v} --tag ${{ + steps.determine-npm-tag.outputs.NPM_TAG }} --no-git-tag-version + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_REF: ${{ github.ref }}