Skip to content

Commit

Permalink
Add iteration of minters
Browse files Browse the repository at this point in the history
  • Loading branch information
eth-r committed Aug 1, 2023
1 parent 5d7877d commit 73964de
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
error::TbtcError,
state::{Config, MinterInfo},
state::{Config, MinterIndex, MinterInfo},
};
use anchor_lang::prelude::*;

Expand All @@ -26,6 +26,15 @@ pub struct AddMinter<'info> {
)]
minter_info: Account<'info, MinterInfo>,

#[account(
init,
payer = authority,
space = 8 + MinterIndex::INIT_SPACE,
seeds = [MinterIndex::SEED_PREFIX, &[config.num_minters]],
bump
)]
minter_index: Account<'info, MinterIndex>,

/// CHECK: Required authority to mint tokens. This pubkey lives in `MinterInfo`.
minter: AccountInfo<'info>,

Expand All @@ -35,9 +44,15 @@ pub struct AddMinter<'info> {
pub fn add_minter(ctx: Context<AddMinter>) -> Result<()> {
ctx.accounts.minter_info.set_inner(MinterInfo {
minter: ctx.accounts.minter.key(),
index: ctx.accounts.config.num_minters,
bump: ctx.bumps["minter_info"],
});

ctx.accounts.minter_index.set_inner(MinterIndex {
minter_info: ctx.accounts.minter_info.key(),
bump: ctx.bumps["minter_index"],
});

ctx.accounts.config.num_minters += 1;
Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
error::TbtcError,
state::{Config, MinterInfo},
state::{Config, MinterIndex, MinterInfo},
};
use anchor_lang::prelude::*;

Expand All @@ -25,6 +25,36 @@ pub struct RemoveMinter<'info> {
)]
minter_info: Account<'info, MinterInfo>,

// the minter info at the last index.
// This gets its index swapped to the position of the removed minter info.
#[account(
mut,
constraint = minter_info_swap.index == config.num_minters - 1,
seeds = [MinterInfo::SEED_PREFIX, minter_info_swap.minter.as_ref()],
bump = minter_info_swap.bump,
)]
minter_info_swap: Account<'info, MinterInfo>,

// The index account of the minter to remove.
// We replace minter_info in this with minter_info_swap.
#[account(
mut,
seeds = [MinterIndex::SEED_PREFIX, &[minter_info.index]],
bump = minter_index_swap.bump,
)]
minter_index_swap: Account<'info, MinterIndex>,

// The last minter index account.
// This gets removed, and its minter_info(_swap) gets put into minter_index_swap instead.
#[account(
mut,
close = authority,
seeds = [MinterIndex::SEED_PREFIX, &[config.num_minters - 1]],
bump = minter_index_tail.bump,
constraint = minter_index_tail.minter_info == minter_info_swap.key(),
)]
minter_index_tail: Account<'info, MinterIndex>,

/// CHECK: Required authority to mint tokens. This pubkey lives in `MinterInfo`.
minter: AccountInfo<'info>,
}
Expand Down
12 changes: 12 additions & 0 deletions cross-chain/solana/programs/tbtc/src/state/minter_index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use anchor_lang::prelude::*;

#[account]
#[derive(Debug, InitSpace)]
pub struct MinterIndex {
pub minter_info: Pubkey,
pub bump: u8,
}

impl MinterIndex {
pub const SEED_PREFIX: &'static [u8] = b"minter-index";
}
1 change: 1 addition & 0 deletions cross-chain/solana/programs/tbtc/src/state/minter_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use anchor_lang::prelude::*;
#[derive(Debug, InitSpace)]
pub struct MinterInfo {
pub minter: Pubkey,
pub index: u8,
pub bump: u8,
}

Expand Down
3 changes: 3 additions & 0 deletions cross-chain/solana/programs/tbtc/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ pub use guardian_index::*;
mod guardian_info;
pub use guardian_info::*;

mod minter_index;
pub use minter_index::*;

mod minter_info;
pub use minter_info::*;
76 changes: 57 additions & 19 deletions cross-chain/solana/tests/01__tbtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,41 @@ function getMinterPDA(
);
}

function getMinterIndexPDA(
program: Program<Tbtc>,
index
): [anchor.web3.PublicKey, number] {
let indexArr = new Uint8Array(1);
indexArr[0] = index;
return web3.PublicKey.findProgramAddressSync(
[
Buffer.from('minter-index'),
indexArr,
],
program.programId
);
}

