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(oft-solana): create new instruction for renouncing freeze authority #1144

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
13 changes: 1 addition & 12 deletions examples/oft-solana/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,24 +100,13 @@ anchor keys sync

:warning: `--force` flag overwrites the existing keys with the ones you generate.

Run `anchor keys list` to view the generated programIds (public keys). The output should look something like this:
Optionally, run `anchor keys list` to view the generated programIds (public keys). The output should look something like this:

```
endpoint: <ENDPOINT_PROGRAM_ID>
oft: <OFT_PROGRAM_ID>
```

Copy the OFT's programId and go into [lib.rs](./programs/oft/src/lib.rs). Note the following snippet:

```
declare_id!(Pubkey::new_from_array(program_id_from_env!(
"OFT_ID",
"9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT"
)));
```

Replace `9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT` with the programId that you have copied.

### Building and Deploying the Solana OFT Program

Ensure you have Docker running before running the build command.
Expand Down
2 changes: 2 additions & 0 deletions examples/oft-solana/programs/oft/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod lz_receive;
pub mod lz_receive_types;
pub mod quote_oft;
pub mod quote_send;
pub mod renounce_freeze;
pub mod send;
pub mod set_oft_config;
pub mod set_pause;
Expand All @@ -14,6 +15,7 @@ pub use lz_receive::*;
pub use lz_receive_types::*;
pub use quote_oft::*;
pub use quote_send::*;
pub use renounce_freeze::*;
pub use send::*;
pub use set_oft_config::*;
pub use set_pause::*;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::*;
use anchor_lang::solana_program;
use anchor_spl::token_2022::spl_token_2022;
use anchor_spl::token_2022::spl_token_2022::instruction::AuthorityType;
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};

#[derive(Accounts)]
#[instruction()]
pub struct RenounceFreezeAuthority<'info> {
pub signer: Signer<'info>,
#[account(
mut,
seeds = [OFT_SEED, oft_store.token_escrow.as_ref()],
bump = oft_store.bump,
constraint = is_valid_signer(signer.key(), &oft_store) @OFTError::Unauthorized
)]
pub oft_store: Account<'info, OFTStore>,
#[account(
mut,
address = oft_store.token_escrow,
token::authority = oft_store,
token::mint = token_mint,
token::token_program = token_program
)]
pub token_escrow: InterfaceAccount<'info, TokenAccount>,

#[account(
mut,
address = oft_store.token_mint,
mint::token_program = token_program
)]
pub token_mint: InterfaceAccount<'info, Mint>,
#[account(mut)]
pub current_authority: AccountInfo<'info>,
pub token_program: Interface<'info, TokenInterface>,
}

//
impl RenounceFreezeAuthority<'_> {
pub fn apply(ctx: Context<RenounceFreezeAuthority>) -> Result<()> {
let mint = &ctx.accounts.token_mint;

// Ensure freeze authority is set
require!(
mint.freeze_authority.is_some(),
CustomError::NoFreezeAuthority
);

let oft_store_seed = ctx.accounts.token_escrow.key();
let seeds: &[&[u8]] = &[
OFT_SEED,
oft_store_seed.as_ref(),
&[ctx.accounts.oft_store.bump],
];

// Create the set_authority instruction directly
let ix = spl_token_2022::instruction::set_authority(
ctx.accounts.token_program.key,
&ctx.accounts.token_mint.key(),
None, // new authority
AuthorityType::FreezeAccount,
&ctx.accounts.current_authority.key(),
&[&ctx.accounts.oft_store.key()],
)?;

// Invoke with signing
solana_program::program::invoke_signed(
&ix,
&[
ctx.accounts.token_mint.to_account_info(),
ctx.accounts.current_authority.to_account_info(),
ctx.accounts.oft_store.to_account_info(),
],
&[&seeds],
)?;

Ok(())
}
}

// Custom error for validation
#[error_code]
pub enum CustomError {
#[msg("No freeze authority exists on this mint.")]
NoFreezeAuthority,
}

fn is_valid_signer(signer: Pubkey, oft_store: &OFTStore) -> bool {
oft_store.admin == signer
}
15 changes: 9 additions & 6 deletions examples/oft-solana/programs/oft/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ use oapp::{
endpoint::{MessagingFee, MessagingReceipt},
LzReceiveParams,
};
use solana_helper::program_id_from_env;
use state::*;

declare_id!(Pubkey::new_from_array(program_id_from_env!(
"OFT_ID",
"9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT"
)));
declare_id!("9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT");

