From 9118d4cd018865c1a9d60c106311d2fa6597288c Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:32:59 -0600 Subject: [PATCH 1/7] have PDA reimburse the ATA creation fee; assuming the withdrawal already paid at least this much in ZRC20 withdraw fee. --- programs/protocol-contracts-solana/src/lib.rs | 28 +++++---- tests/protocol-contracts-solana.ts | 60 ++++++++++--------- 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 6551fab..ae01fe5 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -338,6 +338,7 @@ pub mod gateway { message_hash: [u8; 32], nonce: u64, ) -> Result<()> { + let pda = &mut ctx.accounts.pda; // let program_id = &mut ctx.accounts if nonce != pda.nonce { @@ -386,6 +387,9 @@ pub mod gateway { Errors::SPLAtaAndMintAddressMismatch, ); + let cost_gas = 5000; // default gas cost in lamports + let cost_ata_create = &mut 2039280; // default SPL ATA account creation rent fee in lamports + // 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 @@ -397,8 +401,7 @@ pub mod gateway { recipient_ata_account.key(), ctx.accounts.recipient.key(), ); - let signer_info = &ctx.accounts.signer.to_account_info(); - let bal_before = signer_info.lamports(); + let bal_before = ctx.accounts.signer.lamports(); invoke( &create_associated_token_account( ctx.accounts.signer.to_account_info().key, @@ -419,22 +422,15 @@ pub mod gateway { .clone(), ], )?; - let bal_after = signer_info.lamports(); + let bal_after = ctx.accounts.signer.lamports(); + *cost_ata_create = bal_before - bal_after; msg!("Associated token account for recipient created!"); msg!( - "Refunding the rent paid by the signer {:?}", + "Refunding the rent ({:?} lamports) paid by the signer {:?}", + cost_ata_create, 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( @@ -452,6 +448,12 @@ pub mod gateway { transfer_checked(xfer_ctx, amount, decimals)?; msg!("withdraw spl token successfully"); + // Note: this pda.sub_lamports() must be done here due to this issue https://github.com/solana-labs/solana/issues/9711 + // otherwise the previous CPI calls might fail with error: + // "sum of account balances before and after instruction do not match" + let reimbursement = cost_gas + *cost_ata_create; + pda.sub_lamports(reimbursement)?; + ctx.accounts.signer.add_lamports(reimbursement)?; Ok(()) } diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 251bbb9..fe4345d 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -343,33 +343,6 @@ describe("Gateway", () => { }); - 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("fails to deposit if receiver is empty address", async() => { try { @@ -415,7 +388,38 @@ describe("Gateway", () => { let bal3 = await conn.getBalance(to); expect(bal3).to.be.gte(500_000_000); }) - + + 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(pdaAccount); + 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(pdaAccount); + // expected reimbursement to be gas fee (5000 lamports) + ATA creation cost 2039280 lamports + expect(rentPayerPdaBal0-rentPayerPdaBal1).to.be.eq(to_ata_bal + 5000); // rentPayer pays rent + }); + + it("fails to deposit and call if receiver is empty address", async() => { try { await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000_000), Array(20).fill(0), Buffer.from("hello", "utf-8")).accounts({}).rpc(); From 6aaadc0dc4ba6fc1ad64245b8d7942dc70eb8b93 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:38:32 -0600 Subject: [PATCH 2/7] fmt --- programs/protocol-contracts-solana/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index ae01fe5..c45c0da 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -338,7 +338,6 @@ pub mod gateway { message_hash: [u8; 32], nonce: u64, ) -> Result<()> { - let pda = &mut ctx.accounts.pda; // let program_id = &mut ctx.accounts if nonce != pda.nonce { From a92a84f4acbc18420a3d238623aa9e51f75ce4f4 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:06:19 -0600 Subject: [PATCH 3/7] adjust rent reimbursement --- 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 c45c0da..be24f3e 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -387,7 +387,7 @@ pub mod gateway { ); let cost_gas = 5000; // default gas cost in lamports - let cost_ata_create = &mut 2039280; // default SPL ATA account creation rent fee in lamports + let cost_ata_create = &mut 0; // will be updated if ATA creation is needed // test whether the recipient_ata is created or not; if not, create it let recipient_ata_account = ctx.accounts.recipient_ata.to_account_info(); From 4617aa4e64a70d15c1c01a285fc87f13d26e1d97 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:13:38 -0600 Subject: [PATCH 4/7] remove rent payer PDA --- programs/protocol-contracts-solana/src/lib.rs | 18 ------------------ tests/protocol-contracts-solana.ts | 18 ------------------ 2 files changed, 36 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index be24f3e..b4d9c00 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -167,10 +167,6 @@ 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 @@ -590,9 +586,6 @@ pub struct WithdrawSPLToken<'info> { #[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>, @@ -676,17 +669,6 @@ 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 diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index fe4345d..f4d985b 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -126,12 +126,6 @@ describe("Gateway", () => { 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(); @@ -143,18 +137,6 @@ describe("Gateway", () => { expect(err).to.be.not.null; } }); - 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 () => { // now deploying a fake USDC SPL Token From 948990770f331b81346a721ec0f163c13cb3c790 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:15:18 -0600 Subject: [PATCH 5/7] cleanup test --- tests/protocol-contracts-solana.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index f4d985b..7b1c646 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -372,20 +372,12 @@ describe("Gateway", () => { }) 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(pdaAccount); 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); From ed9ea15db80bd12f060d3dfd71a000535f318390 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:16:57 -0600 Subject: [PATCH 6/7] cleanup test2 --- tests/protocol-contracts-solana.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 7b1c646..183e6da 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -386,8 +386,7 @@ describe("Gateway", () => { 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 - + // pda should have reduced balance let rentPayerPdaBal1 = await conn.getBalance(pdaAccount); // expected reimbursement to be gas fee (5000 lamports) + ATA creation cost 2039280 lamports expect(rentPayerPdaBal0-rentPayerPdaBal1).to.be.eq(to_ata_bal + 5000); // rentPayer pays rent From 505b74ba66e8172559a68600b4a8b96e6086e368 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:21:23 -0600 Subject: [PATCH 7/7] updated comment --- programs/protocol-contracts-solana/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index b4d9c00..1c5cc6b 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -446,6 +446,8 @@ pub mod gateway { // Note: this pda.sub_lamports() must be done here due to this issue https://github.com/solana-labs/solana/issues/9711 // otherwise the previous CPI calls might fail with error: // "sum of account balances before and after instruction do not match" + // Note2: to keep PDA from deficit, all SPL ZRC20 contracts needs to charge withdraw fee of + // at least 5000(gas)+2039280(rent) lamports. let reimbursement = cost_gas + *cost_ata_create; pda.sub_lamports(reimbursement)?; ctx.accounts.signer.add_lamports(reimbursement)?;