Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

separate deposit into two instructions: deposit, deposit_and_call #29

Merged
merged 3 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 23 additions & 86 deletions programs/protocol-contracts-solana/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -85,10 +84,7 @@ pub mod gateway {
Ok(())
}

pub fn deposit(ctx: Context<Deposit>, amount: u64, memo: Vec<u8>) -> Result<()> {
require!(memo.len() >= 20, Errors::MemoLengthTooShort);
require!(memo.len() <= 512, Errors::MemoLengthExceeded);

pub fn deposit(ctx: Context<Deposit>, amount: u64, receiver: [u8; 20]) -> Result<()> {
let pda = &mut ctx.accounts.pda;
require!(!pda.deposit_paused, Errors::DepositPaused);

Expand All @@ -101,48 +97,41 @@ pub mod gateway {
);
system_program::transfer(cpi_context, amount)?;
msg!(
"{:?} deposits {:?} lamports to PDA",
"{:?} deposits {:?} lamports to PDA; receiver {:?}",
ctx.accounts.signer.key(),
amount
amount,
receiver,
);

Ok(())
}

pub fn deposit_spl_token(
ctx: Context<DepositSplToken>,
pub fn deposit_and_call(
ctx: Context<Deposit>,
amount: u64,
memo: Vec<u8>,
receiver: [u8; 20],
message: Vec<u8>,
) -> Result<()> {
require!(memo.len() >= 20, Errors::MemoLengthTooShort);
require!(memo.len() <= 512, Errors::MemoLengthExceeded);
let token = &ctx.accounts.token_program;
let from = &ctx.accounts.from;
require!(message.len() <= 512, Errors::MemoLengthExceeded);
brewmaster012 marked this conversation as resolved.
Show resolved Hide resolved

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(),
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(),
},
);
transfer(xfer_ctx, amount)?;

msg!("deposit spl token successfully");
system_program::transfer(cpi_context, amount)?;
msg!(
"{:?} deposits {:?} lamports to PDA and call contract {:?} with message {:?}",
ctx.accounts.signer.key(),
amount,
receiver,
message,
);

Ok(())
}
Expand Down Expand Up @@ -187,58 +176,6 @@ pub mod gateway {

Ok(())
}

// only tss address stored in PDA can call this instruction
pub fn withdraw_spl_token(
ctx: Context<WithdrawSPLToken>,
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(
Expand Down
191 changes: 15 additions & 176 deletions tests/protocol-contracts-solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -52,6 +51,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();

Expand All @@ -65,188 +70,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);
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();
// 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;
Expand Down Expand Up @@ -277,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);
Expand Down Expand Up @@ -310,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);
Expand Down
Loading