From d6665a1f502d07251dcd00703af2f62438e6c87e Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:00:07 -0700 Subject: [PATCH 1/5] remove SPL related instructions --- programs/protocol-contracts-solana/src/lib.rs | 92 +--------- tests/protocol-contracts-solana.ts | 164 +----------------- 2 files changed, 7 insertions(+), 249 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index b85a2cd..1699caf 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -1,9 +1,8 @@ use anchor_lang::prelude::*; use anchor_lang::system_program; -use anchor_spl::token::{transfer, Token, TokenAccount}; +use anchor_spl::token::{Token, TokenAccount}; use solana_program::keccak::hash; use solana_program::secp256k1_recover::secp256k1_recover; -use spl_associated_token_account; use std::mem::size_of; #[error_code] @@ -109,44 +108,6 @@ pub mod gateway { Ok(()) } - pub fn deposit_spl_token( - ctx: Context, - amount: u64, - memo: Vec, - ) -> Result<()> { - require!(memo.len() >= 20, Errors::MemoLengthTooShort); - require!(memo.len() <= 512, Errors::MemoLengthExceeded); - let token = &ctx.accounts.token_program; - let from = &ctx.accounts.from; - - 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, - ); - // must deposit to the ATA from PDA in order to receive credit - require!( - pda_ata == ctx.accounts.to.to_account_info().key(), - Errors::DepositToAddressMismatch - ); - - let xfer_ctx = CpiContext::new( - token.to_account_info(), - anchor_spl::token::Transfer { - from: ctx.accounts.from.to_account_info(), - to: ctx.accounts.to.to_account_info(), - authority: ctx.accounts.signer.to_account_info(), - }, - ); - transfer(xfer_ctx, amount)?; - - msg!("deposit spl token successfully"); - - Ok(()) - } - // only tss address stored in PDA can call this instruction pub fn withdraw( ctx: Context, @@ -188,57 +149,6 @@ pub mod gateway { Ok(()) } - // only tss address stored in PDA can call this instruction - pub fn withdraw_spl_token( - ctx: Context, - amount: u64, - signature: [u8; 64], - recovery_id: u8, - message_hash: [u8; 32], - nonce: u64, - ) -> Result<()> { - let pda = &mut ctx.accounts.pda; - // let program_id = &mut ctx.accounts - if nonce != pda.nonce { - msg!("mismatch nonce"); - return err!(Errors::NonceMismatch); - } - let mut concatenated_buffer = Vec::new(); - concatenated_buffer.extend_from_slice(&pda.chain_id.to_be_bytes()); - 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.to.key().to_bytes()); - require!( - message_hash == hash(&concatenated_buffer[..]).to_bytes(), - Errors::MessageHashMismatch - ); - - 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); - } - - let token = &ctx.accounts.token_program; - let signer_seeds: &[&[&[u8]]] = &[&[b"meta", &[ctx.bumps.pda]]]; - - let xfer_ctx = CpiContext::new_with_signer( - token.to_account_info(), - anchor_spl::token::Transfer { - from: ctx.accounts.from.to_account_info(), - to: ctx.accounts.to.to_account_info(), - authority: pda.to_account_info(), - }, - signer_seeds, - ); - transfer(xfer_ctx, amount)?; - msg!("withdraw spl token successfully"); - - pda.nonce += 1; - - Ok(()) - } } fn recover_eth_address( diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 498b8de..5876009 100644 --- a/tests/protocol-contracts-solana.ts +++ b/tests/protocol-contracts-solana.ts @@ -52,6 +52,12 @@ describe("some tests", () => { 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( + seeds, + gatewayProgram.programId, + ); + it("Initializes the program", async () => { await gatewayProgram.methods.initialize(tssAddress, chain_id_bn).rpc(); @@ -65,164 +71,6 @@ 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); - const 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, - 6, - wallet.publicKey, - null, - ) - ); - await anchor.web3.sendAndConfirmTransaction(conn, tokenTransaction, [wallet, mint]); - console.log("mint account created!", mint.publicKey.toString()); - - // 2. create token account to receive mint - tokenAccount = await spl.getOrCreateAssociatedTokenAccount( - conn, - wallet, - mint.publicKey, - wallet.publicKey, - ); - // 3. mint some tokens - const mintToTransaction = new anchor.web3.Transaction().add( - spl.createMintToInstruction( - mint.publicKey, - tokenAccount.address, - wallet.publicKey, - 10_000_000, - ) - ); - 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()}`); - }) - - 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()); - const tx = new web3.Transaction(); - const memoInst = memo.createMemoInstruction( - "this is a memo", - [wallet.publicKey], - ); - tx.add(memoInst); - const depositInst = await gatewayProgram.methods.depositSplToken( - new anchor.BN(1_000_000), address).accounts( - { - from: tokenAccount.address, - to: pda_ata.address, - } - ).instruction(); - tx.add(depositInst); - const txsig = await anchor.web3.sendAndConfirmTransaction(conn, tx, [wallet]); - - - try { - await gatewayProgram.methods.depositSplToken(new anchor.BN(1_000_000), address).accounts( - { - from: tokenAccount.address, - to: wallet_ata, - } - ).rpc(); - throw new Error("Expected error not thrown"); - } catch (err) { - expect(err).to.be.instanceof(anchor.AnchorError); - expect(err.message).to.include("DepositToAddressMismatch"); - // console.log("Error message: ", err.message); - } - }); - - it("Withdraw 500_000 USDC from Gateway with ECDSA signature", async () => { - const account2 = await spl.getAccount(conn, pda_ata.address); - 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 message_hash = fromHexString( - // "0a1e2723bd7f1996832b7ed7406df8ad975deba1aa04020b5bfc3e6fe70ecc29" - // ); - // const signature = fromHexString( - // "58be181f57b2d56b0c252127c9874a8fbe5ebd04f7632fb3966935a3e9a765807813692cebcbf3416cb1053ad9c8c83af471ea828242cca22076dd04ddbcd253" - // ); - const amount = new anchor.BN(500_000); - const nonce = pdaAccountData.nonce; - const buffer = Buffer.concat([ - chain_id_bn.toArrayLike(Buffer, 'be', 8), - nonce.toArrayLike(Buffer, 'be', 8), - amount.toArrayLike(Buffer, 'be', 8), - 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(amount, Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce) - .accounts({ - from: pda_ata.address, - to: wallet_ata, - }).rpc(); - - const account3 = await spl.getAccount(conn, pda_ata.address); - expect(account3.amount).to.be.eq(500_000n); - - - try { - (await gatewayProgram.methods.withdrawSplToken(new anchor.BN(500_000), Array.from(signatureBuffer), Number(recoveryParam), Array.from(message_hash), nonce) - .accounts({ - from: pda_ata.address, - to: wallet_ata, - }).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); - expect(err.message).to.include("NonceMismatch"); - 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(500_000n); - } - - }); it("deposit and withdraw 0.5 SOL from Gateway with ECDSA signature", async () => { await gatewayProgram.methods.deposit(new anchor.BN(1_000_000_000), address).accounts({pda: pdaAccount}).rpc(); From 9cc1b4c3509ef096f35cf85600a9c5b5db92b82d Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:28:22 -0700 Subject: [PATCH 2/5] separate deposit into deposit and deposit_and_call --- programs/protocol-contracts-solana/src/lib.rs | 33 ++++++++++++++++--- tests/protocol-contracts-solana.ts | 27 +++++---------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index 1699caf..beaf7d2 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -84,9 +84,30 @@ pub mod gateway { Ok(()) } - pub fn deposit(ctx: Context, amount: u64, memo: Vec) -> Result<()> { - require!(memo.len() >= 20, Errors::MemoLengthTooShort); - require!(memo.len() <= 512, Errors::MemoLengthExceeded); + pub fn deposit(ctx: Context, amount: u64, receiver: [u8; 20]) -> Result<()> { + let pda = &mut ctx.accounts.pda; + require!(!pda.deposit_paused, Errors::DepositPaused); + + let cpi_context = CpiContext::new( + ctx.accounts.system_program.to_account_info(), + system_program::Transfer { + from: ctx.accounts.signer.to_account_info().clone(), + to: ctx.accounts.pda.to_account_info().clone(), + }, + ); + system_program::transfer(cpi_context, amount)?; + msg!( + "{:?} deposits {:?} lamports to PDA; receiver {:?}", + ctx.accounts.signer.key(), + amount, + receiver, + ); + + Ok(()) + } + + pub fn deposit_and_call(ctx: Context, amount: u64, receiver: [u8; 20], message: Vec) -> Result<()> { + require!(message.len() <= 512, Errors::MemoLengthExceeded); let pda = &mut ctx.accounts.pda; require!(!pda.deposit_paused, Errors::DepositPaused); @@ -100,9 +121,11 @@ pub mod gateway { ); system_program::transfer(cpi_context, amount)?; msg!( - "{:?} deposits {:?} lamports to PDA", + "{:?} deposits {:?} lamports to PDA and call contract {:?} with message {:?}", ctx.accounts.signer.key(), - amount + amount, + receiver, + message, ); Ok(()) diff --git a/tests/protocol-contracts-solana.ts b/tests/protocol-contracts-solana.ts index 5876009..51e05e3 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'; @@ -73,28 +72,13 @@ 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), address).accounts({pda: pdaAccount}).rpc(); - // const transaction = new anchor.web3.Transaction(); - // transaction.add( - // web3.SystemProgram.transfer({ - // fromPubkey: wallet.publicKey, - // toPubkey: pdaAccount, - // lamports: 1_000_000_000, - // }) - // ); - // await anchor.web3.sendAndConfirmTransaction(conn, transaction, [wallet]); + await gatewayProgram.methods.deposit(new anchor.BN(1_000_000_000), Array.from(address)).accounts({pda: pdaAccount}).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 = wallet.publicKey; @@ -125,6 +109,13 @@ describe("some tests", () => { expect(bal3).to.be.gte(500_000_000); }) + it("deposit and call", async () => { + let bal1 = await conn.getBalance(pdaAccount); + await gatewayProgram.methods.depositAndCall(new anchor.BN(1_000_000_000), Array.from(address), Buffer.from("hello", "utf-8")).accounts({pda: pdaAccount}).rpc(); + let bal2 = await conn.getBalance(pdaAccount); + expect(bal2-bal1).to.be.gte(1_000_000_000); + }) + it("update TSS address", async () => { const newTss = new Uint8Array(20); randomFillSync(newTss); @@ -158,7 +149,7 @@ describe("some tests", () => { // now try deposit, should fail try { - await gatewayProgram.methods.deposit(new anchor.BN(1_000_000), address).accounts({pda: pdaAccount}).rpc(); + await gatewayProgram.methods.deposit(new anchor.BN(1_000_000), Array.from(address)).accounts({pda: pdaAccount}).rpc(); } catch (err) { console.log("Error message: ", err.message); expect(err).to.be.instanceof(anchor.AnchorError); From a341183e539f136a21201baaceddb4d5c9cc9b5f Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:30:45 -0700 Subject: [PATCH 3/5] format code --- 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 1699caf..280f493 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -148,7 +148,6 @@ pub mod gateway { Ok(()) } - } fn recover_eth_address( From 3081f3f1906318cba4e1c4eb0c06527572131fd6 Mon Sep 17 00:00:00 2001 From: brewmaster012 <88689859+brewmaster012@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:39:57 -0700 Subject: [PATCH 4/5] format code --- programs/protocol-contracts-solana/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/programs/protocol-contracts-solana/src/lib.rs b/programs/protocol-contracts-solana/src/lib.rs index beaf7d2..76572ef 100644 --- a/programs/protocol-contracts-solana/src/lib.rs +++ b/programs/protocol-contracts-solana/src/lib.rs @@ -106,7 +106,12 @@ pub mod gateway { Ok(()) } - pub fn deposit_and_call(ctx: Context, amount: u64, receiver: [u8; 20], message: Vec) -> Result<()> { + pub fn deposit_and_call( + ctx: Context, + amount: u64, + receiver: [u8; 20], + message: Vec, + ) -> Result<()> { require!(message.len() <= 512, Errors::MemoLengthExceeded); let pda = &mut ctx.accounts.pda; @@ -171,7 +176,6 @@ pub mod gateway { Ok(()) } - } fn recover_eth_address( From 13e04d9541a193f4f5144082f403f7a8efeb434e Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Mon, 23 Sep 2024 16:20:04 +0300 Subject: [PATCH 5/5] ci: added license, fix npm publish (#31) --- .github/workflows/publish-npm.yml | 35 ++++++++++++++++++------------- LICENSE | 21 +++++++++++++++++++ package.json | 1 + 3 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 LICENSE diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 561b499..d1cc4f0 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -15,36 +15,41 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable - - name: Install Node.js, Solana CLI and Anchor + - name: Install Node.js, Solana CLI, and Anchor uses: metadaoproject/setup-anchor@v2 - with: - anchor-version: '0.30.0' + with: + anchor-version: '0.30.0' solana-cli-version: '1.18.15' node-version: '21.0.0' + - name: Setup Node.js for NPM Publish + uses: actions/setup-node@v4 + with: + node-version: '21.0.0' + registry-url: 'https://registry.npmjs.org' + auth-token: ${{ secrets.NPM_TOKEN }} + - name: Build run: anchor build - name: Prepare IDL Files - run: | - mv target/idl ./ + run: mv target/idl ./ - name: Determine NPM Tag id: determine-npm-tag run: | - VERSION_TAG=${GITHUB_REF#refs/tags/v} + VERSION_TAG=${{ github.ref_name }} if [[ $VERSION_TAG == *"-"* ]]; then - echo "NPM_TAG=${VERSION_TAG#*-}" >> "$GITHUB_ENV" + NPM_TAG=${VERSION_TAG#*-} else - echo "NPM_TAG=latest" >> "$GITHUB_ENV" + NPM_TAG=latest fi - env: - GITHUB_REF: ${{ github.ref }} + echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT" - name: Publish to NPM run: | - 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 }} + yarn publish \ + --access public \ + --new-version "${{ github.ref_name }}" \ + --tag "${{ steps.determine-npm-tag.outputs.NPM_TAG }}" \ + --no-git-tag-version diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dd5957b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Meta Protocol, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/package.json b/package.json index ec957f5..77b4ffc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@zetachain/protocol-contracts-solana", "private": false, + "license": "MIT", "version": "0.0.0-set-on-publish", "description": "Package contains IDL files for the Solana Gateway program, enabling cross-chain functionality with ZetaChain", "files": [