async function addMinter(
program: Program<Tbtc>,
authority,
minter,
payer
minter
): Promise<anchor.web3.PublicKey> {
const [config,] = getConfigPDA(program);
const [minterInfoPDA, _] = getMinterPDA(program, minter);

let configState = await program.account.config.fetch(config);

const [minterIndexPDA, __] = getMinterIndexPDA(program, configState.numMinters);

await program.methods
.addMinter()
.accounts({
config,
authority: authority.publicKey,
minter: minter.publicKey,
minterInfo: minterInfoPDA,
minterIndex: minterIndexPDA,
minter: minter.publicKey,
})
.signers(maybeAuthorityAnd(authority, []))
.rpc();
Expand All @@ -136,13 +156,22 @@ async function addMinter(

async function checkMinter(
program: Program<Tbtc>,
minter
minter,
expectedIndex
) {
const [minterInfoPDA, bump] = getMinterPDA(program, minter);
let minterInfo = await program.account.minterInfo.fetch(minterInfoPDA);

const [minterIndexPDA, indexBump] = getMinterIndexPDA(program, minterInfo.index);
let minterIndex = await program.account.minterIndex.fetch(minterIndexPDA);

expect(minterInfo.minter).to.eql(minter.publicKey);
expect(minterInfo.bump).to.equal(bump);

expect(minterIndex.minterInfo).to.eql(minterInfoPDA);
expect(minterIndex.bump).to.equal(indexBump);

expect(minterInfo.index).to.equal(expectedIndex);
}

async function removeMinter(
Expand All @@ -152,12 +181,24 @@ async function removeMinter(
minterInfo
) {
const [config,] = getConfigPDA(program);
const configState = await program.account.config.fetch(config);
const minterInfoState = await program.account.minterInfo.fetch(minterInfo);

const [lastIndex,] = getMinterIndexPDA(program, configState.numMinters - 1);
const [swapIndex,] = getMinterIndexPDA(program, minterInfoState.index);

const lastIndexState = await program.account.minterIndex.fetch(lastIndex);
const swapInfo = lastIndexState.minterInfo;

await program.methods
.removeMinter()
.accounts({
config,
authority: authority.publicKey,
minterInfo: minterInfo,
minterInfoSwap: swapInfo,
minterIndexSwap: swapIndex,
minterIndexTail: lastIndex,
minter: minter.publicKey
})
.signers(maybeAuthorityAnd(authority, []))
Expand Down Expand Up @@ -382,8 +423,8 @@ describe("tbtc", () => {

it('add minter', async () => {
await checkState(program, authority, 0, 0, 0);
await addMinter(program, authority, minterKeys, authority);
await checkMinter(program, minterKeys);
await addMinter(program, authority, minterKeys);
await checkMinter(program, minterKeys, 0);
await checkState(program, authority, 1, 0, 0);

// Transfer lamports to imposter.
Expand All @@ -400,7 +441,7 @@ describe("tbtc", () => {
);

try {
await addMinter(program, impostorKeys, minter2Keys, authority);
await addMinter(program, impostorKeys, minter2Keys);
chai.assert(false, "should've failed but didn't");
} catch (_err) {
expect(_err).to.be.instanceOf(AnchorError);
Expand All @@ -413,7 +454,7 @@ describe("tbtc", () => {
it('mint', async () => {
await checkState(program, authority, 1, 0, 0);
const [minterInfoPDA, _] = getMinterPDA(program, minterKeys);
await checkMinter(program, minterKeys);
await checkMinter(program, minterKeys, 0);

// await setupMint(program, authority, recipientKeys);
await mint(program, minterKeys, minterInfoPDA, recipientKeys, 1000, authority);
Expand All @@ -434,7 +475,7 @@ describe("tbtc", () => {
it('won\'t mint', async () => {
await checkState(program, authority, 1, 0, 1000);
const [minterInfoPDA, _] = getMinterPDA(program, minterKeys);
await checkMinter(program, minterKeys);
await checkMinter(program, minterKeys, 0);

// await setupMint(program, authority, recipientKeys);

Expand All @@ -452,9 +493,9 @@ describe("tbtc", () => {
it('use two minters', async () => {
await checkState(program, authority, 1, 0, 1000);
const [minterInfoPDA, _] = getMinterPDA(program, minterKeys);
await checkMinter(program, minterKeys);
const minter2InfoPDA = await addMinter(program, authority, minter2Keys, authority);
await checkMinter(program, minter2Keys);
await checkMinter(program, minterKeys, 0);
const minter2InfoPDA = await addMinter(program, authority, minter2Keys);
await checkMinter(program, minter2Keys, 1);
await checkState(program, authority, 2, 0, 1000);
// await setupMint(program, authority, recipientKeys);

Expand Down Expand Up @@ -487,15 +528,15 @@ describe("tbtc", () => {
it('remove minter', async () => {
await checkState(program, authority, 2, 0, 1500);
const [minter2InfoPDA, _] = getMinterPDA(program, minter2Keys);
await checkMinter(program, minter2Keys);
await checkMinter(program, minter2Keys, 1);
await removeMinter(program, authority, minter2Keys, minter2InfoPDA);
await checkState(program, authority, 1, 0, 1500);
});

it('won\'t remove minter', async () => {
await checkState(program, authority, 1, 0, 1500);
const [minterInfoPDA, _] = getMinterPDA(program, minterKeys);
await checkMinter(program, minterKeys);
await checkMinter(program, minterKeys, 0);

try {
await removeMinter(program, impostorKeys, minterKeys, minterInfoPDA);
Expand All @@ -514,10 +555,7 @@ describe("tbtc", () => {
await removeMinter(program, authority, minterKeys, minterInfoPDA);
chai.assert(false, "should've failed but didn't");
} catch (_err) {
expect(_err).to.be.instanceOf(AnchorError);
const err: AnchorError = _err;
expect(err.error.errorCode.code).to.equal('AccountNotInitialized');
expect(err.program.equals(program.programId)).is.true;
expect(_err.message).to.include('Account does not exist or has no data');
}
});

Expand Down Expand Up @@ -604,7 +642,7 @@ describe("tbtc", () => {

it('won\'t mint when paused', async () => {
await checkState(program, authority, 0, 1, 1500);
const minterInfoPDA = await addMinter(program, authority, minterKeys, authority);
const minterInfoPDA = await addMinter(program, authority, minterKeys);
await pause(program, guardianKeys);
// await setupMint(program, authority, recipientKeys);

Expand Down

0 comments on commit 73964de

Please sign in to comment.