pub const OFT_SEED: &[u8] = b"OFT";
pub const PEER_SEED: &[u8] = b"Peer";
Expand All @@ -32,7 +28,10 @@ pub mod oft {
use super::*;

pub fn oft_version(_ctx: Context<OFTVersion>) -> Result<Version> {
Ok(Version { interface: 2, message: 1 })
Ok(Version {
interface: 2,
message: 1,
})
}

pub fn init_oft(mut ctx: Context<InitOFT>, params: InitOFTParams) -> Result<()> {
Expand Down Expand Up @@ -62,6 +61,10 @@ pub mod oft {
WithdrawFee::apply(&mut ctx, &params)
}

pub fn renounce_freeze(ctx: Context<RenounceFreezeAuthority>) -> Result<()> {
RenounceFreezeAuthority::apply(ctx)
}

// ============================== Public ==============================

pub fn quote_oft(ctx: Context<QuoteOFT>, params: QuoteOFTParams) -> Result<QuoteOFTResult> {
Expand Down
1 change: 1 addition & 0 deletions examples/oft-solana/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import './solana/createOFT'
import './solana/createOFTAdapter'
import './solana/sendOFT'
import './solana/setAuthority'
import './solana/renounceFreeze'
import './solana/getPrioFees'
import './solana/base58'
import './solana/setInboundRateLimit'
Expand Down
4 changes: 3 additions & 1 deletion examples/oft-solana/tasks/solana/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '@metaplex-foundation/umi'
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
import { createWeb3JsEddsa } from '@metaplex-foundation/umi-eddsa-web3js'
import { toWeb3JsInstruction, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters'
import { toWeb3JsInstruction, toWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters'
import { AddressLookupTableAccount, Connection } from '@solana/web3.js'
import { getSimulationComputeUnits } from '@solana-developers/helpers'
import bs58 from 'bs58'
Expand Down Expand Up @@ -63,12 +63,14 @@ export const deriveConnection = async (eid: EndpointId) => {
const umi = createUmi(connection.rpcEndpoint).use(mplToolbox())
const umiWalletKeyPair = umi.eddsa.createKeypairFromSecretKey(bs58.decode(privateKey))
const umiWalletSigner = createSignerFromKeypair(umi, umiWalletKeyPair)
const web3JsKeypair = toWeb3JsKeypair(umiWalletKeyPair)
umi.use(signerIdentity(umiWalletSigner))
return {
connection,
umi,
umiWalletKeyPair,
umiWalletSigner,
web3JsKeypair,
}
}

Expand Down
90 changes: 90 additions & 0 deletions examples/oft-solana/tasks/solana/renounceFreeze.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { AnchorProvider, Program } from '@coral-xyz/anchor'
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { TOKEN_PROGRAM_ID } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
import { task } from 'hardhat/config'

import { EndpointId } from '@layerzerolabs/lz-definitions'

import { deriveConnection, getExplorerTxLink } from './index'

interface Args {
eid: EndpointId
programId: string
oftStore: string
tokenEscrow: string
tokenMint: string
tokenProgram: string
multisig: string
computeUnitPriceScaleFactor: number
}

// TODO: no need to pass in oft store? since we can derive
task('lz:oft:solana:renounce-freeze', 'Renounce freeze authority for an OFT token')
.addParam('eid', 'The endpoint ID', undefined, types.eid)
.addParam('programId', 'The OFT program ID', undefined, types.string)
.addParam('tokenEscrow', 'The OFT token escrow public key', undefined, types.string)
.addParam('oftStore', 'The OFT Store public key', undefined, types.string)
.addParam('tokenMint', 'The OFT token mint public key', undefined, types.string)
.addParam('multisig', 'The multisig public key', undefined, types.string)
.addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, types.float, true)
.setAction(
async ({
eid,
programId: programIdStr,
oftStore: oftStoreStr,
tokenEscrow: tokenEscrowStr,
tokenMint: tokenMintStr,
multisig: multiSigStr,
computeUnitPriceScaleFactor,
}: Args) => {
const { connection, umi, umiWalletSigner, web3JsKeypair } = await deriveConnection(eid)

// TODO: clean up below block
const wallet = new NodeWallet(web3JsKeypair)
const provider = new AnchorProvider(connection, wallet, {
commitment: 'processed',
})

const IDL = await import('../../target/idl/oft.json').then((module) => module.default)
const anchorTypes = await import('../../target/types/oft').then((module) => module)

// @ts-ignore we can ignore the IDL type error, which is a quirk of Anchor
const program = new Program<typeof anchorTypes.IDL>(IDL, programIdStr, provider)

const [oftStorePda, oftStoreBump] = PublicKey.findProgramAddressSync(
[Buffer.from('OFT'), new PublicKey(tokenEscrowStr).toBuffer()],
program.programId
)
if (oftStorePda.toString() != oftStoreStr) {
throw new Error('Mismatch between Token Escrow address and derived OFT Store PDA')
}

const oftStoreAccountData = await program.account.oftStore.fetch(oftStoreStr)

const signerIsAdmin = umiWalletSigner.publicKey.toString() == oftStoreAccountData.admin.toString()
if (!signerIsAdmin) {
throw new Error('Your keypair is not the OFT Store admin.')
}

// Call the method
try {
const tx = await program.methods
.renounceFreeze() // Method name
.accounts({
signer: umiWalletSigner.publicKey,
oftStore: oftStorePda,
tokenEscrow: tokenEscrowStr,
tokenMint: tokenMintStr,
currentAuthority: multiSigStr,
tokenProgram: TOKEN_PROGRAM_ID.toBase58(), // currently only supports SPL Token standard
})
.signers([web3JsKeypair])
.rpc()

console.log('Transaction successful:', getExplorerTxLink(tx, eid == EndpointId.SOLANA_V2_TESTNET))
} catch (err) {
console.error('Transaction failed:', err)
}
}
)
Loading