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

feat: whitelist and unwhitelist SPL tokens #41

Merged
merged 13 commits into from
Oct 8, 2024
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ Mainnet-beta, testnet, devnet gateway program address:
ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis
```

The PDA account address (derived from seeds `b"meta"` and canonical bump) is
```
2f9SLuUNb7TNeM6gzBwT4ZjbL5ZyKzzHg1Ce9yiquEjj
```

This repository hosts the smart contracts (program)
on Solana network to support ZetaChain cross-chain
functionality. Specifically, it consists of a single
Expand Down
80 changes: 71 additions & 9 deletions programs/protocol-contracts-solana/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ declare_id!("ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis");

#[program]
pub mod gateway {

use super::*;

pub fn initialize(
Expand Down Expand Up @@ -89,6 +88,15 @@ pub mod gateway {
Ok(())
}

// whitelisting SPL tokens
pub fn whitelist_spl_mint(_ctx: Context<AddToWhitelist>) -> Result<()> {
Ok(())
}

pub fn de_whitelist_spl_mint(_ctx: Context<DeleteFromWhitelist>) -> Result<()> {
Ok(())
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear what these are for

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think all logic for this is in the accounts definition, when you whitelist new one is created (with init), when you unwhitelist its closed (with close)


// 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
Expand Down Expand Up @@ -338,8 +346,9 @@ pub struct Deposit<'info> {
#[account(mut)]
pub signer: Signer<'info>,

#[account(mut)]
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,

pub system_program: Program<'info, System>,
}

Expand All @@ -348,8 +357,14 @@ pub struct DepositSplToken<'info> {
#[account(mut)]
pub signer: Signer<'info>,

#[account(mut, seeds = [b"meta"], bump)]
#[account(seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,

#[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>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does mint mean?

There should be no minting involved since we lock/unlock SPL tokens?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to description Whitelisting is implemented as a special PDA for each mint account (SPL token id). is needed to create pda for mint account and existence of that pda marks that token is whitelisted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does mint mean?

There should be no minting involved since we lock/unlock SPL tokens?

mint (short for mint account) is the Solana equivalent for ERC20 contract address.
This is Solana jargon.


pub token_program: Program<'info, Token>,

#[account(mut)]
Expand All @@ -363,7 +378,7 @@ pub struct Withdraw<'info> {
#[account(mut)]
pub signer: Signer<'info>,

#[account(mut)]
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,
/// CHECK: to account is not read so no need to check its owners; the program neither knows nor cares who the owner is.
#[account(mut)]
Expand All @@ -378,10 +393,9 @@ pub struct WithdrawSPLToken<'info> {
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,

#[account(mut)]
#[account(mut, token::mint = mint_account, token::authority = pda)]
pub pda_ata: Account<'info, TokenAccount>, // associated token address of PDA

#[account()]
pub mint_account: Account<'info, Mint>,

#[account(mut)]
Expand All @@ -392,28 +406,73 @@ pub struct WithdrawSPLToken<'info> {

#[derive(Accounts)]
pub struct UpdateTss<'info> {
#[account(mut)]
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,
#[account(mut)]
pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct UpdateAuthority<'info> {
#[account(mut)]
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,
#[account(mut)]
pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct UpdatePaused<'info> {
#[account(mut)]
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,
#[account(mut)]
pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct AddToWhitelist<'info> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use consistent terminology with Solidity contracts:

  • whitelist
  • unwhitelist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to whitelist/unwhitelist across the board
e905e67

#[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>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means the signer (the authority) of the Whitelist instruction must be one of the fields in pda struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes,
the b"meta" PDA contains the authority.


pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct DeleteFromWhitelist<'info> {
#[account(
mut,
seeds=[
b"whitelist",
whitelist_candidate.key().as_ref()
],
bump,
close = authority,
)]
pub whitelist_entry: Account<'info, WhitelistEntry>,
pub whitelist_candidate: Account<'info, Mint>,

#[account(mut, seeds = [b"meta"], bump, has_one = authority)]
pub pda: Account<'info, Pda>,
#[account(mut)]
pub authority: Signer<'info>,

pub system_program: Program<'info, System>,
}

#[account]
pub struct Pda {
nonce: u64, // ensure that each signature can only be used once
Expand All @@ -423,6 +482,9 @@ pub struct Pda {
deposit_paused: bool,
}

#[account]
pub struct WhitelistEntry {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious if is there a pros/cons for whitelisting in 2 following ways:

  1. init/close account
  2. store flag within pda marking if its whitelisted or not

is it because having long lived pda in solana is not efficient if not needed because of rent, or something else?

Copy link
Contributor Author

@brewmaster012 brewmaster012 Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For whitelisting we really need is a mapping like Ethereum address=>bool

But Solana does not have mapping as a data structure like Ethereum mapping.
You'll need to roll your own. Say you want to create a mapping data account
and then store a hashmap or list in the PDA to implement the mapping.
There are two issues:

  1. size of the data structure and therefore the account size could dynamically change;
  2. hard to get deterministic constant access time (key-value lookup)

The PDA for each key-value pair is the idiomatic way to do Ethereum mapping on Solana.
Think of part of the seed as the key (mint account address), and value is the PDA account.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good explanation


#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading