From 878e5ed753bf34d1875d06047653623a313486c2 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 11:25:02 -0300 Subject: [PATCH 01/12] Add Whitelist program --- packages/svm/Anchor.toml | 3 +- packages/svm/Cargo.lock | 7 + packages/svm/idls/whitelist.json | 518 +++++++++++++++++ packages/svm/package.json | 11 +- packages/svm/programs/whitelist/Cargo.toml | 26 + .../svm/programs/whitelist/src/constants.rs | 6 + packages/svm/programs/whitelist/src/errors.rs | 28 + .../whitelist/src/instructions/initialize.rs | 57 ++ .../whitelist/src/instructions/mod.rs | 9 + .../src/instructions/propose_admin.rs | 33 ++ .../set_entity_whitelist_status.rs | 70 +++ .../src/instructions/set_proposed_admin.rs | 47 ++ packages/svm/programs/whitelist/src/lib.rs | 41 ++ .../whitelist/src/state/entity_registry.rs | 14 + .../whitelist/src/state/global_settings.rs | 11 + .../svm/programs/whitelist/src/state/mod.rs | 5 + .../whitelist/src/types/entity_type.rs | 13 + .../svm/programs/whitelist/src/types/mod.rs | 5 + .../whitelist/src/types/whitelist_status.rs | 12 + packages/svm/rust-toolchain.toml | 4 + packages/svm/sdks/whitelist/Whitelist.ts | 117 ++++ packages/svm/tests/helpers/constants.ts | 51 ++ packages/svm/tests/utils.ts | 39 +- packages/svm/tests/whitelist.test.ts | 526 ++++++++++++++++++ 24 files changed, 1643 insertions(+), 10 deletions(-) create mode 100644 packages/svm/idls/whitelist.json create mode 100644 packages/svm/programs/whitelist/Cargo.toml create mode 100644 packages/svm/programs/whitelist/src/constants.rs create mode 100644 packages/svm/programs/whitelist/src/errors.rs create mode 100644 packages/svm/programs/whitelist/src/instructions/initialize.rs create mode 100644 packages/svm/programs/whitelist/src/instructions/mod.rs create mode 100644 packages/svm/programs/whitelist/src/instructions/propose_admin.rs create mode 100644 packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs create mode 100644 packages/svm/programs/whitelist/src/instructions/set_proposed_admin.rs create mode 100644 packages/svm/programs/whitelist/src/lib.rs create mode 100644 packages/svm/programs/whitelist/src/state/entity_registry.rs create mode 100644 packages/svm/programs/whitelist/src/state/global_settings.rs create mode 100644 packages/svm/programs/whitelist/src/state/mod.rs create mode 100644 packages/svm/programs/whitelist/src/types/entity_type.rs create mode 100644 packages/svm/programs/whitelist/src/types/mod.rs create mode 100644 packages/svm/programs/whitelist/src/types/whitelist_status.rs create mode 100644 packages/svm/rust-toolchain.toml create mode 100644 packages/svm/sdks/whitelist/Whitelist.ts create mode 100644 packages/svm/tests/helpers/constants.ts create mode 100644 packages/svm/tests/whitelist.test.ts diff --git a/packages/svm/Anchor.toml b/packages/svm/Anchor.toml index 1bb4fa9..8d4d942 100644 --- a/packages/svm/Anchor.toml +++ b/packages/svm/Anchor.toml @@ -7,6 +7,7 @@ skip-lint = false [programs.localnet] settler = "HbNt35Ng8aM4NUy39evpCQqXEC4Nmaq16ewY8dyNF6NF" +whitelist = "7PwVkjnnapxytWFW69WFDLhfVZZgKhBE9m3zwcDZmncr" [registry] url = "https://api.apr.dev" @@ -16,4 +17,4 @@ cluster = "localnet" wallet = "~/.config/solana/id.json" [scripts] -test = "yarn run ts-mocha -p ./tsconfig.json tests/**/*.ts" +test = "yarn run ts-mocha -p ./tsconfig.json tests/*.ts" diff --git a/packages/svm/Cargo.lock b/packages/svm/Cargo.lock index 66d20c5..18046f0 100644 --- a/packages/svm/Cargo.lock +++ b/packages/svm/Cargo.lock @@ -1529,6 +1529,13 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "whitelist" +version = "0.1.0" +dependencies = [ + "anchor-lang", +] + [[package]] name = "windows-link" version = "0.2.1" diff --git a/packages/svm/idls/whitelist.json b/packages/svm/idls/whitelist.json new file mode 100644 index 0000000..8bfadb2 --- /dev/null +++ b/packages/svm/idls/whitelist.json @@ -0,0 +1,518 @@ +{ + "address": "7PwVkjnnapxytWFW69WFDLhfVZZgKhBE9m3zwcDZmncr", + "metadata": { + "name": "whitelist", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "initialize", + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], + "accounts": [ + { + "name": "deployer", + "writable": true, + "signer": true + }, + { + "name": "global_settings", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108, + 45, + 115, + 101, + 116, + 116, + 105, + 110, + 103, + 115 + ] + } + ] + } + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "admin", + "type": "pubkey" + }, + { + "name": "proposed_admin_cooldown", + "type": "u64" + } + ] + }, + { + "name": "propose_admin", + "discriminator": [ + 121, + 214, + 199, + 212, + 87, + 39, + 117, + 234 + ], + "accounts": [ + { + "name": "admin", + "writable": true, + "signer": true, + "relations": [ + "global_settings" + ] + }, + { + "name": "global_settings", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108, + 45, + 115, + 101, + 116, + 116, + 105, + 110, + 103, + 115 + ] + } + ] + } + } + ], + "args": [ + { + "name": "proposed_admin", + "type": "pubkey" + } + ] + }, + { + "name": "set_entity_whitelist_status", + "discriminator": [ + 100, + 20, + 23, + 73, + 220, + 118, + 179, + 50 + ], + "accounts": [ + { + "name": "admin", + "writable": true, + "signer": true, + "relations": [ + "global_settings" + ] + }, + { + "name": "entity_registry", + "writable": true + }, + { + "name": "global_settings", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108, + 45, + 115, + 101, + 116, + 116, + 105, + 110, + 103, + 115 + ] + } + ] + } + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "entity_type", + "type": { + "defined": { + "name": "EntityType" + } + } + }, + { + "name": "entity_pubkey", + "type": "pubkey" + }, + { + "name": "status", + "type": { + "defined": { + "name": "WhitelistStatus" + } + } + } + ] + }, + { + "name": "set_proposed_admin", + "discriminator": [ + 160, + 170, + 199, + 240, + 246, + 244, + 199, + 2 + ], + "accounts": [ + { + "name": "proposed_admin", + "writable": true, + "signer": true + }, + { + "name": "global_settings", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108, + 45, + 115, + 101, + 116, + 116, + 105, + 110, + 103, + 115 + ] + } + ] + } + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "EntityRegistry", + "discriminator": [ + 206, + 167, + 4, + 107, + 132, + 162, + 158, + 163 + ] + }, + { + "name": "GlobalSettings", + "discriminator": [ + 109, + 67, + 50, + 55, + 2, + 20, + 148, + 62 + ] + } + ], + "events": [ + { + "name": "SetEntityWhitelistStatusEvent", + "discriminator": [ + 137, + 194, + 109, + 101, + 80, + 30, + 4, + 114 + ] + }, + { + "name": "SetProposedAdminEvent", + "discriminator": [ + 153, + 83, + 248, + 103, + 132, + 126, + 171, + 96 + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "OnlyDeployer", + "msg": "Only deployer can call this instruction" + }, + { + "code": 6001, + "name": "OnlyAdmin", + "msg": "Only admin can call this instruction" + }, + { + "code": 6002, + "name": "OnlyProposedAdmin", + "msg": "Only proposed admin can call this instruction" + }, + { + "code": 6003, + "name": "ProposedAdminIsAlreadySet", + "msg": "Proposed admin is already set" + }, + { + "code": 6004, + "name": "SetProposedAdminError", + "msg": "Can't set proposed admin - either no next admin is proposed or cooldown period is not over yet" + }, + { + "code": 6005, + "name": "CooldownTooLarge", + "msg": "Cooldown too large" + }, + { + "code": 6006, + "name": "CooldownCantBeZero", + "msg": "Cooldown can't be zero" + }, + { + "code": 6007, + "name": "MathError", + "msg": "Math error" + } + ], + "types": [ + { + "name": "EntityRegistry", + "type": { + "kind": "struct", + "fields": [ + { + "name": "entity_type", + "type": { + "defined": { + "name": "EntityType" + } + } + }, + { + "name": "entity_pubkey", + "type": "pubkey" + }, + { + "name": "status", + "type": { + "defined": { + "name": "WhitelistStatus" + } + } + }, + { + "name": "last_update", + "type": "u64" + }, + { + "name": "updated_by", + "type": "pubkey" + }, + { + "name": "bump", + "type": "u8" + } + ] + } + }, + { + "name": "EntityType", + "repr": { + "kind": "rust" + }, + "type": { + "kind": "enum", + "variants": [ + { + "name": "Validator" + }, + { + "name": "Axia" + }, + { + "name": "Solver" + } + ] + } + }, + { + "name": "GlobalSettings", + "type": { + "kind": "struct", + "fields": [ + { + "name": "admin", + "type": "pubkey" + }, + { + "name": "proposed_admin", + "type": { + "option": "pubkey" + } + }, + { + "name": "proposed_admin_cooldown", + "type": "u64" + }, + { + "name": "proposed_admin_next_change_timestamp", + "type": "u64" + }, + { + "name": "bump", + "type": "u8" + } + ] + } + }, + { + "name": "SetEntityWhitelistStatusEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "entity_type", + "type": { + "defined": { + "name": "EntityType" + } + } + }, + { + "name": "entity_pubkey", + "type": "pubkey" + }, + { + "name": "status", + "type": { + "defined": { + "name": "WhitelistStatus" + } + } + }, + { + "name": "timestamp", + "type": "u64" + }, + { + "name": "updated_by", + "type": "pubkey" + } + ] + } + }, + { + "name": "SetProposedAdminEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "old_admin", + "type": "pubkey" + }, + { + "name": "new_admin", + "type": "pubkey" + } + ] + } + }, + { + "name": "WhitelistStatus", + "repr": { + "kind": "rust" + }, + "type": { + "kind": "enum", + "variants": [ + { + "name": "Whitelisted" + }, + { + "name": "Blacklisted" + } + ] + } + } + ] +} diff --git a/packages/svm/package.json b/packages/svm/package.json index bf28823..9aeca27 100644 --- a/packages/svm/package.json +++ b/packages/svm/package.json @@ -5,13 +5,14 @@ "license": "GPL-3.0", "type": "module", "scripts": { - "build": "anchor build", - "test": "anchor test", + "build": "DEPLOYER_KEY=$(solana-keygen pubkey) anchor build", + "test": "anchor run test", "lint": "eslint . --ext .ts", "lint:fix": "yarn lint --fix" }, "dependencies": { "@coral-xyz/anchor": "^0.32.1", + "@noble/ed25519": "^3.0.0", "@solana/spl-token": "^0.4.13", "anchor-litesvm": "=0.1.0", "litesvm": "=0.1.0" @@ -22,12 +23,12 @@ "@types/mocha": "^10.0.10", "@types/node": "^22.15.18", "chai": "^5.2.0", + "eslint": "^7.9.0", + "eslint-config-mimic": "^0.0.2", "mocha": "^11.2.2", "prettier": "^2.6.2", "ts-mocha": "^10.0.0", - "typescript": "~5.5.0", - "eslint": "^7.9.0", - "eslint-config-mimic": "^0.0.2" + "typescript": "~5.5.0" }, "eslintConfig": { "extends": "eslint-config-mimic" diff --git a/packages/svm/programs/whitelist/Cargo.toml b/packages/svm/programs/whitelist/Cargo.toml new file mode 100644 index 0000000..199f4d5 --- /dev/null +++ b/packages/svm/programs/whitelist/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "whitelist" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "whitelist" + +[features] +default = [] +cpi = ["no-entrypoint"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build"] +anchor-debug = [] +custom-heap = [] +custom-panic = [] + +[dependencies] +anchor-lang = { version = "0.32.1", features = [ "init-if-needed" ] } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/packages/svm/programs/whitelist/src/constants.rs b/packages/svm/programs/whitelist/src/constants.rs new file mode 100644 index 0000000..79c805d --- /dev/null +++ b/packages/svm/programs/whitelist/src/constants.rs @@ -0,0 +1,6 @@ +pub const DEPLOYER_KEY: &'static str = env!( + "DEPLOYER_KEY", + "Please set the DEPLOYER_KEY env variable before compiling." +); + +pub const MAX_COOLDOWN: u64 = 3600 * 24 * 30; diff --git a/packages/svm/programs/whitelist/src/errors.rs b/packages/svm/programs/whitelist/src/errors.rs new file mode 100644 index 0000000..8df3897 --- /dev/null +++ b/packages/svm/programs/whitelist/src/errors.rs @@ -0,0 +1,28 @@ +use anchor_lang::prelude::*; + +#[error_code] +pub enum WhitelistError { + #[msg("Only deployer can call this instruction")] + OnlyDeployer, + + #[msg("Only admin can call this instruction")] + OnlyAdmin, + + #[msg("Only proposed admin can call this instruction")] + OnlyProposedAdmin, + + #[msg("Proposed admin is already set")] + ProposedAdminIsAlreadySet, + + #[msg("Can't set proposed admin - either no next admin is proposed or cooldown period is not over yet")] + SetProposedAdminError, + + #[msg("Cooldown too large")] + CooldownTooLarge, + + #[msg("Cooldown can't be zero")] + CooldownCantBeZero, + + #[msg("Math error")] + MathError, +} diff --git a/packages/svm/programs/whitelist/src/instructions/initialize.rs b/packages/svm/programs/whitelist/src/instructions/initialize.rs new file mode 100644 index 0000000..7aa835d --- /dev/null +++ b/packages/svm/programs/whitelist/src/instructions/initialize.rs @@ -0,0 +1,57 @@ +use anchor_lang::prelude::*; +use std::str::FromStr; + +use crate::{ + constants::{DEPLOYER_KEY, MAX_COOLDOWN}, + errors::WhitelistError, + state::GlobalSettings, +}; + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(mut)] + pub deployer: Signer<'info>, + + #[account( + init, + seeds = [b"global-settings"], + bump, + payer = deployer, + space = 8 + GlobalSettings::INIT_SPACE + )] + pub global_settings: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn initialize( + ctx: Context, + admin: Pubkey, + proposed_admin_cooldown: u64, +) -> Result<()> { + require_keys_eq!( + ctx.accounts.deployer.key(), + Pubkey::from_str(DEPLOYER_KEY).unwrap(), + WhitelistError::OnlyDeployer, + ); + + require!( + proposed_admin_cooldown > 0, + WhitelistError::CooldownCantBeZero, + ); + + require!( + proposed_admin_cooldown <= MAX_COOLDOWN, + WhitelistError::CooldownTooLarge, + ); + + let global_settings = &mut ctx.accounts.global_settings; + + global_settings.admin = admin; + global_settings.proposed_admin = None; + global_settings.proposed_admin_cooldown = proposed_admin_cooldown; + global_settings.proposed_admin_next_change_timestamp = u64::MAX; + global_settings.bump = ctx.bumps.global_settings; + + Ok(()) +} diff --git a/packages/svm/programs/whitelist/src/instructions/mod.rs b/packages/svm/programs/whitelist/src/instructions/mod.rs new file mode 100644 index 0000000..751c01b --- /dev/null +++ b/packages/svm/programs/whitelist/src/instructions/mod.rs @@ -0,0 +1,9 @@ +pub mod initialize; +pub mod propose_admin; +pub mod set_entity_whitelist_status; +pub mod set_proposed_admin; + +pub use initialize::*; +pub use propose_admin::*; +pub use set_entity_whitelist_status::*; +pub use set_proposed_admin::*; diff --git a/packages/svm/programs/whitelist/src/instructions/propose_admin.rs b/packages/svm/programs/whitelist/src/instructions/propose_admin.rs new file mode 100644 index 0000000..75d2f61 --- /dev/null +++ b/packages/svm/programs/whitelist/src/instructions/propose_admin.rs @@ -0,0 +1,33 @@ +use anchor_lang::prelude::*; + +use crate::{errors::WhitelistError, state::GlobalSettings}; + +#[derive(Accounts)] +pub struct ProposeAdmin<'info> { + #[account(mut)] + pub admin: Signer<'info>, + + #[account( + mut, + seeds = [b"global-settings"], + bump = global_settings.bump, + has_one = admin @ WhitelistError::OnlyAdmin + )] + pub global_settings: Box>, +} + +pub fn propose_admin(ctx: Context, proposed_admin: Pubkey) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + let global_settings = &mut ctx.accounts.global_settings; + + if global_settings.proposed_admin != None { + err!(WhitelistError::ProposedAdminIsAlreadySet)?; + } + + global_settings.proposed_admin = Some(proposed_admin); + global_settings.proposed_admin_next_change_timestamp = now + .checked_add(global_settings.proposed_admin_cooldown) + .ok_or(WhitelistError::MathError)?; + + Ok(()) +} diff --git a/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs b/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs new file mode 100644 index 0000000..8d2b234 --- /dev/null +++ b/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs @@ -0,0 +1,70 @@ +use anchor_lang::prelude::*; + +use crate::{ + errors::WhitelistError, + state::{EntityRegistry, GlobalSettings}, + types::{EntityType, WhitelistStatus}, +}; + +#[derive(Accounts)] +#[instruction(entity_type: EntityType, entity_pubkey: Pubkey)] +pub struct SetEntityWhitelistStatus<'info> { + #[account(mut)] + pub admin: Signer<'info>, + + #[account( + init_if_needed, + seeds = [b"entity-registry".as_ref(), &[entity_type as u8], entity_pubkey.as_ref()], + bump, + payer = admin, + space = 8 + EntityRegistry::INIT_SPACE + )] + pub entity_registry: Box>, + + #[account( + seeds = [b"global-settings"], + bump = global_settings.bump, + has_one = admin @ WhitelistError::OnlyAdmin + )] + pub global_settings: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn set_entity_whitelist_status( + ctx: Context, + entity_type: EntityType, + entity_pubkey: Pubkey, + status: WhitelistStatus, +) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + let entity_registry = &mut ctx.accounts.entity_registry; + + if entity_registry.last_update == 0 { + entity_registry.entity_type = entity_type; + entity_registry.entity_pubkey = entity_pubkey; + entity_registry.bump = ctx.bumps.entity_registry; + } + entity_registry.status = status; + entity_registry.last_update = now; + entity_registry.updated_by = ctx.accounts.admin.key(); + + emit!(SetEntityWhitelistStatusEvent { + entity_type, + entity_pubkey, + status, + timestamp: now, + updated_by: entity_registry.updated_by, + }); + + Ok(()) +} + +#[event] +pub struct SetEntityWhitelistStatusEvent { + pub entity_type: EntityType, + pub entity_pubkey: Pubkey, + pub status: WhitelistStatus, + pub timestamp: u64, + pub updated_by: Pubkey, +} diff --git a/packages/svm/programs/whitelist/src/instructions/set_proposed_admin.rs b/packages/svm/programs/whitelist/src/instructions/set_proposed_admin.rs new file mode 100644 index 0000000..48c28fc --- /dev/null +++ b/packages/svm/programs/whitelist/src/instructions/set_proposed_admin.rs @@ -0,0 +1,47 @@ +use anchor_lang::prelude::*; + +use crate::{errors::WhitelistError, state::GlobalSettings}; + +#[derive(Accounts)] +pub struct SetProposedAdmin<'info> { + #[account(mut)] + pub proposed_admin: Signer<'info>, + + #[account( + mut, + seeds = [b"global-settings"], + bump = global_settings.bump, + constraint = + global_settings.proposed_admin == Some(proposed_admin.key()) @ WhitelistError::OnlyProposedAdmin + )] + pub global_settings: Box>, +} + +pub fn set_proposed_admin(ctx: Context) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + let global_settings = &mut ctx.accounts.global_settings; + + let can_change = global_settings.proposed_admin_next_change_timestamp < now; + + match (global_settings.proposed_admin, can_change) { + (Some(_proposed_admin), true) => { + emit!(SetProposedAdminEvent { + old_admin: global_settings.admin, + new_admin: _proposed_admin, + }); + + global_settings.admin = _proposed_admin; + global_settings.proposed_admin = None; + global_settings.proposed_admin_next_change_timestamp = u64::MAX; + } + _ => err!(WhitelistError::SetProposedAdminError)?, + } + + Ok(()) +} + +#[event] +pub struct SetProposedAdminEvent { + pub old_admin: Pubkey, + pub new_admin: Pubkey, +} diff --git a/packages/svm/programs/whitelist/src/lib.rs b/packages/svm/programs/whitelist/src/lib.rs new file mode 100644 index 0000000..057866b --- /dev/null +++ b/packages/svm/programs/whitelist/src/lib.rs @@ -0,0 +1,41 @@ +use anchor_lang::prelude::*; + +declare_id!("7PwVkjnnapxytWFW69WFDLhfVZZgKhBE9m3zwcDZmncr"); + +pub mod constants; +pub mod errors; +pub mod instructions; +pub mod state; +pub mod types; + +use crate::{instructions::*, types::*}; + +#[program] +pub mod whitelist { + use super::*; + + pub fn initialize( + ctx: Context, + admin: Pubkey, + proposed_admin_cooldown: u64, + ) -> Result<()> { + instructions::initialize(ctx, admin, proposed_admin_cooldown) + } + + pub fn propose_admin(ctx: Context, proposed_admin: Pubkey) -> Result<()> { + instructions::propose_admin(ctx, proposed_admin) + } + + pub fn set_entity_whitelist_status( + ctx: Context, + entity_type: EntityType, + entity_pubkey: Pubkey, + status: WhitelistStatus, + ) -> Result<()> { + instructions::set_entity_whitelist_status(ctx, entity_type, entity_pubkey, status) + } + + pub fn set_proposed_admin(ctx: Context) -> Result<()> { + instructions::set_proposed_admin(ctx) + } +} diff --git a/packages/svm/programs/whitelist/src/state/entity_registry.rs b/packages/svm/programs/whitelist/src/state/entity_registry.rs new file mode 100644 index 0000000..a404175 --- /dev/null +++ b/packages/svm/programs/whitelist/src/state/entity_registry.rs @@ -0,0 +1,14 @@ +use anchor_lang::prelude::*; + +use crate::types::{EntityType, WhitelistStatus}; + +#[account] +#[derive(InitSpace)] +pub struct EntityRegistry { + pub entity_type: EntityType, + pub entity_pubkey: Pubkey, + pub status: WhitelistStatus, + pub last_update: u64, + pub updated_by: Pubkey, + pub bump: u8, +} diff --git a/packages/svm/programs/whitelist/src/state/global_settings.rs b/packages/svm/programs/whitelist/src/state/global_settings.rs new file mode 100644 index 0000000..0d8f2e2 --- /dev/null +++ b/packages/svm/programs/whitelist/src/state/global_settings.rs @@ -0,0 +1,11 @@ +use anchor_lang::prelude::*; + +#[account] +#[derive(InitSpace)] +pub struct GlobalSettings { + pub admin: Pubkey, + pub proposed_admin: Option, + pub proposed_admin_cooldown: u64, + pub proposed_admin_next_change_timestamp: u64, + pub bump: u8, +} diff --git a/packages/svm/programs/whitelist/src/state/mod.rs b/packages/svm/programs/whitelist/src/state/mod.rs new file mode 100644 index 0000000..e0bef0c --- /dev/null +++ b/packages/svm/programs/whitelist/src/state/mod.rs @@ -0,0 +1,5 @@ +pub mod entity_registry; +pub mod global_settings; + +pub use entity_registry::*; +pub use global_settings::*; diff --git a/packages/svm/programs/whitelist/src/types/entity_type.rs b/packages/svm/programs/whitelist/src/types/entity_type.rs new file mode 100644 index 0000000..1d798cc --- /dev/null +++ b/packages/svm/programs/whitelist/src/types/entity_type.rs @@ -0,0 +1,13 @@ +use anchor_lang::prelude::*; + +#[repr(u8)] +#[derive(Copy, Clone, AnchorSerialize, AnchorDeserialize)] +pub enum EntityType { + Validator = 1, + Axia = 2, + Solver = 3, +} + +impl anchor_lang::Space for EntityType { + const INIT_SPACE: usize = 1; +} diff --git a/packages/svm/programs/whitelist/src/types/mod.rs b/packages/svm/programs/whitelist/src/types/mod.rs new file mode 100644 index 0000000..7e7c4e8 --- /dev/null +++ b/packages/svm/programs/whitelist/src/types/mod.rs @@ -0,0 +1,5 @@ +pub mod entity_type; +pub mod whitelist_status; + +pub use entity_type::*; +pub use whitelist_status::*; diff --git a/packages/svm/programs/whitelist/src/types/whitelist_status.rs b/packages/svm/programs/whitelist/src/types/whitelist_status.rs new file mode 100644 index 0000000..a4e916b --- /dev/null +++ b/packages/svm/programs/whitelist/src/types/whitelist_status.rs @@ -0,0 +1,12 @@ +use anchor_lang::prelude::*; + +#[repr(u8)] +#[derive(Copy, Clone, AnchorSerialize, AnchorDeserialize)] +pub enum WhitelistStatus { + Whitelisted = 1, + Blacklisted = 2, +} + +impl anchor_lang::Space for WhitelistStatus { + const INIT_SPACE: usize = 1; +} diff --git a/packages/svm/rust-toolchain.toml b/packages/svm/rust-toolchain.toml new file mode 100644 index 0000000..cb684c0 --- /dev/null +++ b/packages/svm/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.89.0" +components = ["rustfmt","clippy"] +profile = "minimal" diff --git a/packages/svm/sdks/whitelist/Whitelist.ts b/packages/svm/sdks/whitelist/Whitelist.ts new file mode 100644 index 0000000..1cc2f72 --- /dev/null +++ b/packages/svm/sdks/whitelist/Whitelist.ts @@ -0,0 +1,117 @@ +import { BN, IdlTypes, Program, Provider, web3 } from '@coral-xyz/anchor' + +import * as WhitelistIDL from '../../target/idl/whitelist.json' +import { Whitelist } from '../../target/types/whitelist' + +export enum EntityType { + // eslint-disable-next-line no-unused-vars + Validator = 1, + // eslint-disable-next-line no-unused-vars + Axia = 2, + // eslint-disable-next-line no-unused-vars + Solver = 3, +} + +export enum WhitelistStatus { + // eslint-disable-next-line no-unused-vars + Whitelisted = 1, + // eslint-disable-next-line no-unused-vars + Blacklisted = 2, +} + +export default class WhitelistSDK { + protected program: Program + + constructor(provider: Provider) { + this.program = new Program(WhitelistIDL, provider) + } + + async initializeIx(admin: web3.PublicKey, proposedAdminCooldown: number): Promise { + const globalSettings = this.getGlobalSettingsPubkey() + const ix = await this.program.methods + .initialize(admin, new BN(proposedAdminCooldown)) + .accountsPartial({ + deployer: this.getSignerKey(), + globalSettings, + }) + .instruction() + return ix + } + + async proposeAdminIx(proposedAdmin: web3.PublicKey): Promise { + const globalSettings = this.getGlobalSettingsPubkey() + const ix = await this.program.methods + .proposeAdmin(proposedAdmin) + .accountsPartial({ + admin: this.getSignerKey(), + globalSettings, + }) + .instruction() + return ix + } + + async setEntityWhitelistStatusIx( + entityType: EntityType, + entityPubkey: web3.PublicKey, + status: WhitelistStatus + ): Promise { + const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) + const globalSettings = this.getGlobalSettingsPubkey() + const ix = await this.program.methods + .setEntityWhitelistStatus( + this.entityTypeToAnchorEnum(entityType), + entityPubkey, + this.whitelistStatusToAnchorEnum(status) + ) + .accountsPartial({ + admin: this.getSignerKey(), + entityRegistry, + globalSettings, + }) + .instruction() + return ix + } + + async setProposedAdminIx(): Promise { + const globalSettings = this.getGlobalSettingsPubkey() + const ix = await this.program.methods + .setProposedAdmin() + .accountsPartial({ + proposedAdmin: this.getSignerKey(), + globalSettings, + }) + .instruction() + return ix + } + + getSignerKey(): web3.PublicKey { + if (!this.program.provider.wallet) throw new Error('Must set program provider wallet') + return this.program.provider.wallet?.publicKey + } + + getGlobalSettingsPubkey(): web3.PublicKey { + return web3.PublicKey.findProgramAddressSync([Buffer.from('global-settings')], this.program.programId)[0] + } + + getEntityRegistryPubkey(entityType: EntityType, entityPubkey: web3.PublicKey): web3.PublicKey { + return web3.PublicKey.findProgramAddressSync( + [Buffer.from('entity-registry'), Buffer.from([entityType]), entityPubkey.toBuffer()], + this.program.programId + )[0] + } + + entityTypeToAnchorEnum(entityType: EntityType): IdlTypes['entityType'] { + if (entityType === EntityType.Validator) return { validator: {} } + if (entityType === EntityType.Axia) return { axia: {} } + if (entityType === EntityType.Solver) return { solver: {} } + + throw new Error(`Unsupported entity type ${entityType}`) + } + + whitelistStatusToAnchorEnum(status: WhitelistStatus): IdlTypes['whitelistStatus'] { + if (status === WhitelistStatus.Whitelisted) return { whitelisted: {} } + if (status === WhitelistStatus.Blacklisted) return { blacklisted: {} } + + throw new Error(`Unsupported whitelist status ${status}`) + } +} diff --git a/packages/svm/tests/helpers/constants.ts b/packages/svm/tests/helpers/constants.ts new file mode 100644 index 0000000..a7cd412 --- /dev/null +++ b/packages/svm/tests/helpers/constants.ts @@ -0,0 +1,51 @@ +// Test constants for time values (in seconds) +export const COOLDOWN_PERIOD = 3600 +export const COOLDOWN_PERIOD_PLUS_ONE = 3601 +export const INTENT_DEADLINE_OFFSET = 3600 +export const PROPOSAL_DEADLINE_OFFSET = 1800 +export const STALE_CLAIM_DELAY = 50 +export const STALE_CLAIM_DELAY_PLUS_ONE = 51 +export const SHORT_DEADLINE = 100 +export const MEDIUM_DEADLINE = 300 +export const LONG_DEADLINE = 500 +export const VERY_SHORT_DEADLINE = 10 +export const WARP_TIME_SHORT = 100 +export const WARP_TIME_MEDIUM = 300 +export const WARP_TIME_LONG = 500 +export const EXPIRATION_TEST_DELAY = 80 +export const EXPIRATION_TEST_DELAY_PLUS_ONE = 81 +export const DOUBLE_CLAIM_DELAY = 90 +export const DOUBLE_CLAIM_DELAY_PLUS_ONE = 91 + +// Test constants for amounts +export const DEFAULT_MAX_FEE = 1000 +export const DEFAULT_MAX_FEE_HALF = 500 +export const DEFAULT_MAX_FEE_EXCEED = 1500 +export const ACCOUNT_CLOSE_FEE = 5000 // Fee for closing accounts + +// Test constants for data +export const DEFAULT_DATA_HEX = '010203' +export const DEFAULT_TOPIC_HEX = Buffer.from(Array(32).fill(1)).toString('hex') +export const DEFAULT_EVENT_DATA_HEX = '040506' +export const EMPTY_DATA_HEX = '' +export const TEST_DATA_HEX_1 = '070809' +export const TEST_DATA_HEX_2 = '0a0b0c' +export const TEST_DATA_HEX_3 = 'deadbeef' + +// Test constants for validation +export const DEFAULT_MIN_VALIDATIONS = 1 +export const MULTIPLE_MIN_VALIDATIONS = 3 + +// Test constants for iterations +export const LARGE_EXTEND_ITERATIONS = 100 +export const MULTIPLE_PROPOSALS_COUNT = 20 + +// Test constants for hex string lengths +export const INTENT_HASH_LENGTH = 32 // bytes +export const NONCE_LENGTH = 32 // bytes +export const SIGNATURE_LENGTH = 64 // bytes + +// Test constants for cooldown validation +export const MAX_COOLDOWN = 3600 * 24 * 30 // 30 days +export const MAX_COOLDOWN_PLUS_ONE = MAX_COOLDOWN + 1 +export const MIN_COOLDOWN = 0 diff --git a/packages/svm/tests/utils.ts b/packages/svm/tests/utils.ts index 54c2529..75bdbad 100644 --- a/packages/svm/tests/utils.ts +++ b/packages/svm/tests/utils.ts @@ -1,6 +1,37 @@ -export function extractLogs(liteSvmTxMetadataString: string): string[] { - const logsMatch = liteSvmTxMetadataString.match(/logs: \[(.*?)\],/s) - if (!logsMatch) return [] +import { web3 } from '@coral-xyz/anchor' +import { LiteSVMProvider } from 'anchor-litesvm' +import { Clock, FailedTransactionMetadata, TransactionMetadata } from 'litesvm' - return logsMatch[1].split('", "') +export async function signAndSendTx( + provider: LiteSVMProvider, + tx: web3.Transaction +): Promise { + tx.recentBlockhash = provider.client.latestBlockhash() + tx.feePayer = provider.wallet.publicKey + const stx = await provider.wallet.signTransaction(tx) + return provider.client.sendTransaction(stx) +} + +export function makeTx(...ixs: web3.TransactionInstruction[]): web3.Transaction { + return new web3.Transaction().add(...ixs) +} + +export async function makeTxSignAndSend( + provider: LiteSVMProvider, + ...ixs: web3.TransactionInstruction[] +): Promise { + return signAndSendTx(provider, makeTx(...ixs)) +} + +export function warpSeconds(provider: LiteSVMProvider, seconds: number): void { + const clock = provider.client.getClock() + provider.client.setClock( + new Clock( + clock.slot, + clock.epochStartTimestamp, + clock.epoch, + clock.leaderScheduleEpoch, + clock.unixTimestamp + BigInt(seconds) + ) + ) } diff --git a/packages/svm/tests/whitelist.test.ts b/packages/svm/tests/whitelist.test.ts new file mode 100644 index 0000000..561b37a --- /dev/null +++ b/packages/svm/tests/whitelist.test.ts @@ -0,0 +1,526 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { Program, Wallet, web3 } from '@coral-xyz/anchor' +import { fromWorkspace, LiteSVMProvider } from 'anchor-litesvm' +import { expect } from 'chai' +import fs from 'fs' +import { LiteSVM } from 'litesvm' +import os from 'os' +import path from 'path' + +import WhitelistSDK, { EntityType, WhitelistStatus } from '../sdks/whitelist/Whitelist' +import * as WhitelistIDL from '../target/idl/whitelist.json' +import { Whitelist } from '../target/types/whitelist' +import { COOLDOWN_PERIOD, COOLDOWN_PERIOD_PLUS_ONE, MAX_COOLDOWN_PLUS_ONE, MIN_COOLDOWN } from './helpers/constants' +import { expectTransactionError } from './helpers/settler-helpers' +import { makeTxSignAndSend, warpSeconds } from './utils' + +describe('Whitelist Program', () => { + let client: LiteSVM + + let deployer: web3.Keypair + let admin: web3.Keypair + let proposedAdmin: web3.Keypair + let malicious: web3.Keypair + + let deployerProvider: LiteSVMProvider + let adminProvider: LiteSVMProvider + let proposedAdminProvider: LiteSVMProvider + let maliciousProvider: LiteSVMProvider + + let program: Program + + let deployerSdk: WhitelistSDK + let adminSdk: WhitelistSDK + let proposedAdminSdk: WhitelistSDK + let maliciousSdk: WhitelistSDK + + before(async () => { + deployer = web3.Keypair.fromSecretKey( + Uint8Array.from(JSON.parse(fs.readFileSync(path.join(os.homedir(), '.config', 'solana', 'id.json'), 'utf8'))) + ) + admin = web3.Keypair.generate() + proposedAdmin = web3.Keypair.generate() + malicious = web3.Keypair.generate() + + client = fromWorkspace(path.join(__dirname, '../')).withBuiltins() + + deployerProvider = new LiteSVMProvider(client, new Wallet(deployer)) + adminProvider = new LiteSVMProvider(client, new Wallet(admin)) + proposedAdminProvider = new LiteSVMProvider(client, new Wallet(proposedAdmin)) + maliciousProvider = new LiteSVMProvider(client, new Wallet(malicious)) + + program = new Program(WhitelistIDL as any, deployerProvider) + + deployerSdk = new WhitelistSDK(deployerProvider) + adminSdk = new WhitelistSDK(adminProvider) + proposedAdminSdk = new WhitelistSDK(proposedAdminProvider) + maliciousSdk = new WhitelistSDK(maliciousProvider) + + deployerProvider.client.airdrop(deployer.publicKey, BigInt(100_000_000_000)) + deployerProvider.client.airdrop(admin.publicKey, BigInt(100_000_000_000)) + deployerProvider.client.airdrop(proposedAdmin.publicKey, BigInt(100_000_000_000)) + deployerProvider.client.airdrop(malicious.publicKey, BigInt(100_000_000_000)) + }) + + beforeEach(() => { + client.expireBlockhash() + }) + + describe('Whitelist', () => { + describe('initialize', () => { + it('cannot initialize if not deployer', async () => { + const newAdmin = web3.Keypair.generate().publicKey + const proposedAdminCooldown = COOLDOWN_PERIOD + + const ix = await maliciousSdk.initializeIx(newAdmin, proposedAdminCooldown) + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, 'Only deployer can call this instruction') + }) + + it('cannot initialize with cooldown = 0', async () => { + const proposedAdminCooldown = MIN_COOLDOWN + + const ix = await deployerSdk.initializeIx(admin.publicKey, proposedAdminCooldown) + const res = await makeTxSignAndSend(deployerProvider, ix) + + expectTransactionError(res, "Cooldown can't be zero") + }) + + it('cannot initialize with cooldown > MAX_COOLDOWN', async () => { + const proposedAdminCooldown = MAX_COOLDOWN_PLUS_ONE + + const ix = await deployerSdk.initializeIx(admin.publicKey, proposedAdminCooldown) + const res = await makeTxSignAndSend(deployerProvider, ix) + + expectTransactionError(res, 'Cooldown too large') + }) + + it('should initialize', async () => { + const proposedAdminCooldown = COOLDOWN_PERIOD + + const ix = await deployerSdk.initializeIx(admin.publicKey, proposedAdminCooldown) + await makeTxSignAndSend(deployerProvider, ix) + + const settings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) + expect(settings.proposedAdmin).to.be.null + expect(settings.proposedAdminCooldown.toNumber()).to.be.eq(COOLDOWN_PERIOD) + expect(settings.proposedAdminNextChangeTimestamp.toString()).to.be.eq('18446744073709551615') // u64::MAX + }) + + it('cannot call initialize again', async () => { + const proposedAdminCooldown = COOLDOWN_PERIOD + + const ix = await deployerSdk.initializeIx(admin.publicKey, proposedAdminCooldown) + const res = await makeTxSignAndSend(deployerProvider, ix) + + expectTransactionError(res, 'already in use') + }) + }) + + describe('propose_admin and set_proposed_admin', () => { + it('cannot propose admin if not admin', async () => { + const ix = await maliciousSdk.proposeAdminIx(proposedAdmin.publicKey) + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, 'Only admin can call this instruction') + }) + + it('cannot set proposed admin if no next admin was proposed yet', async () => { + const ix = await adminSdk.setProposedAdminIx() + const res = await makeTxSignAndSend(adminProvider, ix) + + expectTransactionError(res, 'Only proposed admin can call this instruction') + }) + + it('should propose admin successfully', async () => { + const ix = await adminSdk.proposeAdminIx(proposedAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) + + const updatedSettings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + expect(updatedSettings.proposedAdmin?.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + expect(updatedSettings.proposedAdminNextChangeTimestamp.toNumber()).to.be.greaterThan(0) + }) + + it('cannot propose admin if one is already proposed', async () => { + const proposedAdmin2 = web3.Keypair.generate().publicKey + + const ix = await adminSdk.proposeAdminIx(proposedAdmin2) + const res = await makeTxSignAndSend(adminProvider, ix) + + expectTransactionError(res, 'Proposed admin is already set') + }) + + it('cannot set proposed admin if not proposed admin', async () => { + const ix = await maliciousSdk.setProposedAdminIx() + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, 'Only proposed admin can call this instruction') + }) + + it('cannot set proposed admin if cooldown hasnt passed', async () => { + const ix = await proposedAdminSdk.setProposedAdminIx() + const res = await makeTxSignAndSend(proposedAdminProvider, ix) + + expectTransactionError( + res, + "Can't set proposed admin - either no next admin is proposed or cooldown period is not over yet" + ) + }) + + it('should set proposed admin successfully after cooldown', async () => { + warpSeconds(deployerProvider, COOLDOWN_PERIOD_PLUS_ONE) + + const ix = await proposedAdminSdk.setProposedAdminIx() + await makeTxSignAndSend(proposedAdminProvider, ix) + + const updatedSettings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) + expect(updatedSettings.admin.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + expect(updatedSettings.proposedAdmin).to.be.null + expect(updatedSettings.proposedAdminNextChangeTimestamp.toString()).to.be.eq('18446744073709551615') // u64::MAX + }) + + it('should reset admin to original one for next tests', async () => { + const ix = await proposedAdminSdk.proposeAdminIx(admin.publicKey) + await makeTxSignAndSend(proposedAdminProvider, ix) + + warpSeconds(deployerProvider, COOLDOWN_PERIOD_PLUS_ONE) + + const ix2 = await adminSdk.setProposedAdminIx() + await makeTxSignAndSend(adminProvider, ix2) + + const updatedSettings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) + expect(updatedSettings.admin.toString()).to.be.eq(admin.publicKey.toString()) + expect(updatedSettings.proposedAdmin).to.be.null + expect(updatedSettings.proposedAdminNextChangeTimestamp.toString()).to.be.eq('18446744073709551615') // u64::MAX + }) + + it('should propose same admin as current admin', async () => { + const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + const currentAdmin = settings.admin + + const ix = await adminSdk.proposeAdminIx(currentAdmin) + await makeTxSignAndSend(adminProvider, ix) + + const updatedSettings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + expect(updatedSettings.proposedAdmin?.toString()).to.be.eq(currentAdmin.toString()) + + warpSeconds(deployerProvider, COOLDOWN_PERIOD_PLUS_ONE) + + await makeTxSignAndSend(adminProvider, await adminSdk.setProposedAdminIx()) + const updatedSettings2 = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + expect(updatedSettings2.admin.toString()).to.be.eq(admin.publicKey.toString()) + expect(updatedSettings2.proposedAdmin).to.be.null + }) + + it('should calculate proposed_admin_next_change_timestamp correctly', async () => { + const settingsBefore = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + const cooldown = settingsBefore.proposedAdminCooldown.toNumber() + + const clockBefore = deployerProvider.client.getClock() + const nowBefore = Number(clockBefore.unixTimestamp) + + const ix = await adminSdk.proposeAdminIx(proposedAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) + + const settingsAfter = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + const expectedTimestamp = nowBefore + cooldown + expect(settingsAfter.proposedAdminNextChangeTimestamp.toNumber()).to.be.eq(expectedTimestamp) + }) + }) + + describe('set_entity_whitelist_status', () => { + let validator: web3.PublicKey + let axia: web3.PublicKey + let solver: web3.PublicKey + let validator2: web3.PublicKey + let axia2: web3.PublicKey + let solver2: web3.PublicKey + + before(() => { + validator = web3.Keypair.generate().publicKey + axia = web3.Keypair.generate().publicKey + solver = web3.Keypair.generate().publicKey + validator2 = web3.Keypair.generate().publicKey + axia2 = web3.Keypair.generate().publicKey + solver2 = web3.Keypair.generate().publicKey + }) + + it('cannot set status if not admin', async () => { + const ix = await maliciousSdk.setEntityWhitelistStatusIx( + EntityType.Validator, + validator, + WhitelistStatus.Whitelisted + ) + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, 'Only admin can call this instruction') + }) + + it('should set whitelist status successfully (validator)', async () => { + const ix = await adminSdk.setEntityWhitelistStatusIx( + EntityType.Validator, + validator, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(adminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + }) + + it('should set whitelist status successfully (axia)', async () => { + const ix = await adminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(adminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + }) + + it('should set whitelist status successfully (solver)', async () => { + const ix = await adminSdk.setEntityWhitelistStatusIx(EntityType.Solver, solver, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(adminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + }) + + it('should warp some seconds and change admin for next tests', async () => { + const diff = COOLDOWN_PERIOD_PLUS_ONE + + const then = Number(client.getClock().unixTimestamp) + const ix = await adminSdk.proposeAdminIx(proposedAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) + + warpSeconds(deployerProvider, diff) + + const now = Number(client.getClock().unixTimestamp) + expect(now - then).to.be.eq(diff) + + const ix2 = await proposedAdminSdk.setProposedAdminIx() + await makeTxSignAndSend(proposedAdminProvider, ix2) + }) + + it('should update status correctly (whitelist to blacklist transition) (validator)', async () => { + const ix = await proposedAdminSdk.setEntityWhitelistStatusIx( + EntityType.Validator, + validator, + WhitelistStatus.Blacklisted + ) + await makeTxSignAndSend(proposedAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + proposedAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) + expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + }) + + it('should update status correctly (whitelist to blacklist transition) (axia)', async () => { + const ix = await proposedAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Blacklisted) + await makeTxSignAndSend(proposedAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + proposedAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) + expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + }) + + it('should update status correctly (whitelist to blacklist transition) (solver)', async () => { + const ix = await proposedAdminSdk.setEntityWhitelistStatusIx( + EntityType.Solver, + solver, + WhitelistStatus.Blacklisted + ) + await makeTxSignAndSend(proposedAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + proposedAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) + expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + }) + + it('should update status correctly (blacklist to whitelist transition) (validator)', async () => { + const ix = await proposedAdminSdk.setEntityWhitelistStatusIx( + EntityType.Validator, + validator, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(proposedAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + proposedAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + }) + + it('should update status correctly (blacklist to whitelist transition) (axia)', async () => { + const ix = await proposedAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(proposedAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + proposedAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + }) + + it('should update status correctly (blacklist to whitelist transition) (solver)', async () => { + const ix = await proposedAdminSdk.setEntityWhitelistStatusIx( + EntityType.Solver, + solver, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(proposedAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + proposedAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) + const now = Number(client.getClock().unixTimestamp) + + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) + expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + }) + + it('should reset admin to original one for next tests', async () => { + const ix = await proposedAdminSdk.proposeAdminIx(admin.publicKey) + await makeTxSignAndSend(proposedAdminProvider, ix) + + warpSeconds(deployerProvider, COOLDOWN_PERIOD_PLUS_ONE) + + const ix2 = await adminSdk.setProposedAdminIx() + await makeTxSignAndSend(adminProvider, ix2) + + const updatedSettings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) + expect(updatedSettings.admin.toString()).to.be.eq(admin.publicKey.toString()) + expect(updatedSettings.proposedAdmin).to.be.null + expect(updatedSettings.proposedAdminNextChangeTimestamp.toString()).to.be.eq('18446744073709551615') // u64::MAX + }) + + it('should whitelist another validator', async () => { + const ix = await adminSdk.setEntityWhitelistStatusIx( + EntityType.Validator, + validator2, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(adminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator2) + ) + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) + expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + }) + + it('should whitelist another axia', async () => { + const ix = await adminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia2, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(adminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia2) + ) + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) + expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + }) + + it('should whitelist another solver', async () => { + const ix = await adminSdk.setEntityWhitelistStatusIx(EntityType.Solver, solver2, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(adminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver2) + ) + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) + expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) + expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + }) + + it('should create separate accounts for same pubkey with different entity types', async () => { + const ix1 = await adminSdk.setEntityWhitelistStatusIx(EntityType.Validator, axia, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(adminProvider, ix1) + + const validatorRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) + ) + const axiaRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) + + expect(validatorRegistry.entityType).to.deep.include({ validator: {} }) + expect(validatorRegistry.status).to.deep.include({ whitelisted: {} }) + expect(axiaRegistry.entityType).to.deep.include({ axia: {} }) + expect(axiaRegistry.status).to.deep.include({ whitelisted: {} }) + + const validatorPda = adminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) + const axiaPda = adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + expect(validatorPda.toString()).to.not.eq(axiaPda.toString()) + }) + }) + }) +}) From f5e81b93c57366aabb35ebceb33287846d917d62 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 12:01:18 -0300 Subject: [PATCH 02/12] Fix tests --- packages/svm/tests/helpers/settler-helpers.ts | 18 ++++++++ packages/svm/tests/settler.test.ts | 46 ------------------- 2 files changed, 18 insertions(+), 46 deletions(-) create mode 100644 packages/svm/tests/helpers/settler-helpers.ts delete mode 100644 packages/svm/tests/settler.test.ts diff --git a/packages/svm/tests/helpers/settler-helpers.ts b/packages/svm/tests/helpers/settler-helpers.ts new file mode 100644 index 0000000..f149059 --- /dev/null +++ b/packages/svm/tests/helpers/settler-helpers.ts @@ -0,0 +1,18 @@ +import { expect } from 'chai' +import { FailedTransactionMetadata, TransactionMetadata } from 'litesvm' + +/** + * Helper to expect transaction errors consistently + */ +export function expectTransactionError( + res: TransactionMetadata | FailedTransactionMetadata | string, + expectedMessage: string +): void { + expect(typeof res).to.not.be.eq('TransactionMetadata') + + if (typeof res === 'string') { + expect(res).to.include(expectedMessage) + } else { + expect(res.toString()).to.include(expectedMessage) + } +} diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts deleted file mode 100644 index d5ff4ce..0000000 --- a/packages/svm/tests/settler.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ - -import { Program, Wallet } from '@coral-xyz/anchor' -import { Keypair } from '@solana/web3.js' -import { fromWorkspace, LiteSVMProvider } from 'anchor-litesvm' -import { expect } from 'chai' -import path from 'path' - -import * as SettlerIDL from '../target/idl/settler.json' -import { Settler } from '../target/types/settler' -import { extractLogs } from './utils' - -describe('Settler Program', () => { - let client: any - let provider: LiteSVMProvider - let admin: Keypair - let malicious: Keypair - let program: Program - - before(async () => { - admin = Keypair.generate() - malicious = Keypair.generate() - - client = fromWorkspace(path.join(__dirname, '../')).withBuiltins() - - provider = new LiteSVMProvider(client, new Wallet(admin)) - program = new Program(SettlerIDL as any, provider) - - // Airdrop initial lamports - provider.client.airdrop(admin.publicKey, BigInt(100_000_000_000)) - provider.client.airdrop(malicious.publicKey, BigInt(100_000_000_000)) - }) - - describe('Settler', () => { - it('should call initialize', async () => { - const tx = await program.methods.initialize().transaction() - tx.recentBlockhash = provider.client.latestBlockhash() - tx.feePayer = admin.publicKey - tx.sign(admin) - const res = provider.client.sendTransaction(tx) - - expect(extractLogs(res.toString()).join('').includes(`Greetings from: ${program.programId.toString()}`)).to.be.ok - }) - }) -}) From 804e2a46f42db804b09167ef66da8932b701a8eb Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 12:47:23 -0300 Subject: [PATCH 03/12] Code review: simplify set admin process --- .../svm/programs/whitelist/src/constants.rs | 2 - packages/svm/programs/whitelist/src/errors.rs | 15 - .../whitelist/src/instructions/initialize.rs | 16 +- .../whitelist/src/instructions/mod.rs | 6 +- .../src/instructions/propose_admin.rs | 33 --- .../whitelist/src/instructions/set_admin.rs | 25 ++ .../src/instructions/set_proposed_admin.rs | 47 --- packages/svm/programs/whitelist/src/lib.rs | 14 +- .../whitelist/src/state/global_settings.rs | 3 - packages/svm/sdks/whitelist/Whitelist.ts | 22 +- packages/svm/tests/whitelist.test.ts | 275 +++++------------- 11 files changed, 109 insertions(+), 349 deletions(-) delete mode 100644 packages/svm/programs/whitelist/src/instructions/propose_admin.rs create mode 100644 packages/svm/programs/whitelist/src/instructions/set_admin.rs delete mode 100644 packages/svm/programs/whitelist/src/instructions/set_proposed_admin.rs diff --git a/packages/svm/programs/whitelist/src/constants.rs b/packages/svm/programs/whitelist/src/constants.rs index 79c805d..756ec67 100644 --- a/packages/svm/programs/whitelist/src/constants.rs +++ b/packages/svm/programs/whitelist/src/constants.rs @@ -2,5 +2,3 @@ pub const DEPLOYER_KEY: &'static str = env!( "DEPLOYER_KEY", "Please set the DEPLOYER_KEY env variable before compiling." ); - -pub const MAX_COOLDOWN: u64 = 3600 * 24 * 30; diff --git a/packages/svm/programs/whitelist/src/errors.rs b/packages/svm/programs/whitelist/src/errors.rs index 8df3897..c3e07b9 100644 --- a/packages/svm/programs/whitelist/src/errors.rs +++ b/packages/svm/programs/whitelist/src/errors.rs @@ -8,21 +8,6 @@ pub enum WhitelistError { #[msg("Only admin can call this instruction")] OnlyAdmin, - #[msg("Only proposed admin can call this instruction")] - OnlyProposedAdmin, - - #[msg("Proposed admin is already set")] - ProposedAdminIsAlreadySet, - - #[msg("Can't set proposed admin - either no next admin is proposed or cooldown period is not over yet")] - SetProposedAdminError, - - #[msg("Cooldown too large")] - CooldownTooLarge, - - #[msg("Cooldown can't be zero")] - CooldownCantBeZero, - #[msg("Math error")] MathError, } diff --git a/packages/svm/programs/whitelist/src/instructions/initialize.rs b/packages/svm/programs/whitelist/src/instructions/initialize.rs index 7aa835d..3233998 100644 --- a/packages/svm/programs/whitelist/src/instructions/initialize.rs +++ b/packages/svm/programs/whitelist/src/instructions/initialize.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use std::str::FromStr; use crate::{ - constants::{DEPLOYER_KEY, MAX_COOLDOWN}, + constants::DEPLOYER_KEY, errors::WhitelistError, state::GlobalSettings, }; @@ -27,7 +27,6 @@ pub struct Initialize<'info> { pub fn initialize( ctx: Context, admin: Pubkey, - proposed_admin_cooldown: u64, ) -> Result<()> { require_keys_eq!( ctx.accounts.deployer.key(), @@ -35,22 +34,9 @@ pub fn initialize( WhitelistError::OnlyDeployer, ); - require!( - proposed_admin_cooldown > 0, - WhitelistError::CooldownCantBeZero, - ); - - require!( - proposed_admin_cooldown <= MAX_COOLDOWN, - WhitelistError::CooldownTooLarge, - ); - let global_settings = &mut ctx.accounts.global_settings; global_settings.admin = admin; - global_settings.proposed_admin = None; - global_settings.proposed_admin_cooldown = proposed_admin_cooldown; - global_settings.proposed_admin_next_change_timestamp = u64::MAX; global_settings.bump = ctx.bumps.global_settings; Ok(()) diff --git a/packages/svm/programs/whitelist/src/instructions/mod.rs b/packages/svm/programs/whitelist/src/instructions/mod.rs index 751c01b..2d033f0 100644 --- a/packages/svm/programs/whitelist/src/instructions/mod.rs +++ b/packages/svm/programs/whitelist/src/instructions/mod.rs @@ -1,9 +1,7 @@ pub mod initialize; -pub mod propose_admin; +pub mod set_admin; pub mod set_entity_whitelist_status; -pub mod set_proposed_admin; pub use initialize::*; -pub use propose_admin::*; +pub use set_admin::*; pub use set_entity_whitelist_status::*; -pub use set_proposed_admin::*; diff --git a/packages/svm/programs/whitelist/src/instructions/propose_admin.rs b/packages/svm/programs/whitelist/src/instructions/propose_admin.rs deleted file mode 100644 index 75d2f61..0000000 --- a/packages/svm/programs/whitelist/src/instructions/propose_admin.rs +++ /dev/null @@ -1,33 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::{errors::WhitelistError, state::GlobalSettings}; - -#[derive(Accounts)] -pub struct ProposeAdmin<'info> { - #[account(mut)] - pub admin: Signer<'info>, - - #[account( - mut, - seeds = [b"global-settings"], - bump = global_settings.bump, - has_one = admin @ WhitelistError::OnlyAdmin - )] - pub global_settings: Box>, -} - -pub fn propose_admin(ctx: Context, proposed_admin: Pubkey) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; - let global_settings = &mut ctx.accounts.global_settings; - - if global_settings.proposed_admin != None { - err!(WhitelistError::ProposedAdminIsAlreadySet)?; - } - - global_settings.proposed_admin = Some(proposed_admin); - global_settings.proposed_admin_next_change_timestamp = now - .checked_add(global_settings.proposed_admin_cooldown) - .ok_or(WhitelistError::MathError)?; - - Ok(()) -} diff --git a/packages/svm/programs/whitelist/src/instructions/set_admin.rs b/packages/svm/programs/whitelist/src/instructions/set_admin.rs new file mode 100644 index 0000000..71d8b41 --- /dev/null +++ b/packages/svm/programs/whitelist/src/instructions/set_admin.rs @@ -0,0 +1,25 @@ +use anchor_lang::prelude::*; + +use crate::{errors::WhitelistError, state::GlobalSettings}; + +#[derive(Accounts)] +pub struct SetAdmin<'info> { + #[account(mut)] + pub admin: Signer<'info>, + + #[account( + mut, + seeds = [b"global-settings"], + bump = global_settings.bump, + has_one = admin @ WhitelistError::OnlyAdmin + )] + pub global_settings: Box>, +} + +pub fn set_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { + let global_settings = &mut ctx.accounts.global_settings; + + global_settings.admin = new_admin; + + Ok(()) +} diff --git a/packages/svm/programs/whitelist/src/instructions/set_proposed_admin.rs b/packages/svm/programs/whitelist/src/instructions/set_proposed_admin.rs deleted file mode 100644 index 48c28fc..0000000 --- a/packages/svm/programs/whitelist/src/instructions/set_proposed_admin.rs +++ /dev/null @@ -1,47 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::{errors::WhitelistError, state::GlobalSettings}; - -#[derive(Accounts)] -pub struct SetProposedAdmin<'info> { - #[account(mut)] - pub proposed_admin: Signer<'info>, - - #[account( - mut, - seeds = [b"global-settings"], - bump = global_settings.bump, - constraint = - global_settings.proposed_admin == Some(proposed_admin.key()) @ WhitelistError::OnlyProposedAdmin - )] - pub global_settings: Box>, -} - -pub fn set_proposed_admin(ctx: Context) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; - let global_settings = &mut ctx.accounts.global_settings; - - let can_change = global_settings.proposed_admin_next_change_timestamp < now; - - match (global_settings.proposed_admin, can_change) { - (Some(_proposed_admin), true) => { - emit!(SetProposedAdminEvent { - old_admin: global_settings.admin, - new_admin: _proposed_admin, - }); - - global_settings.admin = _proposed_admin; - global_settings.proposed_admin = None; - global_settings.proposed_admin_next_change_timestamp = u64::MAX; - } - _ => err!(WhitelistError::SetProposedAdminError)?, - } - - Ok(()) -} - -#[event] -pub struct SetProposedAdminEvent { - pub old_admin: Pubkey, - pub new_admin: Pubkey, -} diff --git a/packages/svm/programs/whitelist/src/lib.rs b/packages/svm/programs/whitelist/src/lib.rs index 057866b..a92b511 100644 --- a/packages/svm/programs/whitelist/src/lib.rs +++ b/packages/svm/programs/whitelist/src/lib.rs @@ -17,13 +17,15 @@ pub mod whitelist { pub fn initialize( ctx: Context, admin: Pubkey, - proposed_admin_cooldown: u64, ) -> Result<()> { - instructions::initialize(ctx, admin, proposed_admin_cooldown) + instructions::initialize(ctx, admin) } - pub fn propose_admin(ctx: Context, proposed_admin: Pubkey) -> Result<()> { - instructions::propose_admin(ctx, proposed_admin) + pub fn set_admin( + ctx: Context, + new_admin: Pubkey, + ) -> Result<()> { + instructions::set_admin(ctx, new_admin) } pub fn set_entity_whitelist_status( @@ -34,8 +36,4 @@ pub mod whitelist { ) -> Result<()> { instructions::set_entity_whitelist_status(ctx, entity_type, entity_pubkey, status) } - - pub fn set_proposed_admin(ctx: Context) -> Result<()> { - instructions::set_proposed_admin(ctx) - } } diff --git a/packages/svm/programs/whitelist/src/state/global_settings.rs b/packages/svm/programs/whitelist/src/state/global_settings.rs index 0d8f2e2..6027636 100644 --- a/packages/svm/programs/whitelist/src/state/global_settings.rs +++ b/packages/svm/programs/whitelist/src/state/global_settings.rs @@ -4,8 +4,5 @@ use anchor_lang::prelude::*; #[derive(InitSpace)] pub struct GlobalSettings { pub admin: Pubkey, - pub proposed_admin: Option, - pub proposed_admin_cooldown: u64, - pub proposed_admin_next_change_timestamp: u64, pub bump: u8, } diff --git a/packages/svm/sdks/whitelist/Whitelist.ts b/packages/svm/sdks/whitelist/Whitelist.ts index 1cc2f72..6293a3d 100644 --- a/packages/svm/sdks/whitelist/Whitelist.ts +++ b/packages/svm/sdks/whitelist/Whitelist.ts @@ -1,4 +1,4 @@ -import { BN, IdlTypes, Program, Provider, web3 } from '@coral-xyz/anchor' +import { IdlTypes, Program, Provider, web3 } from '@coral-xyz/anchor' import * as WhitelistIDL from '../../target/idl/whitelist.json' import { Whitelist } from '../../target/types/whitelist' @@ -26,10 +26,10 @@ export default class WhitelistSDK { this.program = new Program(WhitelistIDL, provider) } - async initializeIx(admin: web3.PublicKey, proposedAdminCooldown: number): Promise { + async initializeIx(admin: web3.PublicKey): Promise { const globalSettings = this.getGlobalSettingsPubkey() const ix = await this.program.methods - .initialize(admin, new BN(proposedAdminCooldown)) + .initialize(admin) .accountsPartial({ deployer: this.getSignerKey(), globalSettings, @@ -38,10 +38,10 @@ export default class WhitelistSDK { return ix } - async proposeAdminIx(proposedAdmin: web3.PublicKey): Promise { + async setAdmin(newAdmin: web3.PublicKey): Promise { const globalSettings = this.getGlobalSettingsPubkey() const ix = await this.program.methods - .proposeAdmin(proposedAdmin) + .setAdmin(newAdmin) .accountsPartial({ admin: this.getSignerKey(), globalSettings, @@ -72,18 +72,6 @@ export default class WhitelistSDK { return ix } - async setProposedAdminIx(): Promise { - const globalSettings = this.getGlobalSettingsPubkey() - const ix = await this.program.methods - .setProposedAdmin() - .accountsPartial({ - proposedAdmin: this.getSignerKey(), - globalSettings, - }) - .instruction() - return ix - } - getSignerKey(): web3.PublicKey { if (!this.program.provider.wallet) throw new Error('Must set program provider wallet') return this.program.provider.wallet?.publicKey diff --git a/packages/svm/tests/whitelist.test.ts b/packages/svm/tests/whitelist.test.ts index 561b37a..774166c 100644 --- a/packages/svm/tests/whitelist.test.ts +++ b/packages/svm/tests/whitelist.test.ts @@ -12,7 +12,6 @@ import path from 'path' import WhitelistSDK, { EntityType, WhitelistStatus } from '../sdks/whitelist/Whitelist' import * as WhitelistIDL from '../target/idl/whitelist.json' import { Whitelist } from '../target/types/whitelist' -import { COOLDOWN_PERIOD, COOLDOWN_PERIOD_PLUS_ONE, MAX_COOLDOWN_PLUS_ONE, MIN_COOLDOWN } from './helpers/constants' import { expectTransactionError } from './helpers/settler-helpers' import { makeTxSignAndSend, warpSeconds } from './utils' @@ -21,19 +20,19 @@ describe('Whitelist Program', () => { let deployer: web3.Keypair let admin: web3.Keypair - let proposedAdmin: web3.Keypair + let otherAdmin: web3.Keypair let malicious: web3.Keypair let deployerProvider: LiteSVMProvider let adminProvider: LiteSVMProvider - let proposedAdminProvider: LiteSVMProvider + let otherAdminProvider: LiteSVMProvider let maliciousProvider: LiteSVMProvider let program: Program let deployerSdk: WhitelistSDK let adminSdk: WhitelistSDK - let proposedAdminSdk: WhitelistSDK + let otherAdminSdk: WhitelistSDK let maliciousSdk: WhitelistSDK before(async () => { @@ -41,27 +40,30 @@ describe('Whitelist Program', () => { Uint8Array.from(JSON.parse(fs.readFileSync(path.join(os.homedir(), '.config', 'solana', 'id.json'), 'utf8'))) ) admin = web3.Keypair.generate() - proposedAdmin = web3.Keypair.generate() + otherAdmin = web3.Keypair.generate() malicious = web3.Keypair.generate() client = fromWorkspace(path.join(__dirname, '../')).withBuiltins() deployerProvider = new LiteSVMProvider(client, new Wallet(deployer)) adminProvider = new LiteSVMProvider(client, new Wallet(admin)) - proposedAdminProvider = new LiteSVMProvider(client, new Wallet(proposedAdmin)) + otherAdminProvider = new LiteSVMProvider(client, new Wallet(otherAdmin)) maliciousProvider = new LiteSVMProvider(client, new Wallet(malicious)) program = new Program(WhitelistIDL as any, deployerProvider) deployerSdk = new WhitelistSDK(deployerProvider) adminSdk = new WhitelistSDK(adminProvider) - proposedAdminSdk = new WhitelistSDK(proposedAdminProvider) + otherAdminSdk = new WhitelistSDK(otherAdminProvider) maliciousSdk = new WhitelistSDK(maliciousProvider) deployerProvider.client.airdrop(deployer.publicKey, BigInt(100_000_000_000)) deployerProvider.client.airdrop(admin.publicKey, BigInt(100_000_000_000)) - deployerProvider.client.airdrop(proposedAdmin.publicKey, BigInt(100_000_000_000)) + deployerProvider.client.airdrop(otherAdmin.publicKey, BigInt(100_000_000_000)) deployerProvider.client.airdrop(malicious.publicKey, BigInt(100_000_000_000)) + + // Warp so that we're not at t=0 + warpSeconds(deployerProvider, 100) }) beforeEach(() => { @@ -72,163 +74,49 @@ describe('Whitelist Program', () => { describe('initialize', () => { it('cannot initialize if not deployer', async () => { const newAdmin = web3.Keypair.generate().publicKey - const proposedAdminCooldown = COOLDOWN_PERIOD - const ix = await maliciousSdk.initializeIx(newAdmin, proposedAdminCooldown) + const ix = await maliciousSdk.initializeIx(newAdmin) const res = await makeTxSignAndSend(maliciousProvider, ix) expectTransactionError(res, 'Only deployer can call this instruction') }) - it('cannot initialize with cooldown = 0', async () => { - const proposedAdminCooldown = MIN_COOLDOWN - - const ix = await deployerSdk.initializeIx(admin.publicKey, proposedAdminCooldown) - const res = await makeTxSignAndSend(deployerProvider, ix) - - expectTransactionError(res, "Cooldown can't be zero") - }) - - it('cannot initialize with cooldown > MAX_COOLDOWN', async () => { - const proposedAdminCooldown = MAX_COOLDOWN_PLUS_ONE - - const ix = await deployerSdk.initializeIx(admin.publicKey, proposedAdminCooldown) - const res = await makeTxSignAndSend(deployerProvider, ix) - - expectTransactionError(res, 'Cooldown too large') - }) - it('should initialize', async () => { - const proposedAdminCooldown = COOLDOWN_PERIOD - - const ix = await deployerSdk.initializeIx(admin.publicKey, proposedAdminCooldown) + const ix = await deployerSdk.initializeIx(admin.publicKey) await makeTxSignAndSend(deployerProvider, ix) const settings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) - expect(settings.proposedAdmin).to.be.null - expect(settings.proposedAdminCooldown.toNumber()).to.be.eq(COOLDOWN_PERIOD) - expect(settings.proposedAdminNextChangeTimestamp.toString()).to.be.eq('18446744073709551615') // u64::MAX }) it('cannot call initialize again', async () => { - const proposedAdminCooldown = COOLDOWN_PERIOD - - const ix = await deployerSdk.initializeIx(admin.publicKey, proposedAdminCooldown) + const ix = await deployerSdk.initializeIx(admin.publicKey) const res = await makeTxSignAndSend(deployerProvider, ix) expectTransactionError(res, 'already in use') }) }) - describe('propose_admin and set_proposed_admin', () => { - it('cannot propose admin if not admin', async () => { - const ix = await maliciousSdk.proposeAdminIx(proposedAdmin.publicKey) + describe('set_admin', () => { + it('cannot set admin if not admin', async () => { + const newAdmin = web3.Keypair.generate().publicKey + + const ix = await maliciousSdk.setAdmin(newAdmin) const res = await makeTxSignAndSend(maliciousProvider, ix) expectTransactionError(res, 'Only admin can call this instruction') }) - it('cannot set proposed admin if no next admin was proposed yet', async () => { - const ix = await adminSdk.setProposedAdminIx() - const res = await makeTxSignAndSend(adminProvider, ix) - - expectTransactionError(res, 'Only proposed admin can call this instruction') - }) - - it('should propose admin successfully', async () => { - const ix = await adminSdk.proposeAdminIx(proposedAdmin.publicKey) + it('can set admin', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) await makeTxSignAndSend(adminProvider, ix) - const updatedSettings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - expect(updatedSettings.proposedAdmin?.toString()).to.be.eq(proposedAdmin.publicKey.toString()) - expect(updatedSettings.proposedAdminNextChangeTimestamp.toNumber()).to.be.greaterThan(0) - }) - - it('cannot propose admin if one is already proposed', async () => { - const proposedAdmin2 = web3.Keypair.generate().publicKey - - const ix = await adminSdk.proposeAdminIx(proposedAdmin2) - const res = await makeTxSignAndSend(adminProvider, ix) - - expectTransactionError(res, 'Proposed admin is already set') - }) - - it('cannot set proposed admin if not proposed admin', async () => { - const ix = await maliciousSdk.setProposedAdminIx() - const res = await makeTxSignAndSend(maliciousProvider, ix) - - expectTransactionError(res, 'Only proposed admin can call this instruction') - }) - - it('cannot set proposed admin if cooldown hasnt passed', async () => { - const ix = await proposedAdminSdk.setProposedAdminIx() - const res = await makeTxSignAndSend(proposedAdminProvider, ix) - - expectTransactionError( - res, - "Can't set proposed admin - either no next admin is proposed or cooldown period is not over yet" - ) - }) - - it('should set proposed admin successfully after cooldown', async () => { - warpSeconds(deployerProvider, COOLDOWN_PERIOD_PLUS_ONE) - - const ix = await proposedAdminSdk.setProposedAdminIx() - await makeTxSignAndSend(proposedAdminProvider, ix) - - const updatedSettings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) - expect(updatedSettings.admin.toString()).to.be.eq(proposedAdmin.publicKey.toString()) - expect(updatedSettings.proposedAdmin).to.be.null - expect(updatedSettings.proposedAdminNextChangeTimestamp.toString()).to.be.eq('18446744073709551615') // u64::MAX - }) - - it('should reset admin to original one for next tests', async () => { - const ix = await proposedAdminSdk.proposeAdminIx(admin.publicKey) - await makeTxSignAndSend(proposedAdminProvider, ix) - - warpSeconds(deployerProvider, COOLDOWN_PERIOD_PLUS_ONE) - - const ix2 = await adminSdk.setProposedAdminIx() - await makeTxSignAndSend(adminProvider, ix2) - - const updatedSettings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) - expect(updatedSettings.admin.toString()).to.be.eq(admin.publicKey.toString()) - expect(updatedSettings.proposedAdmin).to.be.null - expect(updatedSettings.proposedAdminNextChangeTimestamp.toString()).to.be.eq('18446744073709551615') // u64::MAX - }) - - it('should propose same admin as current admin', async () => { const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - const currentAdmin = settings.admin - - const ix = await adminSdk.proposeAdminIx(currentAdmin) - await makeTxSignAndSend(adminProvider, ix) - - const updatedSettings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - expect(updatedSettings.proposedAdmin?.toString()).to.be.eq(currentAdmin.toString()) + expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) - warpSeconds(deployerProvider, COOLDOWN_PERIOD_PLUS_ONE) - - await makeTxSignAndSend(adminProvider, await adminSdk.setProposedAdminIx()) - const updatedSettings2 = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - expect(updatedSettings2.admin.toString()).to.be.eq(admin.publicKey.toString()) - expect(updatedSettings2.proposedAdmin).to.be.null - }) - - it('should calculate proposed_admin_next_change_timestamp correctly', async () => { - const settingsBefore = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - const cooldown = settingsBefore.proposedAdminCooldown.toNumber() - - const clockBefore = deployerProvider.client.getClock() - const nowBefore = Number(clockBefore.unixTimestamp) - - const ix = await adminSdk.proposeAdminIx(proposedAdmin.publicKey) - await makeTxSignAndSend(adminProvider, ix) - - const settingsAfter = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - const expectedTimestamp = nowBefore + cooldown - expect(settingsAfter.proposedAdminNextChangeTimestamp.toNumber()).to.be.eq(expectedTimestamp) + // Reset admin to original for subsequent tests + const resetIx = await otherAdminSdk.setAdmin(admin.publicKey) + await makeTxSignAndSend(otherAdminProvider, resetIx) }) }) @@ -312,32 +200,24 @@ describe('Whitelist Program', () => { expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) }) - it('should warp some seconds and change admin for next tests', async () => { - const diff = COOLDOWN_PERIOD_PLUS_ONE - - const then = Number(client.getClock().unixTimestamp) - const ix = await adminSdk.proposeAdminIx(proposedAdmin.publicKey) + it('should change admin for next tests', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) await makeTxSignAndSend(adminProvider, ix) - warpSeconds(deployerProvider, diff) - - const now = Number(client.getClock().unixTimestamp) - expect(now - then).to.be.eq(diff) - - const ix2 = await proposedAdminSdk.setProposedAdminIx() - await makeTxSignAndSend(proposedAdminProvider, ix2) + const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (whitelist to blacklist transition) (validator)', async () => { - const ix = await proposedAdminSdk.setEntityWhitelistStatusIx( + const ix = await otherAdminSdk.setEntityWhitelistStatusIx( EntityType.Validator, validator, WhitelistStatus.Blacklisted ) - await makeTxSignAndSend(proposedAdminProvider, ix) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - proposedAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) ) const now = Number(client.getClock().unixTimestamp) @@ -345,15 +225,15 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (whitelist to blacklist transition) (axia)', async () => { - const ix = await proposedAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Blacklisted) - await makeTxSignAndSend(proposedAdminProvider, ix) + const ix = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Blacklisted) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - proposedAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) ) const now = Number(client.getClock().unixTimestamp) @@ -361,19 +241,19 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (whitelist to blacklist transition) (solver)', async () => { - const ix = await proposedAdminSdk.setEntityWhitelistStatusIx( + const ix = await otherAdminSdk.setEntityWhitelistStatusIx( EntityType.Solver, solver, WhitelistStatus.Blacklisted ) - await makeTxSignAndSend(proposedAdminProvider, ix) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - proposedAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) ) const now = Number(client.getClock().unixTimestamp) @@ -381,19 +261,19 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (blacklist to whitelist transition) (validator)', async () => { - const ix = await proposedAdminSdk.setEntityWhitelistStatusIx( + const ix = await otherAdminSdk.setEntityWhitelistStatusIx( EntityType.Validator, validator, WhitelistStatus.Whitelisted ) - await makeTxSignAndSend(proposedAdminProvider, ix) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - proposedAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) ) const now = Number(client.getClock().unixTimestamp) @@ -401,15 +281,15 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (blacklist to whitelist transition) (axia)', async () => { - const ix = await proposedAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Whitelisted) - await makeTxSignAndSend(proposedAdminProvider, ix) + const ix = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - proposedAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) ) const now = Number(client.getClock().unixTimestamp) @@ -417,19 +297,19 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (blacklist to whitelist transition) (solver)', async () => { - const ix = await proposedAdminSdk.setEntityWhitelistStatusIx( + const ix = await otherAdminSdk.setEntityWhitelistStatusIx( EntityType.Solver, solver, WhitelistStatus.Whitelisted ) - await makeTxSignAndSend(proposedAdminProvider, ix) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - proposedAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) ) const now = Number(client.getClock().unixTimestamp) @@ -437,79 +317,64 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(proposedAdmin.publicKey.toString()) - }) - - it('should reset admin to original one for next tests', async () => { - const ix = await proposedAdminSdk.proposeAdminIx(admin.publicKey) - await makeTxSignAndSend(proposedAdminProvider, ix) - - warpSeconds(deployerProvider, COOLDOWN_PERIOD_PLUS_ONE) - - const ix2 = await adminSdk.setProposedAdminIx() - await makeTxSignAndSend(adminProvider, ix2) - - const updatedSettings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) - expect(updatedSettings.admin.toString()).to.be.eq(admin.publicKey.toString()) - expect(updatedSettings.proposedAdmin).to.be.null - expect(updatedSettings.proposedAdminNextChangeTimestamp.toString()).to.be.eq('18446744073709551615') // u64::MAX + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should whitelist another validator', async () => { - const ix = await adminSdk.setEntityWhitelistStatusIx( + const ix = await otherAdminSdk.setEntityWhitelistStatusIx( EntityType.Validator, validator2, WhitelistStatus.Whitelisted ) - await makeTxSignAndSend(adminProvider, ix) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator2) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator2) ) expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) - expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should whitelist another axia', async () => { - const ix = await adminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia2, WhitelistStatus.Whitelisted) - await makeTxSignAndSend(adminProvider, ix) + const ix = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia2, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia2) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia2) ) expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) - expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should whitelist another solver', async () => { - const ix = await adminSdk.setEntityWhitelistStatusIx(EntityType.Solver, solver2, WhitelistStatus.Whitelisted) - await makeTxSignAndSend(adminProvider, ix) + const ix = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Solver, solver2, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver2) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver2) ) expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) - expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) + expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should create separate accounts for same pubkey with different entity types', async () => { - const ix1 = await adminSdk.setEntityWhitelistStatusIx(EntityType.Validator, axia, WhitelistStatus.Whitelisted) - await makeTxSignAndSend(adminProvider, ix1) + const ix1 = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Validator, axia, WhitelistStatus.Whitelisted) + await makeTxSignAndSend(otherAdminProvider, ix1) const validatorRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) ) const axiaRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) ) expect(validatorRegistry.entityType).to.deep.include({ validator: {} }) @@ -517,8 +382,8 @@ describe('Whitelist Program', () => { expect(axiaRegistry.entityType).to.deep.include({ axia: {} }) expect(axiaRegistry.status).to.deep.include({ whitelisted: {} }) - const validatorPda = adminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) - const axiaPda = adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + const validatorPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) + const axiaPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) expect(validatorPda.toString()).to.not.eq(axiaPda.toString()) }) }) From a8b019b280b5c61d60bcd1c189a57b1da446de89 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 12:50:24 -0300 Subject: [PATCH 04/12] Code review: Remove updated_by and last_update from EntityRegistry --- .../set_entity_whitelist_status.rs | 6 +---- .../whitelist/src/state/entity_registry.rs | 2 -- packages/svm/tests/whitelist.test.ts | 24 ------------------- 3 files changed, 1 insertion(+), 31 deletions(-) diff --git a/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs b/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs index 8d2b234..63a8802 100644 --- a/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs +++ b/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs @@ -40,21 +40,18 @@ pub fn set_entity_whitelist_status( let now = Clock::get()?.unix_timestamp as u64; let entity_registry = &mut ctx.accounts.entity_registry; - if entity_registry.last_update == 0 { + if entity_registry.bump == 0 { entity_registry.entity_type = entity_type; entity_registry.entity_pubkey = entity_pubkey; entity_registry.bump = ctx.bumps.entity_registry; } entity_registry.status = status; - entity_registry.last_update = now; - entity_registry.updated_by = ctx.accounts.admin.key(); emit!(SetEntityWhitelistStatusEvent { entity_type, entity_pubkey, status, timestamp: now, - updated_by: entity_registry.updated_by, }); Ok(()) @@ -66,5 +63,4 @@ pub struct SetEntityWhitelistStatusEvent { pub entity_pubkey: Pubkey, pub status: WhitelistStatus, pub timestamp: u64, - pub updated_by: Pubkey, } diff --git a/packages/svm/programs/whitelist/src/state/entity_registry.rs b/packages/svm/programs/whitelist/src/state/entity_registry.rs index a404175..8cde650 100644 --- a/packages/svm/programs/whitelist/src/state/entity_registry.rs +++ b/packages/svm/programs/whitelist/src/state/entity_registry.rs @@ -8,7 +8,5 @@ pub struct EntityRegistry { pub entity_type: EntityType, pub entity_pubkey: Pubkey, pub status: WhitelistStatus, - pub last_update: u64, - pub updated_by: Pubkey, pub bump: u8, } diff --git a/packages/svm/tests/whitelist.test.ts b/packages/svm/tests/whitelist.test.ts index 774166c..d79e8b3 100644 --- a/packages/svm/tests/whitelist.test.ts +++ b/packages/svm/tests/whitelist.test.ts @@ -164,8 +164,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) }) it('should set whitelist status successfully (axia)', async () => { @@ -180,8 +178,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) }) it('should set whitelist status successfully (solver)', async () => { @@ -196,8 +192,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(admin.publicKey.toString()) }) it('should change admin for next tests', async () => { @@ -224,8 +218,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (whitelist to blacklist transition) (axia)', async () => { @@ -240,8 +232,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (whitelist to blacklist transition) (solver)', async () => { @@ -260,8 +250,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (blacklist to whitelist transition) (validator)', async () => { @@ -280,8 +268,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (blacklist to whitelist transition) (axia)', async () => { @@ -296,8 +282,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should update status correctly (blacklist to whitelist transition) (solver)', async () => { @@ -316,8 +300,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.eq(now) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should whitelist another validator', async () => { @@ -334,8 +316,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should whitelist another axia', async () => { @@ -348,8 +328,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should whitelist another solver', async () => { @@ -362,8 +340,6 @@ describe('Whitelist Program', () => { expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) - expect(entityRegistry.lastUpdate.toNumber()).to.be.greaterThan(0) - expect(entityRegistry.updatedBy.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) it('should create separate accounts for same pubkey with different entity types', async () => { From eff0a238195a970cf9a0a5812841c9d3f60ebc5e Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 13:11:59 -0300 Subject: [PATCH 05/12] Code review: rename Whitelist to Controller/Allowlist --- packages/svm/Anchor.toml | 2 +- packages/svm/Cargo.lock | 14 +- .../idls/{whitelist.json => controller.json} | 18 +-- .../{whitelist => controller}/Cargo.toml | 4 +- .../src/constants.rs | 0 .../{whitelist => controller}/src/errors.rs | 2 +- .../src/instructions/initialize.rs | 13 +- .../src/instructions/mod.rs | 4 +- .../src/instructions/set_admin.rs | 4 +- .../set_entity_allowlist_status.rs} | 20 +-- .../{whitelist => controller}/src/lib.rs | 20 +-- .../src/state/entity_registry.rs | 4 +- .../src/state/global_settings.rs | 0 .../src/state/mod.rs | 0 .../src/types/allowlist_status.rs} | 8 +- .../src/types/entity_type.rs | 0 .../svm/programs/controller/src/types/mod.rs | 5 + .../svm/programs/whitelist/src/types/mod.rs | 5 - .../Whitelist.ts => controller/Controller.ts} | 34 ++--- .../{whitelist.test.ts => controller.test.ts} | 141 ++++++++---------- 20 files changed, 132 insertions(+), 166 deletions(-) rename packages/svm/idls/{whitelist.json => controller.json} (96%) rename packages/svm/programs/{whitelist => controller}/Cargo.toml (92%) rename packages/svm/programs/{whitelist => controller}/src/constants.rs (100%) rename packages/svm/programs/{whitelist => controller}/src/errors.rs (89%) rename packages/svm/programs/{whitelist => controller}/src/instructions/initialize.rs (76%) rename packages/svm/programs/{whitelist => controller}/src/instructions/mod.rs (52%) rename packages/svm/programs/{whitelist => controller}/src/instructions/set_admin.rs (81%) rename packages/svm/programs/{whitelist/src/instructions/set_entity_whitelist_status.rs => controller/src/instructions/set_entity_allowlist_status.rs} (77%) rename packages/svm/programs/{whitelist => controller}/src/lib.rs (53%) rename packages/svm/programs/{whitelist => controller}/src/state/entity_registry.rs (68%) rename packages/svm/programs/{whitelist => controller}/src/state/global_settings.rs (100%) rename packages/svm/programs/{whitelist => controller}/src/state/mod.rs (100%) rename packages/svm/programs/{whitelist/src/types/whitelist_status.rs => controller/src/types/allowlist_status.rs} (54%) rename packages/svm/programs/{whitelist => controller}/src/types/entity_type.rs (100%) create mode 100644 packages/svm/programs/controller/src/types/mod.rs delete mode 100644 packages/svm/programs/whitelist/src/types/mod.rs rename packages/svm/sdks/{whitelist/Whitelist.ts => controller/Controller.ts} (74%) rename packages/svm/tests/{whitelist.test.ts => controller.test.ts} (69%) diff --git a/packages/svm/Anchor.toml b/packages/svm/Anchor.toml index 8d4d942..e1ff45d 100644 --- a/packages/svm/Anchor.toml +++ b/packages/svm/Anchor.toml @@ -7,7 +7,7 @@ skip-lint = false [programs.localnet] settler = "HbNt35Ng8aM4NUy39evpCQqXEC4Nmaq16ewY8dyNF6NF" -whitelist = "7PwVkjnnapxytWFW69WFDLhfVZZgKhBE9m3zwcDZmncr" +controller = "7PwVkjnnapxytWFW69WFDLhfVZZgKhBE9m3zwcDZmncr" [registry] url = "https://api.apr.dev" diff --git a/packages/svm/Cargo.lock b/packages/svm/Cargo.lock index 18046f0..3c06e6d 100644 --- a/packages/svm/Cargo.lock +++ b/packages/svm/Cargo.lock @@ -404,6 +404,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "controller" +version = "0.1.0" +dependencies = [ + "anchor-lang", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1529,13 +1536,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "whitelist" -version = "0.1.0" -dependencies = [ - "anchor-lang", -] - [[package]] name = "windows-link" version = "0.2.1" diff --git a/packages/svm/idls/whitelist.json b/packages/svm/idls/controller.json similarity index 96% rename from packages/svm/idls/whitelist.json rename to packages/svm/idls/controller.json index 8bfadb2..71f8265 100644 --- a/packages/svm/idls/whitelist.json +++ b/packages/svm/idls/controller.json @@ -1,7 +1,7 @@ { "address": "7PwVkjnnapxytWFW69WFDLhfVZZgKhBE9m3zwcDZmncr", "metadata": { - "name": "whitelist", + "name": "controller", "version": "0.1.0", "spec": "0.1.0", "description": "Created with Anchor" @@ -127,7 +127,7 @@ ] }, { - "name": "set_entity_whitelist_status", + "name": "set_entity_allowlist_status", "discriminator": [ 100, 20, @@ -200,7 +200,7 @@ "name": "status", "type": { "defined": { - "name": "WhitelistStatus" + "name": "AllowlistStatus" } } } @@ -286,7 +286,7 @@ ], "events": [ { - "name": "SetEntityWhitelistStatusEvent", + "name": "SetEntityAllowlistStatusEvent", "discriminator": [ 137, 194, @@ -376,7 +376,7 @@ "name": "status", "type": { "defined": { - "name": "WhitelistStatus" + "name": "AllowlistStatus" } } }, @@ -446,7 +446,7 @@ } }, { - "name": "SetEntityWhitelistStatusEvent", + "name": "SetEntityAllowlistStatusEvent", "type": { "kind": "struct", "fields": [ @@ -466,7 +466,7 @@ "name": "status", "type": { "defined": { - "name": "WhitelistStatus" + "name": "AllowlistStatus" } } }, @@ -498,7 +498,7 @@ } }, { - "name": "WhitelistStatus", + "name": "AllowlistStatus", "repr": { "kind": "rust" }, @@ -506,7 +506,7 @@ "kind": "enum", "variants": [ { - "name": "Whitelisted" + "name": "Allowlisted" }, { "name": "Blacklisted" diff --git a/packages/svm/programs/whitelist/Cargo.toml b/packages/svm/programs/controller/Cargo.toml similarity index 92% rename from packages/svm/programs/whitelist/Cargo.toml rename to packages/svm/programs/controller/Cargo.toml index 199f4d5..5826092 100644 --- a/packages/svm/programs/whitelist/Cargo.toml +++ b/packages/svm/programs/controller/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "whitelist" +name = "controller" version = "0.1.0" description = "Created with Anchor" edition = "2021" [lib] crate-type = ["cdylib", "lib"] -name = "whitelist" +name = "controller" [features] default = [] diff --git a/packages/svm/programs/whitelist/src/constants.rs b/packages/svm/programs/controller/src/constants.rs similarity index 100% rename from packages/svm/programs/whitelist/src/constants.rs rename to packages/svm/programs/controller/src/constants.rs diff --git a/packages/svm/programs/whitelist/src/errors.rs b/packages/svm/programs/controller/src/errors.rs similarity index 89% rename from packages/svm/programs/whitelist/src/errors.rs rename to packages/svm/programs/controller/src/errors.rs index c3e07b9..3c8221f 100644 --- a/packages/svm/programs/whitelist/src/errors.rs +++ b/packages/svm/programs/controller/src/errors.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; #[error_code] -pub enum WhitelistError { +pub enum ControllerError { #[msg("Only deployer can call this instruction")] OnlyDeployer, diff --git a/packages/svm/programs/whitelist/src/instructions/initialize.rs b/packages/svm/programs/controller/src/instructions/initialize.rs similarity index 76% rename from packages/svm/programs/whitelist/src/instructions/initialize.rs rename to packages/svm/programs/controller/src/instructions/initialize.rs index 3233998..094cd0a 100644 --- a/packages/svm/programs/whitelist/src/instructions/initialize.rs +++ b/packages/svm/programs/controller/src/instructions/initialize.rs @@ -1,11 +1,7 @@ use anchor_lang::prelude::*; use std::str::FromStr; -use crate::{ - constants::DEPLOYER_KEY, - errors::WhitelistError, - state::GlobalSettings, -}; +use crate::{constants::DEPLOYER_KEY, errors::ControllerError, state::GlobalSettings}; #[derive(Accounts)] pub struct Initialize<'info> { @@ -24,14 +20,11 @@ pub struct Initialize<'info> { pub system_program: Program<'info, System>, } -pub fn initialize( - ctx: Context, - admin: Pubkey, -) -> Result<()> { +pub fn initialize(ctx: Context, admin: Pubkey) -> Result<()> { require_keys_eq!( ctx.accounts.deployer.key(), Pubkey::from_str(DEPLOYER_KEY).unwrap(), - WhitelistError::OnlyDeployer, + ControllerError::OnlyDeployer, ); let global_settings = &mut ctx.accounts.global_settings; diff --git a/packages/svm/programs/whitelist/src/instructions/mod.rs b/packages/svm/programs/controller/src/instructions/mod.rs similarity index 52% rename from packages/svm/programs/whitelist/src/instructions/mod.rs rename to packages/svm/programs/controller/src/instructions/mod.rs index 2d033f0..1afdfd5 100644 --- a/packages/svm/programs/whitelist/src/instructions/mod.rs +++ b/packages/svm/programs/controller/src/instructions/mod.rs @@ -1,7 +1,7 @@ pub mod initialize; pub mod set_admin; -pub mod set_entity_whitelist_status; +pub mod set_entity_allowlist_status; pub use initialize::*; pub use set_admin::*; -pub use set_entity_whitelist_status::*; +pub use set_entity_allowlist_status::*; diff --git a/packages/svm/programs/whitelist/src/instructions/set_admin.rs b/packages/svm/programs/controller/src/instructions/set_admin.rs similarity index 81% rename from packages/svm/programs/whitelist/src/instructions/set_admin.rs rename to packages/svm/programs/controller/src/instructions/set_admin.rs index 71d8b41..ef96220 100644 --- a/packages/svm/programs/whitelist/src/instructions/set_admin.rs +++ b/packages/svm/programs/controller/src/instructions/set_admin.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use crate::{errors::WhitelistError, state::GlobalSettings}; +use crate::{errors::ControllerError, state::GlobalSettings}; #[derive(Accounts)] pub struct SetAdmin<'info> { @@ -11,7 +11,7 @@ pub struct SetAdmin<'info> { mut, seeds = [b"global-settings"], bump = global_settings.bump, - has_one = admin @ WhitelistError::OnlyAdmin + has_one = admin @ ControllerError::OnlyAdmin )] pub global_settings: Box>, } diff --git a/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs b/packages/svm/programs/controller/src/instructions/set_entity_allowlist_status.rs similarity index 77% rename from packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs rename to packages/svm/programs/controller/src/instructions/set_entity_allowlist_status.rs index 63a8802..3cccb4e 100644 --- a/packages/svm/programs/whitelist/src/instructions/set_entity_whitelist_status.rs +++ b/packages/svm/programs/controller/src/instructions/set_entity_allowlist_status.rs @@ -1,14 +1,14 @@ use anchor_lang::prelude::*; use crate::{ - errors::WhitelistError, + errors::ControllerError, state::{EntityRegistry, GlobalSettings}, - types::{EntityType, WhitelistStatus}, + types::{AllowlistStatus, EntityType}, }; #[derive(Accounts)] #[instruction(entity_type: EntityType, entity_pubkey: Pubkey)] -pub struct SetEntityWhitelistStatus<'info> { +pub struct SetEntityAllowlistStatus<'info> { #[account(mut)] pub admin: Signer<'info>, @@ -24,18 +24,18 @@ pub struct SetEntityWhitelistStatus<'info> { #[account( seeds = [b"global-settings"], bump = global_settings.bump, - has_one = admin @ WhitelistError::OnlyAdmin + has_one = admin @ ControllerError::OnlyAdmin )] pub global_settings: Box>, pub system_program: Program<'info, System>, } -pub fn set_entity_whitelist_status( - ctx: Context, +pub fn set_entity_allowlist_status( + ctx: Context, entity_type: EntityType, entity_pubkey: Pubkey, - status: WhitelistStatus, + status: AllowlistStatus, ) -> Result<()> { let now = Clock::get()?.unix_timestamp as u64; let entity_registry = &mut ctx.accounts.entity_registry; @@ -47,7 +47,7 @@ pub fn set_entity_whitelist_status( } entity_registry.status = status; - emit!(SetEntityWhitelistStatusEvent { + emit!(SetEntityAllowlistStatusEvent { entity_type, entity_pubkey, status, @@ -58,9 +58,9 @@ pub fn set_entity_whitelist_status( } #[event] -pub struct SetEntityWhitelistStatusEvent { +pub struct SetEntityAllowlistStatusEvent { pub entity_type: EntityType, pub entity_pubkey: Pubkey, - pub status: WhitelistStatus, + pub status: AllowlistStatus, pub timestamp: u64, } diff --git a/packages/svm/programs/whitelist/src/lib.rs b/packages/svm/programs/controller/src/lib.rs similarity index 53% rename from packages/svm/programs/whitelist/src/lib.rs rename to packages/svm/programs/controller/src/lib.rs index a92b511..38dc67e 100644 --- a/packages/svm/programs/whitelist/src/lib.rs +++ b/packages/svm/programs/controller/src/lib.rs @@ -11,29 +11,23 @@ pub mod types; use crate::{instructions::*, types::*}; #[program] -pub mod whitelist { +pub mod controller { use super::*; - pub fn initialize( - ctx: Context, - admin: Pubkey, - ) -> Result<()> { + pub fn initialize(ctx: Context, admin: Pubkey) -> Result<()> { instructions::initialize(ctx, admin) } - pub fn set_admin( - ctx: Context, - new_admin: Pubkey, - ) -> Result<()> { + pub fn set_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { instructions::set_admin(ctx, new_admin) } - pub fn set_entity_whitelist_status( - ctx: Context, + pub fn set_entity_allowlist_status( + ctx: Context, entity_type: EntityType, entity_pubkey: Pubkey, - status: WhitelistStatus, + status: AllowlistStatus, ) -> Result<()> { - instructions::set_entity_whitelist_status(ctx, entity_type, entity_pubkey, status) + instructions::set_entity_allowlist_status(ctx, entity_type, entity_pubkey, status) } } diff --git a/packages/svm/programs/whitelist/src/state/entity_registry.rs b/packages/svm/programs/controller/src/state/entity_registry.rs similarity index 68% rename from packages/svm/programs/whitelist/src/state/entity_registry.rs rename to packages/svm/programs/controller/src/state/entity_registry.rs index 8cde650..95c57de 100644 --- a/packages/svm/programs/whitelist/src/state/entity_registry.rs +++ b/packages/svm/programs/controller/src/state/entity_registry.rs @@ -1,12 +1,12 @@ use anchor_lang::prelude::*; -use crate::types::{EntityType, WhitelistStatus}; +use crate::types::{AllowlistStatus, EntityType}; #[account] #[derive(InitSpace)] pub struct EntityRegistry { pub entity_type: EntityType, pub entity_pubkey: Pubkey, - pub status: WhitelistStatus, + pub status: AllowlistStatus, pub bump: u8, } diff --git a/packages/svm/programs/whitelist/src/state/global_settings.rs b/packages/svm/programs/controller/src/state/global_settings.rs similarity index 100% rename from packages/svm/programs/whitelist/src/state/global_settings.rs rename to packages/svm/programs/controller/src/state/global_settings.rs diff --git a/packages/svm/programs/whitelist/src/state/mod.rs b/packages/svm/programs/controller/src/state/mod.rs similarity index 100% rename from packages/svm/programs/whitelist/src/state/mod.rs rename to packages/svm/programs/controller/src/state/mod.rs diff --git a/packages/svm/programs/whitelist/src/types/whitelist_status.rs b/packages/svm/programs/controller/src/types/allowlist_status.rs similarity index 54% rename from packages/svm/programs/whitelist/src/types/whitelist_status.rs rename to packages/svm/programs/controller/src/types/allowlist_status.rs index a4e916b..4555d94 100644 --- a/packages/svm/programs/whitelist/src/types/whitelist_status.rs +++ b/packages/svm/programs/controller/src/types/allowlist_status.rs @@ -2,11 +2,11 @@ use anchor_lang::prelude::*; #[repr(u8)] #[derive(Copy, Clone, AnchorSerialize, AnchorDeserialize)] -pub enum WhitelistStatus { - Whitelisted = 1, - Blacklisted = 2, +pub enum AllowlistStatus { + Allowed = 1, + Disallowed = 2, } -impl anchor_lang::Space for WhitelistStatus { +impl anchor_lang::Space for AllowlistStatus { const INIT_SPACE: usize = 1; } diff --git a/packages/svm/programs/whitelist/src/types/entity_type.rs b/packages/svm/programs/controller/src/types/entity_type.rs similarity index 100% rename from packages/svm/programs/whitelist/src/types/entity_type.rs rename to packages/svm/programs/controller/src/types/entity_type.rs diff --git a/packages/svm/programs/controller/src/types/mod.rs b/packages/svm/programs/controller/src/types/mod.rs new file mode 100644 index 0000000..7554519 --- /dev/null +++ b/packages/svm/programs/controller/src/types/mod.rs @@ -0,0 +1,5 @@ +pub mod allowlist_status; +pub mod entity_type; + +pub use allowlist_status::*; +pub use entity_type::*; diff --git a/packages/svm/programs/whitelist/src/types/mod.rs b/packages/svm/programs/whitelist/src/types/mod.rs deleted file mode 100644 index 7e7c4e8..0000000 --- a/packages/svm/programs/whitelist/src/types/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod entity_type; -pub mod whitelist_status; - -pub use entity_type::*; -pub use whitelist_status::*; diff --git a/packages/svm/sdks/whitelist/Whitelist.ts b/packages/svm/sdks/controller/Controller.ts similarity index 74% rename from packages/svm/sdks/whitelist/Whitelist.ts rename to packages/svm/sdks/controller/Controller.ts index 6293a3d..d5a3ded 100644 --- a/packages/svm/sdks/whitelist/Whitelist.ts +++ b/packages/svm/sdks/controller/Controller.ts @@ -1,7 +1,7 @@ import { IdlTypes, Program, Provider, web3 } from '@coral-xyz/anchor' -import * as WhitelistIDL from '../../target/idl/whitelist.json' -import { Whitelist } from '../../target/types/whitelist' +import * as ControllerIDL from '../../target/idl/controller.json' +import { Controller } from '../../target/types/controller' export enum EntityType { // eslint-disable-next-line no-unused-vars @@ -12,18 +12,18 @@ export enum EntityType { Solver = 3, } -export enum WhitelistStatus { +export enum AllowlistStatus { // eslint-disable-next-line no-unused-vars - Whitelisted = 1, + Allowed = 1, // eslint-disable-next-line no-unused-vars - Blacklisted = 2, + Disallowed = 2, } -export default class WhitelistSDK { - protected program: Program +export default class ControllerSDK { + protected program: Program constructor(provider: Provider) { - this.program = new Program(WhitelistIDL, provider) + this.program = new Program(ControllerIDL, provider) } async initializeIx(admin: web3.PublicKey): Promise { @@ -50,18 +50,18 @@ export default class WhitelistSDK { return ix } - async setEntityWhitelistStatusIx( + async setEntityAllowlistStatusIx( entityType: EntityType, entityPubkey: web3.PublicKey, - status: WhitelistStatus + status: AllowlistStatus ): Promise { const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) const globalSettings = this.getGlobalSettingsPubkey() const ix = await this.program.methods - .setEntityWhitelistStatus( + .setEntityAllowlistStatus( this.entityTypeToAnchorEnum(entityType), entityPubkey, - this.whitelistStatusToAnchorEnum(status) + this.allowlistStatusToAnchorEnum(status) ) .accountsPartial({ admin: this.getSignerKey(), @@ -88,7 +88,7 @@ export default class WhitelistSDK { )[0] } - entityTypeToAnchorEnum(entityType: EntityType): IdlTypes['entityType'] { + entityTypeToAnchorEnum(entityType: EntityType): IdlTypes['entityType'] { if (entityType === EntityType.Validator) return { validator: {} } if (entityType === EntityType.Axia) return { axia: {} } if (entityType === EntityType.Solver) return { solver: {} } @@ -96,10 +96,10 @@ export default class WhitelistSDK { throw new Error(`Unsupported entity type ${entityType}`) } - whitelistStatusToAnchorEnum(status: WhitelistStatus): IdlTypes['whitelistStatus'] { - if (status === WhitelistStatus.Whitelisted) return { whitelisted: {} } - if (status === WhitelistStatus.Blacklisted) return { blacklisted: {} } + allowlistStatusToAnchorEnum(status: AllowlistStatus): IdlTypes['allowlistStatus'] { + if (status === AllowlistStatus.Allowed) return { allowed: {} } + if (status === AllowlistStatus.Disallowed) return { disallowed: {} } - throw new Error(`Unsupported whitelist status ${status}`) + throw new Error(`Unsupported allowlist status ${status}`) } } diff --git a/packages/svm/tests/whitelist.test.ts b/packages/svm/tests/controller.test.ts similarity index 69% rename from packages/svm/tests/whitelist.test.ts rename to packages/svm/tests/controller.test.ts index d79e8b3..860bc72 100644 --- a/packages/svm/tests/whitelist.test.ts +++ b/packages/svm/tests/controller.test.ts @@ -9,13 +9,13 @@ import { LiteSVM } from 'litesvm' import os from 'os' import path from 'path' -import WhitelistSDK, { EntityType, WhitelistStatus } from '../sdks/whitelist/Whitelist' -import * as WhitelistIDL from '../target/idl/whitelist.json' -import { Whitelist } from '../target/types/whitelist' +import ControllerSDK, { AllowlistStatus, EntityType } from '../sdks/controller/Controller' +import * as ControllerIDL from '../target/idl/controller.json' +import { Controller } from '../target/types/controller' import { expectTransactionError } from './helpers/settler-helpers' import { makeTxSignAndSend, warpSeconds } from './utils' -describe('Whitelist Program', () => { +describe('Controller Program', () => { let client: LiteSVM let deployer: web3.Keypair @@ -28,12 +28,12 @@ describe('Whitelist Program', () => { let otherAdminProvider: LiteSVMProvider let maliciousProvider: LiteSVMProvider - let program: Program + let program: Program - let deployerSdk: WhitelistSDK - let adminSdk: WhitelistSDK - let otherAdminSdk: WhitelistSDK - let maliciousSdk: WhitelistSDK + let deployerSdk: ControllerSDK + let adminSdk: ControllerSDK + let otherAdminSdk: ControllerSDK + let maliciousSdk: ControllerSDK before(async () => { deployer = web3.Keypair.fromSecretKey( @@ -50,12 +50,12 @@ describe('Whitelist Program', () => { otherAdminProvider = new LiteSVMProvider(client, new Wallet(otherAdmin)) maliciousProvider = new LiteSVMProvider(client, new Wallet(malicious)) - program = new Program(WhitelistIDL as any, deployerProvider) + program = new Program(ControllerIDL as any, deployerProvider) - deployerSdk = new WhitelistSDK(deployerProvider) - adminSdk = new WhitelistSDK(adminProvider) - otherAdminSdk = new WhitelistSDK(otherAdminProvider) - maliciousSdk = new WhitelistSDK(maliciousProvider) + deployerSdk = new ControllerSDK(deployerProvider) + adminSdk = new ControllerSDK(adminProvider) + otherAdminSdk = new ControllerSDK(otherAdminProvider) + maliciousSdk = new ControllerSDK(maliciousProvider) deployerProvider.client.airdrop(deployer.publicKey, BigInt(100_000_000_000)) deployerProvider.client.airdrop(admin.publicKey, BigInt(100_000_000_000)) @@ -70,7 +70,7 @@ describe('Whitelist Program', () => { client.expireBlockhash() }) - describe('Whitelist', () => { + describe('Controller', () => { describe('initialize', () => { it('cannot initialize if not deployer', async () => { const newAdmin = web3.Keypair.generate().publicKey @@ -120,7 +120,7 @@ describe('Whitelist Program', () => { }) }) - describe('set_entity_whitelist_status', () => { + describe('set_entity_allowlist_status', () => { let validator: web3.PublicKey let axia: web3.PublicKey let solver: web3.PublicKey @@ -138,60 +138,53 @@ describe('Whitelist Program', () => { }) it('cannot set status if not admin', async () => { - const ix = await maliciousSdk.setEntityWhitelistStatusIx( + const ix = await maliciousSdk.setEntityAllowlistStatusIx( EntityType.Validator, validator, - WhitelistStatus.Whitelisted + AllowlistStatus.Allowed ) const res = await makeTxSignAndSend(maliciousProvider, ix) expectTransactionError(res, 'Only admin can call this instruction') }) - it('should set whitelist status successfully (validator)', async () => { - const ix = await adminSdk.setEntityWhitelistStatusIx( - EntityType.Validator, - validator, - WhitelistStatus.Whitelisted - ) + it('should set allowlist status successfully (validator)', async () => { + const ix = await adminSdk.setEntityAllowlistStatusIx(EntityType.Validator, validator, AllowlistStatus.Allowed) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should set whitelist status successfully (axia)', async () => { - const ix = await adminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Whitelisted) + it('should set allowlist status successfully (axia)', async () => { + const ix = await adminSdk.setEntityAllowlistStatusIx(EntityType.Axia, axia, AllowlistStatus.Allowed) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should set whitelist status successfully (solver)', async () => { - const ix = await adminSdk.setEntityWhitelistStatusIx(EntityType.Solver, solver, WhitelistStatus.Whitelisted) + it('should set allowlist status successfully (solver)', async () => { + const ix = await adminSdk.setEntityAllowlistStatusIx(EntityType.Solver, solver, AllowlistStatus.Allowed) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) it('should change admin for next tests', async () => { @@ -202,111 +195,97 @@ describe('Whitelist Program', () => { expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) - it('should update status correctly (whitelist to blacklist transition) (validator)', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx( + it('should update status correctly (allowlist to blacklist transition) (validator)', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx( EntityType.Validator, validator, - WhitelistStatus.Blacklisted + AllowlistStatus.Disallowed ) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) + expect(entityRegistry.status).to.deep.include({ disallowed: {} }) }) - it('should update status correctly (whitelist to blacklist transition) (axia)', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Blacklisted) + it('should update status correctly (allowlist to blacklist transition) (axia)', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Axia, axia, AllowlistStatus.Disallowed) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) + expect(entityRegistry.status).to.deep.include({ disallowed: {} }) }) - it('should update status correctly (whitelist to blacklist transition) (solver)', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx( - EntityType.Solver, - solver, - WhitelistStatus.Blacklisted - ) + it('should update status correctly (allowlist to blacklist transition) (solver)', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Solver, solver, AllowlistStatus.Disallowed) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - expect(entityRegistry.status).to.deep.include({ blacklisted: {} }) + expect(entityRegistry.status).to.deep.include({ disallowed: {} }) }) - it('should update status correctly (blacklist to whitelist transition) (validator)', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx( + it('should update status correctly (blacklist to allowlist transition) (validator)', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx( EntityType.Validator, validator, - WhitelistStatus.Whitelisted + AllowlistStatus.Allowed ) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should update status correctly (blacklist to whitelist transition) (axia)', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia, WhitelistStatus.Whitelisted) + it('should update status correctly (blacklist to allowlist transition) (axia)', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Axia, axia, AllowlistStatus.Allowed) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should update status correctly (blacklist to whitelist transition) (solver)', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx( - EntityType.Solver, - solver, - WhitelistStatus.Whitelisted - ) + it('should update status correctly (blacklist to allowlist transition) (solver)', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Solver, solver, AllowlistStatus.Allowed) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) ) - const now = Number(client.getClock().unixTimestamp) expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should whitelist another validator', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx( + it('should allowlist another validator', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx( EntityType.Validator, validator2, - WhitelistStatus.Whitelisted + AllowlistStatus.Allowed ) await makeTxSignAndSend(otherAdminProvider, ix) @@ -315,11 +294,11 @@ describe('Whitelist Program', () => { ) expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should whitelist another axia', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Axia, axia2, WhitelistStatus.Whitelisted) + it('should allowlist another axia', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Axia, axia2, AllowlistStatus.Allowed) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -327,11 +306,11 @@ describe('Whitelist Program', () => { ) expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should whitelist another solver', async () => { - const ix = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Solver, solver2, WhitelistStatus.Whitelisted) + it('should allowlist another solver', async () => { + const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Solver, solver2, AllowlistStatus.Allowed) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -339,11 +318,11 @@ describe('Whitelist Program', () => { ) expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) - expect(entityRegistry.status).to.deep.include({ whitelisted: {} }) + expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) it('should create separate accounts for same pubkey with different entity types', async () => { - const ix1 = await otherAdminSdk.setEntityWhitelistStatusIx(EntityType.Validator, axia, WhitelistStatus.Whitelisted) + const ix1 = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Validator, axia, AllowlistStatus.Allowed) await makeTxSignAndSend(otherAdminProvider, ix1) const validatorRegistry = await program.account.entityRegistry.fetch( @@ -354,9 +333,9 @@ describe('Whitelist Program', () => { ) expect(validatorRegistry.entityType).to.deep.include({ validator: {} }) - expect(validatorRegistry.status).to.deep.include({ whitelisted: {} }) + expect(validatorRegistry.status).to.deep.include({ allowed: {} }) expect(axiaRegistry.entityType).to.deep.include({ axia: {} }) - expect(axiaRegistry.status).to.deep.include({ whitelisted: {} }) + expect(axiaRegistry.status).to.deep.include({ allowed: {} }) const validatorPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) const axiaPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) From 90c6e3cc9a4165ee9ac2e86825c1f595faa6609b Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 15:15:05 -0300 Subject: [PATCH 06/12] Code review: rm EntityRegistry status and close when not in allowlist --- .../src/instructions/close_entity_registry.rs | 52 +++++++ ...st_status.rs => create_entity_registry.rs} | 34 +---- .../controller/src/instructions/mod.rs | 6 +- packages/svm/programs/controller/src/lib.rs | 15 +- .../controller/src/state/entity_registry.rs | 3 +- .../controller/src/types/allowlist_status.rs | 12 -- .../svm/programs/controller/src/types/mod.rs | 2 - packages/svm/sdks/controller/Controller.ts | 44 +++--- packages/svm/tests/controller.test.ts | 130 +++++++----------- 9 files changed, 152 insertions(+), 146 deletions(-) create mode 100644 packages/svm/programs/controller/src/instructions/close_entity_registry.rs rename packages/svm/programs/controller/src/instructions/{set_entity_allowlist_status.rs => create_entity_registry.rs} (54%) delete mode 100644 packages/svm/programs/controller/src/types/allowlist_status.rs diff --git a/packages/svm/programs/controller/src/instructions/close_entity_registry.rs b/packages/svm/programs/controller/src/instructions/close_entity_registry.rs new file mode 100644 index 0000000..91390bf --- /dev/null +++ b/packages/svm/programs/controller/src/instructions/close_entity_registry.rs @@ -0,0 +1,52 @@ +use anchor_lang::prelude::*; + +use crate::{ + errors::ControllerError, + state::{EntityRegistry, GlobalSettings}, + types::EntityType, +}; + +#[derive(Accounts)] +#[instruction(entity_type: EntityType, entity_pubkey: Pubkey)] +pub struct CloseEntityRegistry<'info> { + #[account(mut)] + pub admin: Signer<'info>, + + #[account( + mut, + seeds = [b"entity-registry".as_ref(), &[entity_type as u8], entity_pubkey.as_ref()], + bump = entity_registry.bump, + close = admin, + )] + pub entity_registry: Box>, + + #[account( + seeds = [b"global-settings"], + bump = global_settings.bump, + has_one = admin @ ControllerError::OnlyAdmin + )] + pub global_settings: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn close_entity_registry( + _ctx: Context, + entity_type: EntityType, + entity_pubkey: Pubkey, +) -> Result<()> { + emit!(CloseEntityRegistryEvent { + entity_type, + entity_pubkey, + timestamp: Clock::get()?.unix_timestamp as u64, + }); + + Ok(()) +} + +#[event] +pub struct CloseEntityRegistryEvent { + pub entity_type: EntityType, + pub entity_pubkey: Pubkey, + pub timestamp: u64, +} diff --git a/packages/svm/programs/controller/src/instructions/set_entity_allowlist_status.rs b/packages/svm/programs/controller/src/instructions/create_entity_registry.rs similarity index 54% rename from packages/svm/programs/controller/src/instructions/set_entity_allowlist_status.rs rename to packages/svm/programs/controller/src/instructions/create_entity_registry.rs index 3cccb4e..c58a739 100644 --- a/packages/svm/programs/controller/src/instructions/set_entity_allowlist_status.rs +++ b/packages/svm/programs/controller/src/instructions/create_entity_registry.rs @@ -3,12 +3,12 @@ use anchor_lang::prelude::*; use crate::{ errors::ControllerError, state::{EntityRegistry, GlobalSettings}, - types::{AllowlistStatus, EntityType}, + types::EntityType, }; #[derive(Accounts)] #[instruction(entity_type: EntityType, entity_pubkey: Pubkey)] -pub struct SetEntityAllowlistStatus<'info> { +pub struct CreateEntityRegistry<'info> { #[account(mut)] pub admin: Signer<'info>, @@ -31,36 +31,16 @@ pub struct SetEntityAllowlistStatus<'info> { pub system_program: Program<'info, System>, } -pub fn set_entity_allowlist_status( - ctx: Context, +pub fn create_entity_registry( + ctx: Context, entity_type: EntityType, entity_pubkey: Pubkey, - status: AllowlistStatus, ) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; let entity_registry = &mut ctx.accounts.entity_registry; - if entity_registry.bump == 0 { - entity_registry.entity_type = entity_type; - entity_registry.entity_pubkey = entity_pubkey; - entity_registry.bump = ctx.bumps.entity_registry; - } - entity_registry.status = status; - - emit!(SetEntityAllowlistStatusEvent { - entity_type, - entity_pubkey, - status, - timestamp: now, - }); + entity_registry.entity_type = entity_type; + entity_registry.entity_pubkey = entity_pubkey; + entity_registry.bump = ctx.bumps.entity_registry; Ok(()) } - -#[event] -pub struct SetEntityAllowlistStatusEvent { - pub entity_type: EntityType, - pub entity_pubkey: Pubkey, - pub status: AllowlistStatus, - pub timestamp: u64, -} diff --git a/packages/svm/programs/controller/src/instructions/mod.rs b/packages/svm/programs/controller/src/instructions/mod.rs index 1afdfd5..98788d1 100644 --- a/packages/svm/programs/controller/src/instructions/mod.rs +++ b/packages/svm/programs/controller/src/instructions/mod.rs @@ -1,7 +1,9 @@ +pub mod close_entity_registry; +pub mod create_entity_registry; pub mod initialize; pub mod set_admin; -pub mod set_entity_allowlist_status; +pub use close_entity_registry::*; +pub use create_entity_registry::*; pub use initialize::*; pub use set_admin::*; -pub use set_entity_allowlist_status::*; diff --git a/packages/svm/programs/controller/src/lib.rs b/packages/svm/programs/controller/src/lib.rs index 38dc67e..22a70d9 100644 --- a/packages/svm/programs/controller/src/lib.rs +++ b/packages/svm/programs/controller/src/lib.rs @@ -22,12 +22,19 @@ pub mod controller { instructions::set_admin(ctx, new_admin) } - pub fn set_entity_allowlist_status( - ctx: Context, + pub fn create_entity_registry( + ctx: Context, entity_type: EntityType, entity_pubkey: Pubkey, - status: AllowlistStatus, ) -> Result<()> { - instructions::set_entity_allowlist_status(ctx, entity_type, entity_pubkey, status) + instructions::create_entity_registry(ctx, entity_type, entity_pubkey) + } + + pub fn close_entity_registry( + ctx: Context, + entity_type: EntityType, + entity_pubkey: Pubkey, + ) -> Result<()> { + instructions::close_entity_registry(ctx, entity_type, entity_pubkey) } } diff --git a/packages/svm/programs/controller/src/state/entity_registry.rs b/packages/svm/programs/controller/src/state/entity_registry.rs index 95c57de..dbf917c 100644 --- a/packages/svm/programs/controller/src/state/entity_registry.rs +++ b/packages/svm/programs/controller/src/state/entity_registry.rs @@ -1,12 +1,11 @@ use anchor_lang::prelude::*; -use crate::types::{AllowlistStatus, EntityType}; +use crate::types::EntityType; #[account] #[derive(InitSpace)] pub struct EntityRegistry { pub entity_type: EntityType, pub entity_pubkey: Pubkey, - pub status: AllowlistStatus, pub bump: u8, } diff --git a/packages/svm/programs/controller/src/types/allowlist_status.rs b/packages/svm/programs/controller/src/types/allowlist_status.rs deleted file mode 100644 index 4555d94..0000000 --- a/packages/svm/programs/controller/src/types/allowlist_status.rs +++ /dev/null @@ -1,12 +0,0 @@ -use anchor_lang::prelude::*; - -#[repr(u8)] -#[derive(Copy, Clone, AnchorSerialize, AnchorDeserialize)] -pub enum AllowlistStatus { - Allowed = 1, - Disallowed = 2, -} - -impl anchor_lang::Space for AllowlistStatus { - const INIT_SPACE: usize = 1; -} diff --git a/packages/svm/programs/controller/src/types/mod.rs b/packages/svm/programs/controller/src/types/mod.rs index 7554519..5e95bd3 100644 --- a/packages/svm/programs/controller/src/types/mod.rs +++ b/packages/svm/programs/controller/src/types/mod.rs @@ -1,5 +1,3 @@ -pub mod allowlist_status; pub mod entity_type; -pub use allowlist_status::*; pub use entity_type::*; diff --git a/packages/svm/sdks/controller/Controller.ts b/packages/svm/sdks/controller/Controller.ts index d5a3ded..527aaa8 100644 --- a/packages/svm/sdks/controller/Controller.ts +++ b/packages/svm/sdks/controller/Controller.ts @@ -12,13 +12,6 @@ export enum EntityType { Solver = 3, } -export enum AllowlistStatus { - // eslint-disable-next-line no-unused-vars - Allowed = 1, - // eslint-disable-next-line no-unused-vars - Disallowed = 2, -} - export default class ControllerSDK { protected program: Program @@ -50,18 +43,36 @@ export default class ControllerSDK { return ix } - async setEntityAllowlistStatusIx( + async createEntityRegistryIx( entityType: EntityType, - entityPubkey: web3.PublicKey, - status: AllowlistStatus + entityPubkey: web3.PublicKey ): Promise { const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) const globalSettings = this.getGlobalSettingsPubkey() const ix = await this.program.methods - .setEntityAllowlistStatus( + .createEntityRegistry( this.entityTypeToAnchorEnum(entityType), - entityPubkey, - this.allowlistStatusToAnchorEnum(status) + entityPubkey + ) + .accountsPartial({ + admin: this.getSignerKey(), + entityRegistry, + globalSettings, + }) + .instruction() + return ix + } + + async closeEntityRegistryIx( + entityType: EntityType, + entityPubkey: web3.PublicKey + ): Promise { + const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) + const globalSettings = this.getGlobalSettingsPubkey() + const ix = await this.program.methods + .closeEntityRegistry( + this.entityTypeToAnchorEnum(entityType), + entityPubkey ) .accountsPartial({ admin: this.getSignerKey(), @@ -95,11 +106,4 @@ export default class ControllerSDK { throw new Error(`Unsupported entity type ${entityType}`) } - - allowlistStatusToAnchorEnum(status: AllowlistStatus): IdlTypes['allowlistStatus'] { - if (status === AllowlistStatus.Allowed) return { allowed: {} } - if (status === AllowlistStatus.Disallowed) return { disallowed: {} } - - throw new Error(`Unsupported allowlist status ${status}`) - } } diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts index 860bc72..68db439 100644 --- a/packages/svm/tests/controller.test.ts +++ b/packages/svm/tests/controller.test.ts @@ -9,7 +9,7 @@ import { LiteSVM } from 'litesvm' import os from 'os' import path from 'path' -import ControllerSDK, { AllowlistStatus, EntityType } from '../sdks/controller/Controller' +import ControllerSDK, { EntityType } from '../sdks/controller/Controller' import * as ControllerIDL from '../target/idl/controller.json' import { Controller } from '../target/types/controller' import { expectTransactionError } from './helpers/settler-helpers' @@ -120,7 +120,7 @@ describe('Controller Program', () => { }) }) - describe('set_entity_allowlist_status', () => { + describe('EntityRegistry management', () => { let validator: web3.PublicKey let axia: web3.PublicKey let solver: web3.PublicKey @@ -137,19 +137,15 @@ describe('Controller Program', () => { solver2 = web3.Keypair.generate().publicKey }) - it('cannot set status if not admin', async () => { - const ix = await maliciousSdk.setEntityAllowlistStatusIx( - EntityType.Validator, - validator, - AllowlistStatus.Allowed - ) + it('cannot create registry if not admin', async () => { + const ix = await maliciousSdk.createEntityRegistryIx(EntityType.Validator, validator) const res = await makeTxSignAndSend(maliciousProvider, ix) expectTransactionError(res, 'Only admin can call this instruction') }) - it('should set allowlist status successfully (validator)', async () => { - const ix = await adminSdk.setEntityAllowlistStatusIx(EntityType.Validator, validator, AllowlistStatus.Allowed) + it('should create entity registry successfully (validator)', async () => { + const ix = await adminSdk.createEntityRegistryIx(EntityType.Validator, validator) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -158,11 +154,10 @@ describe('Controller Program', () => { expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should set allowlist status successfully (axia)', async () => { - const ix = await adminSdk.setEntityAllowlistStatusIx(EntityType.Axia, axia, AllowlistStatus.Allowed) + it('should create entity registry successfully (axia)', async () => { + const ix = await adminSdk.createEntityRegistryIx(EntityType.Axia, axia) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -171,11 +166,10 @@ describe('Controller Program', () => { expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should set allowlist status successfully (solver)', async () => { - const ix = await adminSdk.setEntityAllowlistStatusIx(EntityType.Solver, solver, AllowlistStatus.Allowed) + it('should create entity registry successfully (solver)', async () => { + const ix = await adminSdk.createEntityRegistryIx(EntityType.Solver, solver) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -184,7 +178,6 @@ describe('Controller Program', () => { expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) it('should change admin for next tests', async () => { @@ -195,55 +188,50 @@ describe('Controller Program', () => { expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) - it('should update status correctly (allowlist to blacklist transition) (validator)', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx( - EntityType.Validator, - validator, - AllowlistStatus.Disallowed - ) + it('should close entity registry (validator)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Validator, validator) await makeTxSignAndSend(otherAdminProvider, ix) - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) - ) - - expect(entityRegistry.entityType).to.deep.include({ validator: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - expect(entityRegistry.status).to.deep.include({ disallowed: {} }) + try { + await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } }) - it('should update status correctly (allowlist to blacklist transition) (axia)', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Axia, axia, AllowlistStatus.Disallowed) + it('should close entity registry (axia)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Axia, axia) await makeTxSignAndSend(otherAdminProvider, ix) - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - ) - - expect(entityRegistry.entityType).to.deep.include({ axia: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - expect(entityRegistry.status).to.deep.include({ disallowed: {} }) + try { + await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } }) - it('should update status correctly (allowlist to blacklist transition) (solver)', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Solver, solver, AllowlistStatus.Disallowed) + it('should close entity registry (solver)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Solver, solver) await makeTxSignAndSend(otherAdminProvider, ix) - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) - ) - - expect(entityRegistry.entityType).to.deep.include({ solver: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - expect(entityRegistry.status).to.deep.include({ disallowed: {} }) + try { + await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } }) - it('should update status correctly (blacklist to allowlist transition) (validator)', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx( - EntityType.Validator, - validator, - AllowlistStatus.Allowed - ) + it('should create entity registry after closing (validator)', async () => { + const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Validator, validator) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -252,11 +240,10 @@ describe('Controller Program', () => { expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should update status correctly (blacklist to allowlist transition) (axia)', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Axia, axia, AllowlistStatus.Allowed) + it('should create entity registry after closing (axia)', async () => { + const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Axia, axia) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -265,11 +252,10 @@ describe('Controller Program', () => { expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should update status correctly (blacklist to allowlist transition) (solver)', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Solver, solver, AllowlistStatus.Allowed) + it('should create entity registry after closing (solver)', async () => { + const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Solver, solver) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -278,15 +264,10 @@ describe('Controller Program', () => { expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should allowlist another validator', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx( - EntityType.Validator, - validator2, - AllowlistStatus.Allowed - ) + it('should create another validator registry', async () => { + const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Validator, validator2) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -294,11 +275,10 @@ describe('Controller Program', () => { ) expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should allowlist another axia', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Axia, axia2, AllowlistStatus.Allowed) + it('should create another axia registry', async () => { + const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Axia, axia2) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -306,11 +286,10 @@ describe('Controller Program', () => { ) expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) - it('should allowlist another solver', async () => { - const ix = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Solver, solver2, AllowlistStatus.Allowed) + it('should create another solver registry', async () => { + const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Solver, solver2) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -318,11 +297,10 @@ describe('Controller Program', () => { ) expect(entityRegistry.entityType).to.deep.include({ solver: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) - expect(entityRegistry.status).to.deep.include({ allowed: {} }) }) it('should create separate accounts for same pubkey with different entity types', async () => { - const ix1 = await otherAdminSdk.setEntityAllowlistStatusIx(EntityType.Validator, axia, AllowlistStatus.Allowed) + const ix1 = await otherAdminSdk.createEntityRegistryIx(EntityType.Validator, axia) await makeTxSignAndSend(otherAdminProvider, ix1) const validatorRegistry = await program.account.entityRegistry.fetch( @@ -333,9 +311,7 @@ describe('Controller Program', () => { ) expect(validatorRegistry.entityType).to.deep.include({ validator: {} }) - expect(validatorRegistry.status).to.deep.include({ allowed: {} }) expect(axiaRegistry.entityType).to.deep.include({ axia: {} }) - expect(axiaRegistry.status).to.deep.include({ allowed: {} }) const validatorPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) const axiaPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) From 5cfce2b585dc50c0c4c07836e905ea4ff6b8f875 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 15:21:46 -0300 Subject: [PATCH 07/12] Several fixes in Controller code --- packages/svm/programs/controller/src/errors.rs | 3 --- .../src/instructions/create_entity_registry.rs | 2 +- .../programs/controller/src/instructions/set_admin.rs | 11 +++++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/svm/programs/controller/src/errors.rs b/packages/svm/programs/controller/src/errors.rs index 3c8221f..3c6c177 100644 --- a/packages/svm/programs/controller/src/errors.rs +++ b/packages/svm/programs/controller/src/errors.rs @@ -7,7 +7,4 @@ pub enum ControllerError { #[msg("Only admin can call this instruction")] OnlyAdmin, - - #[msg("Math error")] - MathError, } diff --git a/packages/svm/programs/controller/src/instructions/create_entity_registry.rs b/packages/svm/programs/controller/src/instructions/create_entity_registry.rs index c58a739..d328cc6 100644 --- a/packages/svm/programs/controller/src/instructions/create_entity_registry.rs +++ b/packages/svm/programs/controller/src/instructions/create_entity_registry.rs @@ -13,7 +13,7 @@ pub struct CreateEntityRegistry<'info> { pub admin: Signer<'info>, #[account( - init_if_needed, + init, seeds = [b"entity-registry".as_ref(), &[entity_type as u8], entity_pubkey.as_ref()], bump, payer = admin, diff --git a/packages/svm/programs/controller/src/instructions/set_admin.rs b/packages/svm/programs/controller/src/instructions/set_admin.rs index ef96220..dc59043 100644 --- a/packages/svm/programs/controller/src/instructions/set_admin.rs +++ b/packages/svm/programs/controller/src/instructions/set_admin.rs @@ -21,5 +21,16 @@ pub fn set_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { global_settings.admin = new_admin; + emit!(SetAdminEvent { + new_admin, + timestamp: Clock::get()?.unix_timestamp as u64, + }); + Ok(()) } + +#[event] +pub struct SetAdminEvent { + pub new_admin: Pubkey, + pub timestamp: u64, +} From 4282a1fa884536018a5fdceb999a8bc11127f841 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 15:27:02 -0300 Subject: [PATCH 08/12] Code review: address other comments --- packages/svm/programs/controller/Cargo.toml | 2 +- .../svm/programs/controller/src/constants.rs | 2 +- packages/svm/sdks/controller/Controller.ts | 25 +++++++------------ packages/svm/tests/controller.test.ts | 8 ++---- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/packages/svm/programs/controller/Cargo.toml b/packages/svm/programs/controller/Cargo.toml index 5826092..f564902 100644 --- a/packages/svm/programs/controller/Cargo.toml +++ b/packages/svm/programs/controller/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "controller" version = "0.1.0" -description = "Created with Anchor" +description = "Manages allowlist for Mimic Protocol entities" edition = "2021" [lib] diff --git a/packages/svm/programs/controller/src/constants.rs b/packages/svm/programs/controller/src/constants.rs index 756ec67..c2edf8e 100644 --- a/packages/svm/programs/controller/src/constants.rs +++ b/packages/svm/programs/controller/src/constants.rs @@ -1,4 +1,4 @@ -pub const DEPLOYER_KEY: &'static str = env!( +pub const DEPLOYER_KEY: &str = env!( "DEPLOYER_KEY", "Please set the DEPLOYER_KEY env variable before compiling." ); diff --git a/packages/svm/sdks/controller/Controller.ts b/packages/svm/sdks/controller/Controller.ts index 527aaa8..5bc252b 100644 --- a/packages/svm/sdks/controller/Controller.ts +++ b/packages/svm/sdks/controller/Controller.ts @@ -3,14 +3,13 @@ import { IdlTypes, Program, Provider, web3 } from '@coral-xyz/anchor' import * as ControllerIDL from '../../target/idl/controller.json' import { Controller } from '../../target/types/controller' -export enum EntityType { - // eslint-disable-next-line no-unused-vars - Validator = 1, - // eslint-disable-next-line no-unused-vars - Axia = 2, - // eslint-disable-next-line no-unused-vars - Solver = 3, -} +export const EntityType = { + Validator: 1, + Axia: 2, + Solver: 3, +} as const + +export type EntityType = (typeof EntityType)[keyof typeof EntityType] export default class ControllerSDK { protected program: Program @@ -50,10 +49,7 @@ export default class ControllerSDK { const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) const globalSettings = this.getGlobalSettingsPubkey() const ix = await this.program.methods - .createEntityRegistry( - this.entityTypeToAnchorEnum(entityType), - entityPubkey - ) + .createEntityRegistry(this.entityTypeToAnchorEnum(entityType), entityPubkey) .accountsPartial({ admin: this.getSignerKey(), entityRegistry, @@ -70,10 +66,7 @@ export default class ControllerSDK { const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) const globalSettings = this.getGlobalSettingsPubkey() const ix = await this.program.methods - .closeEntityRegistry( - this.entityTypeToAnchorEnum(entityType), - entityPubkey - ) + .closeEntityRegistry(this.entityTypeToAnchorEnum(entityType), entityPubkey) .accountsPartial({ admin: this.getSignerKey(), entityRegistry, diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts index 68db439..7e2c171 100644 --- a/packages/svm/tests/controller.test.ts +++ b/packages/svm/tests/controller.test.ts @@ -207,9 +207,7 @@ describe('Controller Program', () => { await makeTxSignAndSend(otherAdminProvider, ix) try { - await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - ) + await program.account.entityRegistry.fetch(otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia)) expect.fail('Entity registry should not exist after closing') } catch (error: any) { expect(error.message).to.include('Account does not exist') @@ -221,9 +219,7 @@ describe('Controller Program', () => { await makeTxSignAndSend(otherAdminProvider, ix) try { - await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) - ) + await program.account.entityRegistry.fetch(otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver)) expect.fail('Entity registry should not exist after closing') } catch (error: any) { expect(error.message).to.include('Account does not exist') From b918391ffac344c84582b1cb312886430959708b Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 15:33:05 -0300 Subject: [PATCH 09/12] Code review: add license --- packages/svm/programs/controller/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svm/programs/controller/Cargo.toml b/packages/svm/programs/controller/Cargo.toml index f564902..bd4a3c1 100644 --- a/packages/svm/programs/controller/Cargo.toml +++ b/packages/svm/programs/controller/Cargo.toml @@ -3,6 +3,7 @@ name = "controller" version = "0.1.0" description = "Manages allowlist for Mimic Protocol entities" edition = "2021" +license = "GPL-3.0-only" [lib] crate-type = ["cdylib", "lib"] From e1ef9edaec1e4ced8057fde71e90e58146f44c9a Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Mon, 5 Jan 2026 15:17:11 -0300 Subject: [PATCH 10/12] Code review: several comments --- .../controller/src/instructions/mod.rs | 4 +- ...tity_registry.rs => set_allowed_entity.rs} | 6 +- packages/svm/programs/controller/src/lib.rs | 6 +- packages/svm/sdks/controller/Controller.ts | 7 +-- packages/svm/tests/controller.test.ts | 58 ++++++++++--------- packages/svm/tests/helpers/constants.ts | 51 ---------------- .../{settler-helpers.ts => helpers.ts} | 15 +++++ 7 files changed, 55 insertions(+), 92 deletions(-) rename packages/svm/programs/controller/src/instructions/{create_entity_registry.rs => set_allowed_entity.rs} (90%) delete mode 100644 packages/svm/tests/helpers/constants.ts rename packages/svm/tests/helpers/{settler-helpers.ts => helpers.ts} (59%) diff --git a/packages/svm/programs/controller/src/instructions/mod.rs b/packages/svm/programs/controller/src/instructions/mod.rs index 98788d1..3028dc9 100644 --- a/packages/svm/programs/controller/src/instructions/mod.rs +++ b/packages/svm/programs/controller/src/instructions/mod.rs @@ -1,9 +1,9 @@ pub mod close_entity_registry; -pub mod create_entity_registry; pub mod initialize; pub mod set_admin; +pub mod set_allowed_entity; pub use close_entity_registry::*; -pub use create_entity_registry::*; pub use initialize::*; pub use set_admin::*; +pub use set_allowed_entity::*; diff --git a/packages/svm/programs/controller/src/instructions/create_entity_registry.rs b/packages/svm/programs/controller/src/instructions/set_allowed_entity.rs similarity index 90% rename from packages/svm/programs/controller/src/instructions/create_entity_registry.rs rename to packages/svm/programs/controller/src/instructions/set_allowed_entity.rs index d328cc6..f264eea 100644 --- a/packages/svm/programs/controller/src/instructions/create_entity_registry.rs +++ b/packages/svm/programs/controller/src/instructions/set_allowed_entity.rs @@ -8,7 +8,7 @@ use crate::{ #[derive(Accounts)] #[instruction(entity_type: EntityType, entity_pubkey: Pubkey)] -pub struct CreateEntityRegistry<'info> { +pub struct SetAllowedEntity<'info> { #[account(mut)] pub admin: Signer<'info>, @@ -31,8 +31,8 @@ pub struct CreateEntityRegistry<'info> { pub system_program: Program<'info, System>, } -pub fn create_entity_registry( - ctx: Context, +pub fn set_allowed_entity( + ctx: Context, entity_type: EntityType, entity_pubkey: Pubkey, ) -> Result<()> { diff --git a/packages/svm/programs/controller/src/lib.rs b/packages/svm/programs/controller/src/lib.rs index 22a70d9..a52a231 100644 --- a/packages/svm/programs/controller/src/lib.rs +++ b/packages/svm/programs/controller/src/lib.rs @@ -22,12 +22,12 @@ pub mod controller { instructions::set_admin(ctx, new_admin) } - pub fn create_entity_registry( - ctx: Context, + pub fn set_allowed_entity( + ctx: Context, entity_type: EntityType, entity_pubkey: Pubkey, ) -> Result<()> { - instructions::create_entity_registry(ctx, entity_type, entity_pubkey) + instructions::set_allowed_entity(ctx, entity_type, entity_pubkey) } pub fn close_entity_registry( diff --git a/packages/svm/sdks/controller/Controller.ts b/packages/svm/sdks/controller/Controller.ts index 5bc252b..e2e7b72 100644 --- a/packages/svm/sdks/controller/Controller.ts +++ b/packages/svm/sdks/controller/Controller.ts @@ -42,14 +42,11 @@ export default class ControllerSDK { return ix } - async createEntityRegistryIx( - entityType: EntityType, - entityPubkey: web3.PublicKey - ): Promise { + async setAllowedEntityIx(entityType: EntityType, entityPubkey: web3.PublicKey): Promise { const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) const globalSettings = this.getGlobalSettingsPubkey() const ix = await this.program.methods - .createEntityRegistry(this.entityTypeToAnchorEnum(entityType), entityPubkey) + .setAllowedEntity(this.entityTypeToAnchorEnum(entityType), entityPubkey) .accountsPartial({ admin: this.getSignerKey(), entityRegistry, diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts index 7e2c171..a0a2ffa 100644 --- a/packages/svm/tests/controller.test.ts +++ b/packages/svm/tests/controller.test.ts @@ -12,7 +12,7 @@ import path from 'path' import ControllerSDK, { EntityType } from '../sdks/controller/Controller' import * as ControllerIDL from '../target/idl/controller.json' import { Controller } from '../target/types/controller' -import { expectTransactionError } from './helpers/settler-helpers' +import { expectTransactionError, randomKeypair, randomPubkey, toLamports } from './helpers/helpers' import { makeTxSignAndSend, warpSeconds } from './utils' describe('Controller Program', () => { @@ -39,9 +39,9 @@ describe('Controller Program', () => { deployer = web3.Keypair.fromSecretKey( Uint8Array.from(JSON.parse(fs.readFileSync(path.join(os.homedir(), '.config', 'solana', 'id.json'), 'utf8'))) ) - admin = web3.Keypair.generate() - otherAdmin = web3.Keypair.generate() - malicious = web3.Keypair.generate() + admin = randomKeypair() + otherAdmin = randomKeypair() + malicious = randomKeypair() client = fromWorkspace(path.join(__dirname, '../')).withBuiltins() @@ -57,10 +57,10 @@ describe('Controller Program', () => { otherAdminSdk = new ControllerSDK(otherAdminProvider) maliciousSdk = new ControllerSDK(maliciousProvider) - deployerProvider.client.airdrop(deployer.publicKey, BigInt(100_000_000_000)) - deployerProvider.client.airdrop(admin.publicKey, BigInt(100_000_000_000)) - deployerProvider.client.airdrop(otherAdmin.publicKey, BigInt(100_000_000_000)) - deployerProvider.client.airdrop(malicious.publicKey, BigInt(100_000_000_000)) + deployerProvider.client.airdrop(deployer.publicKey, toLamports(100)) + deployerProvider.client.airdrop(admin.publicKey, toLamports(100)) + deployerProvider.client.airdrop(otherAdmin.publicKey, toLamports(100)) + deployerProvider.client.airdrop(malicious.publicKey, toLamports(100)) // Warp so that we're not at t=0 warpSeconds(deployerProvider, 100) @@ -73,7 +73,7 @@ describe('Controller Program', () => { describe('Controller', () => { describe('initialize', () => { it('cannot initialize if not deployer', async () => { - const newAdmin = web3.Keypair.generate().publicKey + const newAdmin = randomPubkey() const ix = await maliciousSdk.initializeIx(newAdmin) const res = await makeTxSignAndSend(maliciousProvider, ix) @@ -97,9 +97,9 @@ describe('Controller Program', () => { }) }) - describe('set_admin', () => { + describe('set admin', () => { it('cannot set admin if not admin', async () => { - const newAdmin = web3.Keypair.generate().publicKey + const newAdmin = randomPubkey() const ix = await maliciousSdk.setAdmin(newAdmin) const res = await makeTxSignAndSend(maliciousProvider, ix) @@ -113,7 +113,9 @@ describe('Controller Program', () => { const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) + }) + after('reset admin to original for subsequent tests', async () => { // Reset admin to original for subsequent tests const resetIx = await otherAdminSdk.setAdmin(admin.publicKey) await makeTxSignAndSend(otherAdminProvider, resetIx) @@ -129,23 +131,23 @@ describe('Controller Program', () => { let solver2: web3.PublicKey before(() => { - validator = web3.Keypair.generate().publicKey - axia = web3.Keypair.generate().publicKey - solver = web3.Keypair.generate().publicKey - validator2 = web3.Keypair.generate().publicKey - axia2 = web3.Keypair.generate().publicKey - solver2 = web3.Keypair.generate().publicKey + validator = randomPubkey() + axia = randomPubkey() + solver = randomPubkey() + validator2 = randomPubkey() + axia2 = randomPubkey() + solver2 = randomPubkey() }) it('cannot create registry if not admin', async () => { - const ix = await maliciousSdk.createEntityRegistryIx(EntityType.Validator, validator) + const ix = await maliciousSdk.setAllowedEntityIx(EntityType.Validator, validator) const res = await makeTxSignAndSend(maliciousProvider, ix) expectTransactionError(res, 'Only admin can call this instruction') }) it('should create entity registry successfully (validator)', async () => { - const ix = await adminSdk.createEntityRegistryIx(EntityType.Validator, validator) + const ix = await adminSdk.setAllowedEntityIx(EntityType.Validator, validator) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -157,7 +159,7 @@ describe('Controller Program', () => { }) it('should create entity registry successfully (axia)', async () => { - const ix = await adminSdk.createEntityRegistryIx(EntityType.Axia, axia) + const ix = await adminSdk.setAllowedEntityIx(EntityType.Axia, axia) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -169,7 +171,7 @@ describe('Controller Program', () => { }) it('should create entity registry successfully (solver)', async () => { - const ix = await adminSdk.createEntityRegistryIx(EntityType.Solver, solver) + const ix = await adminSdk.setAllowedEntityIx(EntityType.Solver, solver) await makeTxSignAndSend(adminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -227,7 +229,7 @@ describe('Controller Program', () => { }) it('should create entity registry after closing (validator)', async () => { - const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Validator, validator) + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -239,7 +241,7 @@ describe('Controller Program', () => { }) it('should create entity registry after closing (axia)', async () => { - const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Axia, axia) + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -251,7 +253,7 @@ describe('Controller Program', () => { }) it('should create entity registry after closing (solver)', async () => { - const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Solver, solver) + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -263,7 +265,7 @@ describe('Controller Program', () => { }) it('should create another validator registry', async () => { - const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Validator, validator2) + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator2) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -274,7 +276,7 @@ describe('Controller Program', () => { }) it('should create another axia registry', async () => { - const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Axia, axia2) + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia2) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -285,7 +287,7 @@ describe('Controller Program', () => { }) it('should create another solver registry', async () => { - const ix = await otherAdminSdk.createEntityRegistryIx(EntityType.Solver, solver2) + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver2) await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( @@ -296,7 +298,7 @@ describe('Controller Program', () => { }) it('should create separate accounts for same pubkey with different entity types', async () => { - const ix1 = await otherAdminSdk.createEntityRegistryIx(EntityType.Validator, axia) + const ix1 = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, axia) await makeTxSignAndSend(otherAdminProvider, ix1) const validatorRegistry = await program.account.entityRegistry.fetch( diff --git a/packages/svm/tests/helpers/constants.ts b/packages/svm/tests/helpers/constants.ts deleted file mode 100644 index a7cd412..0000000 --- a/packages/svm/tests/helpers/constants.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Test constants for time values (in seconds) -export const COOLDOWN_PERIOD = 3600 -export const COOLDOWN_PERIOD_PLUS_ONE = 3601 -export const INTENT_DEADLINE_OFFSET = 3600 -export const PROPOSAL_DEADLINE_OFFSET = 1800 -export const STALE_CLAIM_DELAY = 50 -export const STALE_CLAIM_DELAY_PLUS_ONE = 51 -export const SHORT_DEADLINE = 100 -export const MEDIUM_DEADLINE = 300 -export const LONG_DEADLINE = 500 -export const VERY_SHORT_DEADLINE = 10 -export const WARP_TIME_SHORT = 100 -export const WARP_TIME_MEDIUM = 300 -export const WARP_TIME_LONG = 500 -export const EXPIRATION_TEST_DELAY = 80 -export const EXPIRATION_TEST_DELAY_PLUS_ONE = 81 -export const DOUBLE_CLAIM_DELAY = 90 -export const DOUBLE_CLAIM_DELAY_PLUS_ONE = 91 - -// Test constants for amounts -export const DEFAULT_MAX_FEE = 1000 -export const DEFAULT_MAX_FEE_HALF = 500 -export const DEFAULT_MAX_FEE_EXCEED = 1500 -export const ACCOUNT_CLOSE_FEE = 5000 // Fee for closing accounts - -// Test constants for data -export const DEFAULT_DATA_HEX = '010203' -export const DEFAULT_TOPIC_HEX = Buffer.from(Array(32).fill(1)).toString('hex') -export const DEFAULT_EVENT_DATA_HEX = '040506' -export const EMPTY_DATA_HEX = '' -export const TEST_DATA_HEX_1 = '070809' -export const TEST_DATA_HEX_2 = '0a0b0c' -export const TEST_DATA_HEX_3 = 'deadbeef' - -// Test constants for validation -export const DEFAULT_MIN_VALIDATIONS = 1 -export const MULTIPLE_MIN_VALIDATIONS = 3 - -// Test constants for iterations -export const LARGE_EXTEND_ITERATIONS = 100 -export const MULTIPLE_PROPOSALS_COUNT = 20 - -// Test constants for hex string lengths -export const INTENT_HASH_LENGTH = 32 // bytes -export const NONCE_LENGTH = 32 // bytes -export const SIGNATURE_LENGTH = 64 // bytes - -// Test constants for cooldown validation -export const MAX_COOLDOWN = 3600 * 24 * 30 // 30 days -export const MAX_COOLDOWN_PLUS_ONE = MAX_COOLDOWN + 1 -export const MIN_COOLDOWN = 0 diff --git a/packages/svm/tests/helpers/settler-helpers.ts b/packages/svm/tests/helpers/helpers.ts similarity index 59% rename from packages/svm/tests/helpers/settler-helpers.ts rename to packages/svm/tests/helpers/helpers.ts index f149059..717d553 100644 --- a/packages/svm/tests/helpers/settler-helpers.ts +++ b/packages/svm/tests/helpers/helpers.ts @@ -1,6 +1,9 @@ +import { web3 } from '@coral-xyz/anchor' import { expect } from 'chai' import { FailedTransactionMetadata, TransactionMetadata } from 'litesvm' +export const LAMPORTS_PER_SOL = 1_000_000_000 + /** * Helper to expect transaction errors consistently */ @@ -16,3 +19,15 @@ export function expectTransactionError( expect(res.toString()).to.include(expectedMessage) } } + +export function toLamports(sol: number): bigint { + return BigInt(sol * LAMPORTS_PER_SOL) +} + +export function randomKeypair(): web3.Keypair { + return web3.Keypair.generate() +} + +export function randomPubkey(): web3.PublicKey { + return randomKeypair().publicKey +} From c715467bc99fe4fbd5bfe417847e3c7d651822ce Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Mon, 5 Jan 2026 15:42:38 -0300 Subject: [PATCH 11/12] Code review: context() pattern in tests --- packages/svm/tests/controller.test.ts | 405 ++++++++++++++------------ 1 file changed, 214 insertions(+), 191 deletions(-) diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts index a0a2ffa..b22292a 100644 --- a/packages/svm/tests/controller.test.ts +++ b/packages/svm/tests/controller.test.ts @@ -72,53 +72,60 @@ describe('Controller Program', () => { describe('Controller', () => { describe('initialize', () => { - it('cannot initialize if not deployer', async () => { - const newAdmin = randomPubkey() + context('when caller is not deployer', async () => { + it('cannot initialize', async () => { + const newAdmin = randomPubkey() - const ix = await maliciousSdk.initializeIx(newAdmin) - const res = await makeTxSignAndSend(maliciousProvider, ix) + const ix = await maliciousSdk.initializeIx(newAdmin) + const res = await makeTxSignAndSend(maliciousProvider, ix) - expectTransactionError(res, 'Only deployer can call this instruction') + expectTransactionError(res, 'Only deployer can call this instruction') + }) }) - it('should initialize', async () => { - const ix = await deployerSdk.initializeIx(admin.publicKey) - await makeTxSignAndSend(deployerProvider, ix) + context('when caller is deployer', async () => { + it('should initialize', async () => { + const ix = await deployerSdk.initializeIx(admin.publicKey) + await makeTxSignAndSend(deployerProvider, ix) - const settings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) - }) + const settings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) + }) - it('cannot call initialize again', async () => { - const ix = await deployerSdk.initializeIx(admin.publicKey) - const res = await makeTxSignAndSend(deployerProvider, ix) + it('cannot call initialize again', async () => { + const ix = await deployerSdk.initializeIx(admin.publicKey) + const res = await makeTxSignAndSend(deployerProvider, ix) - expectTransactionError(res, 'already in use') + expectTransactionError(res, 'already in use') + }) }) }) describe('set admin', () => { - it('cannot set admin if not admin', async () => { - const newAdmin = randomPubkey() + context('when caller is not admin', async () => { + it('cannot set admin', async () => { + const newAdmin = randomPubkey() - const ix = await maliciousSdk.setAdmin(newAdmin) - const res = await makeTxSignAndSend(maliciousProvider, ix) + const ix = await maliciousSdk.setAdmin(newAdmin) + const res = await makeTxSignAndSend(maliciousProvider, ix) - expectTransactionError(res, 'Only admin can call this instruction') + expectTransactionError(res, 'Only admin can call this instruction') + }) }) - it('can set admin', async () => { - const ix = await adminSdk.setAdmin(otherAdmin.publicKey) - await makeTxSignAndSend(adminProvider, ix) + context('when caller is admin', async () => { + after('reset admin to original for subsequent tests', async () => { + const resetIx = await otherAdminSdk.setAdmin(admin.publicKey) + await makeTxSignAndSend(otherAdminProvider, resetIx) + }) - const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) - }) + it('can set admin', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) - after('reset admin to original for subsequent tests', async () => { - // Reset admin to original for subsequent tests - const resetIx = await otherAdminSdk.setAdmin(admin.publicKey) - await makeTxSignAndSend(otherAdminProvider, resetIx) + const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) + }) }) }) @@ -139,181 +146,197 @@ describe('Controller Program', () => { solver2 = randomPubkey() }) - it('cannot create registry if not admin', async () => { - const ix = await maliciousSdk.setAllowedEntityIx(EntityType.Validator, validator) - const res = await makeTxSignAndSend(maliciousProvider, ix) - - expectTransactionError(res, 'Only admin can call this instruction') - }) - - it('should create entity registry successfully (validator)', async () => { - const ix = await adminSdk.setAllowedEntityIx(EntityType.Validator, validator) - await makeTxSignAndSend(adminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) - ) + context('when the caller is admin', async () => { + it('cannot create registry', async () => { + const ix = await maliciousSdk.setAllowedEntityIx(EntityType.Validator, validator) + const res = await makeTxSignAndSend(maliciousProvider, ix) - expect(entityRegistry.entityType).to.deep.include({ validator: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) + expectTransactionError(res, 'Only admin can call this instruction') + }) }) - it('should create entity registry successfully (axia)', async () => { - const ix = await adminSdk.setAllowedEntityIx(EntityType.Axia, axia) - await makeTxSignAndSend(adminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - ) - - expect(entityRegistry.entityType).to.deep.include({ axia: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - }) - - it('should create entity registry successfully (solver)', async () => { - const ix = await adminSdk.setAllowedEntityIx(EntityType.Solver, solver) - await makeTxSignAndSend(adminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) - ) + context('when the caller is admin', async () => { + it('should create entity registry successfully (validator)', async () => { + const ix = await adminSdk.setAllowedEntityIx(EntityType.Validator, validator) + await makeTxSignAndSend(adminProvider, ix) - expect(entityRegistry.entityType).to.deep.include({ solver: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - }) - - it('should change admin for next tests', async () => { - const ix = await adminSdk.setAdmin(otherAdmin.publicKey) - await makeTxSignAndSend(adminProvider, ix) - - const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) - }) - - it('should close entity registry (validator)', async () => { - const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Validator, validator) - await makeTxSignAndSend(otherAdminProvider, ix) - - try { - await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) ) - expect.fail('Entity registry should not exist after closing') - } catch (error: any) { - expect(error.message).to.include('Account does not exist') - } - }) - - it('should close entity registry (axia)', async () => { - const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Axia, axia) - await makeTxSignAndSend(otherAdminProvider, ix) - - try { - await program.account.entityRegistry.fetch(otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia)) - expect.fail('Entity registry should not exist after closing') - } catch (error: any) { - expect(error.message).to.include('Account does not exist') - } - }) - it('should close entity registry (solver)', async () => { - const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Solver, solver) - await makeTxSignAndSend(otherAdminProvider, ix) + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) + }) - try { - await program.account.entityRegistry.fetch(otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver)) - expect.fail('Entity registry should not exist after closing') - } catch (error: any) { - expect(error.message).to.include('Account does not exist') - } - }) - - it('should create entity registry after closing (validator)', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator) - await makeTxSignAndSend(otherAdminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) - ) - - expect(entityRegistry.entityType).to.deep.include({ validator: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - }) - - it('should create entity registry after closing (axia)', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia) - await makeTxSignAndSend(otherAdminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - ) - - expect(entityRegistry.entityType).to.deep.include({ axia: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - }) - - it('should create entity registry after closing (solver)', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver) - await makeTxSignAndSend(otherAdminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) - ) + it('should create entity registry successfully (axia)', async () => { + const ix = await adminSdk.setAllowedEntityIx(EntityType.Axia, axia) + await makeTxSignAndSend(adminProvider, ix) - expect(entityRegistry.entityType).to.deep.include({ solver: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - }) - - it('should create another validator registry', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator2) - await makeTxSignAndSend(otherAdminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator2) - ) - expect(entityRegistry.entityType).to.deep.include({ validator: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) - }) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) - it('should create another axia registry', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia2) - await makeTxSignAndSend(otherAdminProvider, ix) + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) + }) - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia2) - ) - expect(entityRegistry.entityType).to.deep.include({ axia: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) - }) + it('should create entity registry successfully (solver)', async () => { + const ix = await adminSdk.setAllowedEntityIx(EntityType.Solver, solver) + await makeTxSignAndSend(adminProvider, ix) - it('should create another solver registry', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver2) - await makeTxSignAndSend(otherAdminProvider, ix) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver2) - ) - expect(entityRegistry.entityType).to.deep.include({ solver: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) + }) }) - it('should create separate accounts for same pubkey with different entity types', async () => { - const ix1 = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, axia) - await makeTxSignAndSend(otherAdminProvider, ix1) - - const validatorRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) - ) - const axiaRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - ) - - expect(validatorRegistry.entityType).to.deep.include({ validator: {} }) - expect(axiaRegistry.entityType).to.deep.include({ axia: {} }) - - const validatorPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) - const axiaPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - expect(validatorPda.toString()).to.not.eq(axiaPda.toString()) + context('when the admin is changed and caller is new admin', async () => { + before('should change admin for next tests', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) + + const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) + }) + + context('when closing entity registries', async () => { + it('should close entity registry (validator)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Validator, validator) + await makeTxSignAndSend(otherAdminProvider, ix) + + try { + await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } + }) + + it('should close entity registry (axia)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Axia, axia) + await makeTxSignAndSend(otherAdminProvider, ix) + + try { + await program.account.entityRegistry.fetch(otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia)) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } + }) + + it('should close entity registry (solver)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Solver, solver) + await makeTxSignAndSend(otherAdminProvider, ix) + + try { + await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } + }) + }) + + context('when allowing entities after closing their registries', async () => { + it('should create entity registry after closing (validator)', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator) + await makeTxSignAndSend(otherAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) + + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) + }) + + it('should create entity registry after closing (axia)', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia) + await makeTxSignAndSend(otherAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) + + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) + }) + + it('should create entity registry after closing (solver)', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver) + await makeTxSignAndSend(otherAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) + + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) + }) + }) + + context('when allowing other entities', async () => { + it('should create another validator registry', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator2) + await makeTxSignAndSend(otherAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator2) + ) + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) + }) + + it('should create another axia registry', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia2) + await makeTxSignAndSend(otherAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia2) + ) + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) + }) + + it('should create another solver registry', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver2) + await makeTxSignAndSend(otherAdminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver2) + ) + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) + }) + }) + + context('when allowing entities for multiple roles', async () => { + it('should create separate accounts for same pubkey with different entity types', async () => { + const ix1 = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, axia) + await makeTxSignAndSend(otherAdminProvider, ix1) + + const validatorRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) + ) + const axiaRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) + + expect(validatorRegistry.entityType).to.deep.include({ validator: {} }) + expect(axiaRegistry.entityType).to.deep.include({ axia: {} }) + + const validatorPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) + const axiaPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + expect(validatorPda.toString()).to.not.eq(axiaPda.toString()) + }) + }) }) }) }) From c54c71b14b6fc41debb9b4240c7b63f69658f0de Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 6 Jan 2026 12:20:37 -0300 Subject: [PATCH 12/12] Code review: final comments --- packages/svm/programs/controller/Cargo.toml | 2 +- packages/svm/tests/controller.test.ts | 399 ++++++++++---------- 2 files changed, 196 insertions(+), 205 deletions(-) diff --git a/packages/svm/programs/controller/Cargo.toml b/packages/svm/programs/controller/Cargo.toml index bd4a3c1..29e05b6 100644 --- a/packages/svm/programs/controller/Cargo.toml +++ b/packages/svm/programs/controller/Cargo.toml @@ -3,7 +3,7 @@ name = "controller" version = "0.1.0" description = "Manages allowlist for Mimic Protocol entities" edition = "2021" -license = "GPL-3.0-only" +license = "GPL-3.0" [lib] crate-type = ["cdylib", "lib"] diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts index b22292a..bf070a1 100644 --- a/packages/svm/tests/controller.test.ts +++ b/packages/svm/tests/controller.test.ts @@ -15,7 +15,7 @@ import { Controller } from '../target/types/controller' import { expectTransactionError, randomKeypair, randomPubkey, toLamports } from './helpers/helpers' import { makeTxSignAndSend, warpSeconds } from './utils' -describe('Controller Program', () => { +describe('Controller', () => { let client: LiteSVM let deployer: web3.Keypair @@ -70,122 +70,203 @@ describe('Controller Program', () => { client.expireBlockhash() }) - describe('Controller', () => { - describe('initialize', () => { - context('when caller is not deployer', async () => { - it('cannot initialize', async () => { - const newAdmin = randomPubkey() + describe('initialize', () => { + context('when caller is not deployer', async () => { + it('cannot initialize', async () => { + const newAdmin = randomPubkey() - const ix = await maliciousSdk.initializeIx(newAdmin) - const res = await makeTxSignAndSend(maliciousProvider, ix) + const ix = await maliciousSdk.initializeIx(newAdmin) + const res = await makeTxSignAndSend(maliciousProvider, ix) - expectTransactionError(res, 'Only deployer can call this instruction') - }) + expectTransactionError(res, 'Only deployer can call this instruction') }) + }) - context('when caller is deployer', async () => { - it('should initialize', async () => { - const ix = await deployerSdk.initializeIx(admin.publicKey) - await makeTxSignAndSend(deployerProvider, ix) + context('when caller is deployer', async () => { + it('should initialize', async () => { + const ix = await deployerSdk.initializeIx(admin.publicKey) + await makeTxSignAndSend(deployerProvider, ix) - const settings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) - }) + const settings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) + }) - it('cannot call initialize again', async () => { - const ix = await deployerSdk.initializeIx(admin.publicKey) - const res = await makeTxSignAndSend(deployerProvider, ix) + it('cannot call initialize again', async () => { + const ix = await deployerSdk.initializeIx(admin.publicKey) + const res = await makeTxSignAndSend(deployerProvider, ix) - expectTransactionError(res, 'already in use') - }) + expectTransactionError(res, 'already in use') }) }) + }) - describe('set admin', () => { - context('when caller is not admin', async () => { - it('cannot set admin', async () => { - const newAdmin = randomPubkey() + describe('set admin', () => { + context('when caller is not admin', async () => { + it('cannot set admin', async () => { + const newAdmin = randomPubkey() - const ix = await maliciousSdk.setAdmin(newAdmin) - const res = await makeTxSignAndSend(maliciousProvider, ix) + const ix = await maliciousSdk.setAdmin(newAdmin) + const res = await makeTxSignAndSend(maliciousProvider, ix) - expectTransactionError(res, 'Only admin can call this instruction') - }) + expectTransactionError(res, 'Only admin can call this instruction') }) + }) - context('when caller is admin', async () => { - after('reset admin to original for subsequent tests', async () => { - const resetIx = await otherAdminSdk.setAdmin(admin.publicKey) - await makeTxSignAndSend(otherAdminProvider, resetIx) - }) + context('when caller is admin', async () => { + after('reset admin to original for subsequent tests', async () => { + const resetIx = await otherAdminSdk.setAdmin(admin.publicKey) + await makeTxSignAndSend(otherAdminProvider, resetIx) + }) + + it('can set admin', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) + + const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) + }) + }) + }) + + describe('EntityRegistry management', () => { + const validator = randomPubkey() + const axia = randomPubkey() + const solver = randomPubkey() + const validator2 = randomPubkey() + const axia2 = randomPubkey() + const solver2 = randomPubkey() + + context('when the caller is not admin', async () => { + it('cannot create registry', async () => { + const ix = await maliciousSdk.setAllowedEntityIx(EntityType.Validator, validator) + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, 'Only admin can call this instruction') + }) + }) - it('can set admin', async () => { - const ix = await adminSdk.setAdmin(otherAdmin.publicKey) - await makeTxSignAndSend(adminProvider, ix) + context('when the caller is admin', async () => { + it('should create entity registry successfully (validator)', async () => { + const ix = await adminSdk.setAllowedEntityIx(EntityType.Validator, validator) + await makeTxSignAndSend(adminProvider, ix) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) + + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) + }) + + it('should create entity registry successfully (axia)', async () => { + const ix = await adminSdk.setAllowedEntityIx(EntityType.Axia, axia) + await makeTxSignAndSend(adminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) + + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) + }) + + it('should create entity registry successfully (solver)', async () => { + const ix = await adminSdk.setAllowedEntityIx(EntityType.Solver, solver) + await makeTxSignAndSend(adminProvider, ix) + + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) + + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) + }) + }) + + context('when the admin is changed and caller is new admin', async () => { + before('change admin for next tests', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) + }) + + context('when the admin was changed', async () => { + it('should have the new admin as admin', async () => { const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) }) - }) - describe('EntityRegistry management', () => { - let validator: web3.PublicKey - let axia: web3.PublicKey - let solver: web3.PublicKey - let validator2: web3.PublicKey - let axia2: web3.PublicKey - let solver2: web3.PublicKey - - before(() => { - validator = randomPubkey() - axia = randomPubkey() - solver = randomPubkey() - validator2 = randomPubkey() - axia2 = randomPubkey() - solver2 = randomPubkey() - }) + context('when closing entity registries', async () => { + it('should close entity registry (validator)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Validator, validator) + await makeTxSignAndSend(otherAdminProvider, ix) + + try { + await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } + }) - context('when the caller is admin', async () => { - it('cannot create registry', async () => { - const ix = await maliciousSdk.setAllowedEntityIx(EntityType.Validator, validator) - const res = await makeTxSignAndSend(maliciousProvider, ix) + it('should close entity registry (axia)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Axia, axia) + await makeTxSignAndSend(otherAdminProvider, ix) - expectTransactionError(res, 'Only admin can call this instruction') + try { + await program.account.entityRegistry.fetch(otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia)) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } + }) + + it('should close entity registry (solver)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Solver, solver) + await makeTxSignAndSend(otherAdminProvider, ix) + + try { + await program.account.entityRegistry.fetch(otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver)) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } }) }) - context('when the caller is admin', async () => { - it('should create entity registry successfully (validator)', async () => { - const ix = await adminSdk.setAllowedEntityIx(EntityType.Validator, validator) - await makeTxSignAndSend(adminProvider, ix) + context('when allowing entities after closing their registries', async () => { + it('should create entity registry after closing (validator)', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) ) expect(entityRegistry.entityType).to.deep.include({ validator: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) }) - it('should create entity registry successfully (axia)', async () => { - const ix = await adminSdk.setAllowedEntityIx(EntityType.Axia, axia) - await makeTxSignAndSend(adminProvider, ix) + it('should create entity registry after closing (axia)', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) ) expect(entityRegistry.entityType).to.deep.include({ axia: {} }) expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) }) - it('should create entity registry successfully (solver)', async () => { - const ix = await adminSdk.setAllowedEntityIx(EntityType.Solver, solver) - await makeTxSignAndSend(adminProvider, ix) + it('should create entity registry after closing (solver)', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver) + await makeTxSignAndSend(otherAdminProvider, ix) const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) ) expect(entityRegistry.entityType).to.deep.include({ solver: {} }) @@ -193,149 +274,59 @@ describe('Controller Program', () => { }) }) - context('when the admin is changed and caller is new admin', async () => { - before('should change admin for next tests', async () => { - const ix = await adminSdk.setAdmin(otherAdmin.publicKey) - await makeTxSignAndSend(adminProvider, ix) - - const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) - }) + context('when allowing other entities', async () => { + it('should create another validator registry', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator2) + await makeTxSignAndSend(otherAdminProvider, ix) - context('when closing entity registries', async () => { - it('should close entity registry (validator)', async () => { - const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Validator, validator) - await makeTxSignAndSend(otherAdminProvider, ix) - - try { - await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) - ) - expect.fail('Entity registry should not exist after closing') - } catch (error: any) { - expect(error.message).to.include('Account does not exist') - } - }) - - it('should close entity registry (axia)', async () => { - const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Axia, axia) - await makeTxSignAndSend(otherAdminProvider, ix) - - try { - await program.account.entityRegistry.fetch(otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia)) - expect.fail('Entity registry should not exist after closing') - } catch (error: any) { - expect(error.message).to.include('Account does not exist') - } - }) - - it('should close entity registry (solver)', async () => { - const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Solver, solver) - await makeTxSignAndSend(otherAdminProvider, ix) - - try { - await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) - ) - expect.fail('Entity registry should not exist after closing') - } catch (error: any) { - expect(error.message).to.include('Account does not exist') - } - }) + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator2) + ) + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) }) - context('when allowing entities after closing their registries', async () => { - it('should create entity registry after closing (validator)', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator) - await makeTxSignAndSend(otherAdminProvider, ix) + it('should create another axia registry', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia2) + await makeTxSignAndSend(otherAdminProvider, ix) - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) - ) - - expect(entityRegistry.entityType).to.deep.include({ validator: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - }) - - it('should create entity registry after closing (axia)', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia) - await makeTxSignAndSend(otherAdminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - ) - - expect(entityRegistry.entityType).to.deep.include({ axia: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - }) - - it('should create entity registry after closing (solver)', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver) - await makeTxSignAndSend(otherAdminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) - ) - - expect(entityRegistry.entityType).to.deep.include({ solver: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - }) + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia2) + ) + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) }) - context('when allowing other entities', async () => { - it('should create another validator registry', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, validator2) - await makeTxSignAndSend(otherAdminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, validator2) - ) - expect(entityRegistry.entityType).to.deep.include({ validator: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator2.toString()) - }) - - it('should create another axia registry', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia2) - await makeTxSignAndSend(otherAdminProvider, ix) - - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia2) - ) - expect(entityRegistry.entityType).to.deep.include({ axia: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia2.toString()) - }) - - it('should create another solver registry', async () => { - const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver2) - await makeTxSignAndSend(otherAdminProvider, ix) + it('should create another solver registry', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Solver, solver2) + await makeTxSignAndSend(otherAdminProvider, ix) - const entityRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver2) - ) - expect(entityRegistry.entityType).to.deep.include({ solver: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) - }) + const entityRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Solver, solver2) + ) + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver2.toString()) }) + }) - context('when allowing entities for multiple roles', async () => { - it('should create separate accounts for same pubkey with different entity types', async () => { - const ix1 = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, axia) - await makeTxSignAndSend(otherAdminProvider, ix1) + context('when allowing entities for multiple roles', async () => { + it('should create separate accounts for same pubkey with different entity types', async () => { + const ix1 = await otherAdminSdk.setAllowedEntityIx(EntityType.Validator, axia) + await makeTxSignAndSend(otherAdminProvider, ix1) - const validatorRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) - ) - const axiaRegistry = await program.account.entityRegistry.fetch( - otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - ) + const validatorRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) + ) + const axiaRegistry = await program.account.entityRegistry.fetch( + otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) - expect(validatorRegistry.entityType).to.deep.include({ validator: {} }) - expect(axiaRegistry.entityType).to.deep.include({ axia: {} }) + expect(validatorRegistry.entityType).to.deep.include({ validator: {} }) + expect(axiaRegistry.entityType).to.deep.include({ axia: {} }) - const validatorPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) - const axiaPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - expect(validatorPda.toString()).to.not.eq(axiaPda.toString()) - }) + const validatorPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Validator, axia) + const axiaPda = otherAdminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + expect(validatorPda.toString()).to.not.eq(axiaPda.toString()) }) }) })