From 878e5ed753bf34d1875d06047653623a313486c2 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 11:25:02 -0300 Subject: [PATCH 01/39] 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 57af08c08ec54839e5a7d13d02906a40b2790d14 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 11:36:40 -0300 Subject: [PATCH 02/39] Settler: Intent lifecycle --- packages/svm/programs/settler/Cargo.toml | 6 + .../svm/programs/settler/src/constants.rs | 4 + packages/svm/programs/settler/src/errors.rs | 79 + .../src/instructions/claim_stale_intent.rs | 27 + .../settler/src/instructions/create_intent.rs | 87 + .../settler/src/instructions/extend_intent.rs | 55 + .../settler/src/instructions/initialize.rs | 37 + .../programs/settler/src/instructions/mod.rs | 25 + .../src/instructions/set_paused_state.rs | 46 + packages/svm/programs/settler/src/lib.rs | 104 +- .../settler/src/state/fulfilled_intent.rs | 7 + .../svm/programs/settler/src/state/intent.rs | 96 + .../svm/programs/settler/src/state/mod.rs | 9 + .../settler/src/state/settler_settings.rs | 9 + .../settler/src/types/intent_event.rs | 13 + .../svm/programs/settler/src/types/max_fee.rs | 11 + .../svm/programs/settler/src/types/mod.rs | 7 + .../svm/programs/settler/src/types/op_type.rs | 9 + .../svm/programs/settler/src/utils/math.rs | 17 + .../svm/programs/settler/src/utils/mod.rs | 5 + .../svm/programs/settler/src/utils/sigs.rs | 75 + packages/svm/sdks/settler/Settler.ts | 317 ++ packages/svm/sdks/settler/types.ts | 49 + packages/svm/tests/helpers/settler-helpers.ts | 231 ++ packages/svm/tests/settler.test.ts | 2640 ++++++++++++++++- 25 files changed, 3944 insertions(+), 21 deletions(-) create mode 100644 packages/svm/programs/settler/src/constants.rs create mode 100644 packages/svm/programs/settler/src/errors.rs create mode 100644 packages/svm/programs/settler/src/instructions/claim_stale_intent.rs create mode 100644 packages/svm/programs/settler/src/instructions/create_intent.rs create mode 100644 packages/svm/programs/settler/src/instructions/extend_intent.rs create mode 100644 packages/svm/programs/settler/src/instructions/initialize.rs create mode 100644 packages/svm/programs/settler/src/instructions/mod.rs create mode 100644 packages/svm/programs/settler/src/instructions/set_paused_state.rs create mode 100644 packages/svm/programs/settler/src/state/fulfilled_intent.rs create mode 100644 packages/svm/programs/settler/src/state/intent.rs create mode 100644 packages/svm/programs/settler/src/state/mod.rs create mode 100644 packages/svm/programs/settler/src/state/settler_settings.rs create mode 100644 packages/svm/programs/settler/src/types/intent_event.rs create mode 100644 packages/svm/programs/settler/src/types/max_fee.rs create mode 100644 packages/svm/programs/settler/src/types/mod.rs create mode 100644 packages/svm/programs/settler/src/types/op_type.rs create mode 100644 packages/svm/programs/settler/src/utils/math.rs create mode 100644 packages/svm/programs/settler/src/utils/mod.rs create mode 100644 packages/svm/programs/settler/src/utils/sigs.rs create mode 100644 packages/svm/sdks/settler/Settler.ts create mode 100644 packages/svm/sdks/settler/types.ts create mode 100644 packages/svm/tests/helpers/settler-helpers.ts diff --git a/packages/svm/programs/settler/Cargo.toml b/packages/svm/programs/settler/Cargo.toml index 899599c..e20a186 100644 --- a/packages/svm/programs/settler/Cargo.toml +++ b/packages/svm/programs/settler/Cargo.toml @@ -15,6 +15,12 @@ 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/settler/src/constants.rs b/packages/svm/programs/settler/src/constants.rs new file mode 100644 index 0000000..756ec67 --- /dev/null +++ b/packages/svm/programs/settler/src/constants.rs @@ -0,0 +1,4 @@ +pub const DEPLOYER_KEY: &'static str = env!( + "DEPLOYER_KEY", + "Please set the DEPLOYER_KEY env variable before compiling." +); diff --git a/packages/svm/programs/settler/src/errors.rs b/packages/svm/programs/settler/src/errors.rs new file mode 100644 index 0000000..ae62bae --- /dev/null +++ b/packages/svm/programs/settler/src/errors.rs @@ -0,0 +1,79 @@ +use anchor_lang::prelude::*; + +#[error_code] +pub enum SettlerError { + #[msg("Only Deployer can call this instruction")] + OnlyDeployer, + + #[msg("Only a whitelisted solver can call this instruction")] + OnlySolver, + + #[msg("Provided Axia address is not whitelisted")] + AxiaNotWhitelisted, + + #[msg("Only a whitelisted validator can call this instruction")] + OnlyValidator, + + #[msg("No max fees provided")] + NoMaxFees, + + #[msg("Validator is not whitelisted")] + ValidatorNotWhitelisted, + + #[msg("Signer must be intent creator")] + IncorrectIntentCreator, + + #[msg("Signer must be proposal creator")] + IncorrectProposalCreator, + + #[msg("Intent is already final")] + IntentIsFinal, + + #[msg("Intent is not final")] + IntentIsNotFinal, + + #[msg("Proposal is already final")] + ProposalIsFinal, + + #[msg("Proposal is not final")] + ProposalIsNotFinal, + + #[msg("Intent not yet expired. Please wait for the deadline to pass")] + IntentNotYetExpired, + + #[msg("Intent has already expired")] + IntentIsExpired, + + #[msg("Proposal not yet expired. Please wait for the deadline to pass")] + ProposalNotYetExpired, + + #[msg("Proposal has already expired")] + ProposalIsExpired, + + #[msg("Deadline must be in the future")] + DeadlineIsInThePast, + + #[msg("Proposal deadline can't be after the Intent's deadline")] + ProposalDeadlineExceedsIntentDeadline, + + #[msg("Intent has insufficient validations")] + InsufficientIntentValidations, + + #[msg("Signature verification failed")] + SigVerificationFailed, + + #[msg("Incorrect intent for proposal")] + IncorrectIntentForProposal, + + #[msg("Proposal is not signed by Axia")] + ProposalIsNotSigned, + + #[msg("Invalid fee mint")] + InvalidFeeMint, + + #[msg("Fee amount exceeds max fee")] + FeeAmountExceedsMaxFee, + + #[msg("Math Error")] + MathError, +} diff --git a/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs b/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs new file mode 100644 index 0000000..bd0b2cd --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs @@ -0,0 +1,27 @@ +use anchor_lang::prelude::*; + +use crate::{errors::SettlerError, state::Intent}; + +#[derive(Accounts)] +pub struct ClaimStaleIntent<'info> { + #[account(mut)] + pub intent_creator: Signer<'info>, + + #[account( + mut, + close = intent_creator, + has_one = intent_creator @ SettlerError::IncorrectIntentCreator, + )] + pub intent: Box>, +} + +pub fn claim_stale_intent(ctx: Context) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + + require!( + ctx.accounts.intent.deadline < now, + SettlerError::IntentNotYetExpired + ); + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/create_intent.rs b/packages/svm/programs/settler/src/instructions/create_intent.rs new file mode 100644 index 0000000..aa62b6d --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/create_intent.rs @@ -0,0 +1,87 @@ +use anchor_lang::prelude::*; + +use crate::{ + errors::SettlerError, + state::Intent, + types::{IntentEvent, OpType, TokenFee}, + whitelist::{ + accounts::EntityRegistry, + types::{EntityType, WhitelistStatus}, + }, +}; + +#[derive(Accounts)] +// TODO: can we optimize this deser? we just need the three Vec for their length +#[instruction(intent_hash: [u8; 32], data: Vec, max_fees: Vec, events: Vec, min_validations: u16)] +pub struct CreateIntent<'info> { + #[account(mut)] + pub solver: Signer<'info>, + + #[account( + seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], + bump = solver_registry.bump, + seeds::program = crate::whitelist::ID, + constraint = + solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + )] + pub solver_registry: Box>, + + #[account( + init, + seeds = [b"intent", intent_hash.as_ref()], + bump, + payer = solver, + space = Intent::total_size(data.len(), max_fees.len(), &events, min_validations)? + )] + // TODO: change to AccountLoader? + // TODO: init within the handler body to save compute? + pub intent: Box>, + + #[account( + seeds = [b"fulfilled-intent", intent_hash.as_ref()], + bump + )] + /// This PDA must be uninitialized + pub fulfilled_intent: SystemAccount<'info>, + + pub system_program: Program<'info, System>, +} + +pub fn create_intent( + ctx: Context, + intent_hash: [u8; 32], + data: Vec, + max_fees: Vec, + events: Vec, + min_validations: u16, + op: OpType, + user: Pubkey, + nonce: [u8; 32], + deadline: u64, + is_final: bool, +) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + require!(deadline > now, SettlerError::DeadlineIsInThePast); + require!(max_fees.len() > 0, SettlerError::NoMaxFees); + + // TODO: check hash + + let intent = &mut ctx.accounts.intent; + + intent.op = op; + intent.user = user; + intent.intent_creator = ctx.accounts.solver.key(); + intent.intent_hash = intent_hash; + intent.nonce = nonce; + intent.deadline = deadline; + intent.min_validations = min_validations; + intent.validations = 0; + intent.is_final = is_final; + intent.intent_data = data; + intent.max_fees = max_fees; + intent.events = events; + intent.validators = vec![]; + intent.bump = ctx.bumps.intent; + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/extend_intent.rs b/packages/svm/programs/settler/src/instructions/extend_intent.rs new file mode 100644 index 0000000..f14c596 --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/extend_intent.rs @@ -0,0 +1,55 @@ +use anchor_lang::prelude::*; + +use crate::{ + errors::SettlerError, + state::Intent, + types::{IntentEvent, TokenFee}, +}; + +#[derive(Accounts)] +#[instruction(more_data: Option>, more_max_fees: Option>, more_events: Option>)] +pub struct ExtendIntent<'info> { + #[account(mut)] + pub intent_creator: Signer<'info>, + + #[account( + mut, + has_one = intent_creator @ SettlerError::IncorrectIntentCreator, + constraint = !intent.is_final @ SettlerError::IntentIsFinal, + realloc = + Intent::extended_size(intent.to_account_info().data_len(), &more_data, &more_max_fees, &more_events)?, + realloc::payer = intent_creator, + realloc::zero = true + )] + pub intent: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn extend_intent( + ctx: Context, + more_data: Option>, + more_max_fees: Option>, + more_events: Option>, + finalize: bool, +) -> Result<()> { + let intent = &mut ctx.accounts.intent; + + if let Some(_more_data) = more_data { + intent.intent_data.extend_from_slice(&_more_data); + } + + if let Some(_more_max_fees) = more_max_fees { + intent.max_fees.extend_from_slice(&_more_max_fees); + } + + if let Some(_more_events) = more_events { + intent.events.extend_from_slice(&_more_events); + } + + if finalize { + intent.is_final = true; + } + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/initialize.rs b/packages/svm/programs/settler/src/instructions/initialize.rs new file mode 100644 index 0000000..7aa463c --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/initialize.rs @@ -0,0 +1,37 @@ +use anchor_lang::prelude::*; +use std::str::FromStr; + +use crate::{constants::DEPLOYER_KEY, errors::SettlerError, state::SettlerSettings}; + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(mut)] + pub deployer: Signer<'info>, + + #[account( + init, + seeds = [b"settler-settings"], + bump, + payer = deployer, + space = 8 + SettlerSettings::INIT_SPACE, + )] + pub settler_settings: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn initialize(ctx: Context) -> Result<()> { + require_keys_eq!( + ctx.accounts.deployer.key(), + Pubkey::from_str(DEPLOYER_KEY).unwrap(), + SettlerError::OnlyDeployer, + ); + + let settler_settings = &mut ctx.accounts.settler_settings; + + settler_settings.whitelist_program = crate::whitelist::ID; + settler_settings.is_paused = false; + settler_settings.bump = ctx.bumps.settler_settings; + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/mod.rs b/packages/svm/programs/settler/src/instructions/mod.rs new file mode 100644 index 0000000..f335e7a --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/mod.rs @@ -0,0 +1,25 @@ +pub mod add_axia_sig; +pub mod add_instructions_to_proposal; +pub mod add_validator_sig; +pub mod change_whitelist_program; +pub mod claim_stale_intent; +pub mod claim_stale_proposal; +pub mod create_intent; +pub mod create_proposal; +pub mod execute_proposal; +pub mod extend_intent; +pub mod initialize; +pub mod set_paused_state; + +pub use add_axia_sig::*; +pub use add_instructions_to_proposal::*; +pub use add_validator_sig::*; +pub use change_whitelist_program::*; +pub use claim_stale_intent::*; +pub use claim_stale_proposal::*; +pub use create_intent::*; +pub use create_proposal::*; +pub use execute_proposal::*; +pub use extend_intent::*; +pub use initialize::*; +pub use set_paused_state::*; diff --git a/packages/svm/programs/settler/src/instructions/set_paused_state.rs b/packages/svm/programs/settler/src/instructions/set_paused_state.rs new file mode 100644 index 0000000..ca8b52e --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/set_paused_state.rs @@ -0,0 +1,46 @@ +use anchor_lang::prelude::*; + +use crate::state::SettlerSettings; + +#[derive(Accounts)] +pub struct SetPausedState<'info> { + #[account(mut)] + pub admin: Signer<'info>, + + #[account( + seeds = [b"global-settings"], + bump = whitelist_program_global_settings.bump, + seeds::program = settler_settings.whitelist_program, + has_one = admin @ crate::whitelist::errors::ProgramError::OnlyAdmin + )] + pub whitelist_program_global_settings: + Box>, + + #[account( + mut, + seeds = [b"settler-settings"], + bump = settler_settings.bump + )] + pub settler_settings: Box>, +} + +pub fn set_paused_state(ctx: Context, is_paused: bool) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + + ctx.accounts.settler_settings.is_paused = is_paused; + + emit!(SetPausedStateEvent { + changed_at: now, + changed_by: ctx.accounts.admin.key(), + is_paused, + }); + + Ok(()) +} + +#[event] +pub struct SetPausedStateEvent { + changed_at: u64, + changed_by: Pubkey, + is_paused: bool, +} diff --git a/packages/svm/programs/settler/src/lib.rs b/packages/svm/programs/settler/src/lib.rs index 59630fe..77058f3 100644 --- a/packages/svm/programs/settler/src/lib.rs +++ b/packages/svm/programs/settler/src/lib.rs @@ -1,18 +1,108 @@ -#![allow(unexpected_cfgs)] - use anchor_lang::prelude::*; declare_id!("HbNt35Ng8aM4NUy39evpCQqXEC4Nmaq16ewY8dyNF6NF"); +declare_program!(whitelist); + +pub mod constants; +pub mod errors; +pub mod instructions; +pub mod state; +pub mod types; +pub mod utils; + +use crate::{instructions::*, state::*, types::*}; #[program] pub mod settler { use super::*; + pub fn add_axia_sig(ctx: Context) -> Result<()> { + instructions::add_axia_sig(ctx) + } + + pub fn add_instructions_to_proposal( + ctx: Context, + more_instructions: Vec, + finalize: bool, + ) -> Result<()> { + instructions::add_instructions_to_proposal(ctx, more_instructions, finalize) + } + + pub fn add_validator_sig(ctx: Context) -> Result<()> { + instructions::add_validator_sig(ctx) + } + + pub fn change_whitelist_program(ctx: Context) -> Result<()> { + instructions::change_whitelist_program(ctx) + } + + pub fn claim_stale_intent(ctx: Context) -> Result<()> { + instructions::claim_stale_intent(ctx) + } + + pub fn claim_stale_proposal<'info>( + ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposal<'info>>, + ) -> Result<()> { + instructions::claim_stale_proposal(ctx) + } + + pub fn create_intent( + ctx: Context, + intent_hash: [u8; 32], + data: Vec, + max_fees: Vec, + events: Vec, + min_validations: u16, + op: OpType, + user: Pubkey, + nonce: [u8; 32], + deadline: u64, + is_final: bool, + ) -> Result<()> { + instructions::create_intent( + ctx, + intent_hash, + data, + max_fees, + events, + min_validations, + op, + user, + nonce, + deadline, + is_final, + ) + } + + pub fn create_proposal( + ctx: Context, + instructions: Vec, + fees: Vec, + deadline: u64, + is_final: bool, + ) -> Result<()> { + instructions::create_proposal(ctx, instructions, fees, deadline, is_final) + } + + pub fn execute_proposal(ctx: Context) -> Result<()> { + instructions::execute_proposal(ctx) + } + + pub fn extend_intent( + ctx: Context, + more_data: Option>, + more_max_fees: Option>, + more_events: Option>, + finalize: bool, + ) -> Result<()> { + instructions::extend_intent(ctx, more_data, more_max_fees, more_events, finalize) + } + pub fn initialize(ctx: Context) -> Result<()> { - msg!("Greetings from: {:?}", ctx.program_id); - Ok(()) + instructions::initialize(ctx) } -} -#[derive(Accounts)] -pub struct Initialize {} + pub fn set_paused_state(ctx: Context, is_paused: bool) -> Result<()> { + instructions::set_paused_state(ctx, is_paused) + } +} diff --git a/packages/svm/programs/settler/src/state/fulfilled_intent.rs b/packages/svm/programs/settler/src/state/fulfilled_intent.rs new file mode 100644 index 0000000..812a6e2 --- /dev/null +++ b/packages/svm/programs/settler/src/state/fulfilled_intent.rs @@ -0,0 +1,7 @@ +use anchor_lang::prelude::*; + +#[account] +#[derive(InitSpace)] +pub struct FulfilledIntent { + pub fulfilled_at: u64, +} diff --git a/packages/svm/programs/settler/src/state/intent.rs b/packages/svm/programs/settler/src/state/intent.rs new file mode 100644 index 0000000..087eb01 --- /dev/null +++ b/packages/svm/programs/settler/src/state/intent.rs @@ -0,0 +1,96 @@ +use anchor_lang::prelude::*; + +use crate::{ + types::{IntentEvent, OpType, TokenFee}, + utils::{add, mul, sub}, +}; + +#[account] +pub struct Intent { + pub op: OpType, + pub user: Pubkey, + pub intent_creator: Pubkey, + pub intent_hash: [u8; 32], + pub nonce: [u8; 32], + pub deadline: u64, + pub min_validations: u16, + pub validations: u16, + pub is_final: bool, + pub validators: Vec, // TODO: how to store more efficiently? + pub intent_data: Vec, + pub max_fees: Vec, + pub events: Vec, + pub bump: u8, +} + +impl Intent { + /// Doesn't take into account size of variable fields + pub const BASE_LEN: usize = + 1 + // op + 32 + // user + 32 + // intent_creator + 32 + // intent_hash + 32 + // nonce + 8 + // deadline + 2 + // min_validations + 2 + // validations + 1 + // is_final + 1 // bump + ; + + pub fn total_size( + data_len: usize, + max_fees_len: usize, + events: &[IntentEvent], + min_validations: u16, + ) -> Result { + let size = add(8, Intent::BASE_LEN)?; + let size = add(size, Intent::data_size(data_len)?)?; + let size = add(size, Intent::max_fees_size(max_fees_len)?)?; + let size = add(size, Intent::events_size(events)?)?; + let size = add(size, Intent::validators_size(min_validations)?)?; + Ok(size) + } + + pub fn data_size(len: usize) -> Result { + add(4, len) + } + + pub fn max_fees_size(len: usize) -> Result { + add(4, mul(TokenFee::INIT_SPACE, len)?) + } + + pub fn events_size(events: &[IntentEvent]) -> Result { + let sum = events + .iter() + .try_fold(0usize, |acc, e| add(acc, e.size()))?; + add(4, sum) + } + + pub fn validators_size(min_validations: u16) -> Result { + add(4, mul(min_validations as usize, 32)?) + } + + pub fn extended_size( + size: usize, + more_data: &Option>, + more_max_fees: &Option>, + more_events: &Option>, + ) -> Result { + let mut size = size; + + if let Some(v) = more_data { + size = add(size, sub(Intent::data_size(v.len())?, 4)?)?; + } + + if let Some(v) = more_max_fees { + size = add(size, sub(Intent::max_fees_size(v.len())?, 4)?)?; + } + + if let Some(v) = more_events { + size = add(size, sub(Intent::events_size(v)?, 4)?)?; + } + + Ok(size) + } +} diff --git a/packages/svm/programs/settler/src/state/mod.rs b/packages/svm/programs/settler/src/state/mod.rs new file mode 100644 index 0000000..fb0b3ba --- /dev/null +++ b/packages/svm/programs/settler/src/state/mod.rs @@ -0,0 +1,9 @@ +pub mod fulfilled_intent; +pub mod intent; +pub mod proposal; +pub mod settler_settings; + +pub use fulfilled_intent::*; +pub use intent::*; +pub use proposal::*; +pub use settler_settings::*; diff --git a/packages/svm/programs/settler/src/state/settler_settings.rs b/packages/svm/programs/settler/src/state/settler_settings.rs new file mode 100644 index 0000000..01a3942 --- /dev/null +++ b/packages/svm/programs/settler/src/state/settler_settings.rs @@ -0,0 +1,9 @@ +use anchor_lang::prelude::*; + +#[account] +#[derive(InitSpace)] +pub struct SettlerSettings { + pub whitelist_program: Pubkey, + pub is_paused: bool, + pub bump: u8, +} diff --git a/packages/svm/programs/settler/src/types/intent_event.rs b/packages/svm/programs/settler/src/types/intent_event.rs new file mode 100644 index 0000000..0eb00c4 --- /dev/null +++ b/packages/svm/programs/settler/src/types/intent_event.rs @@ -0,0 +1,13 @@ +use anchor_lang::prelude::*; + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct IntentEvent { + pub topic: [u8; 32], + pub data: Vec, +} + +impl IntentEvent { + pub fn size(&self) -> usize { + 32 + 4 + self.data.len() + } +} diff --git a/packages/svm/programs/settler/src/types/max_fee.rs b/packages/svm/programs/settler/src/types/max_fee.rs new file mode 100644 index 0000000..dc072b9 --- /dev/null +++ b/packages/svm/programs/settler/src/types/max_fee.rs @@ -0,0 +1,11 @@ +use anchor_lang::prelude::*; + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct TokenFee { + pub mint: Pubkey, + pub amount: u64, +} + +impl Space for TokenFee { + const INIT_SPACE: usize = 32 + 8; +} diff --git a/packages/svm/programs/settler/src/types/mod.rs b/packages/svm/programs/settler/src/types/mod.rs new file mode 100644 index 0000000..76aa716 --- /dev/null +++ b/packages/svm/programs/settler/src/types/mod.rs @@ -0,0 +1,7 @@ +pub mod intent_event; +pub mod max_fee; +pub mod op_type; + +pub use intent_event::*; +pub use max_fee::*; +pub use op_type::*; diff --git a/packages/svm/programs/settler/src/types/op_type.rs b/packages/svm/programs/settler/src/types/op_type.rs new file mode 100644 index 0000000..620ab7e --- /dev/null +++ b/packages/svm/programs/settler/src/types/op_type.rs @@ -0,0 +1,9 @@ +use anchor_lang::prelude::*; + +#[repr(u8)] +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub enum OpType { + Swap = 1, + Transfer = 2, + Call = 3, +} diff --git a/packages/svm/programs/settler/src/utils/math.rs b/packages/svm/programs/settler/src/utils/math.rs new file mode 100644 index 0000000..e48c97a --- /dev/null +++ b/packages/svm/programs/settler/src/utils/math.rs @@ -0,0 +1,17 @@ +use crate::errors::SettlerError; +use anchor_lang::prelude::*; + +#[inline] +pub fn add(a: usize, b: usize) -> Result { + Ok(a.checked_add(b).ok_or(SettlerError::MathError)?) +} + +#[inline] +pub fn sub(a: usize, b: usize) -> Result { + Ok(a.checked_sub(b).ok_or(SettlerError::MathError)?) +} + +#[inline] +pub fn mul(a: usize, b: usize) -> Result { + Ok(a.checked_mul(b).ok_or(SettlerError::MathError)?) +} diff --git a/packages/svm/programs/settler/src/utils/mod.rs b/packages/svm/programs/settler/src/utils/mod.rs new file mode 100644 index 0000000..bd9ec41 --- /dev/null +++ b/packages/svm/programs/settler/src/utils/mod.rs @@ -0,0 +1,5 @@ +pub mod math; +pub mod sigs; + +pub use math::*; +pub use sigs::*; diff --git a/packages/svm/programs/settler/src/utils/sigs.rs b/packages/svm/programs/settler/src/utils/sigs.rs new file mode 100644 index 0000000..a5ce59b --- /dev/null +++ b/packages/svm/programs/settler/src/utils/sigs.rs @@ -0,0 +1,75 @@ +use anchor_lang::prelude::{instruction::Instruction, *}; + +use crate::errors::SettlerError; + +pub fn check_ed25519_ix(ix: &Instruction) -> Result<()> { + if ix.program_id.to_string() != "Ed25519SigVerify111111111111111111111111111" + || ix.accounts.len() != 0 + { + return err!(SettlerError::SigVerificationFailed); + } + + Ok(()) +} + +pub struct Ed25519Args<'a> { + pub pubkey: &'a [u8; 32], + pub sig: &'a [u8; 64], + pub msg: &'a [u8], +} + +pub fn get_args_from_ed25519_ix_data(data: &[u8]) -> Result> { + if data.len() < 112 { + return err!(SettlerError::SigVerificationFailed); + } + + // Header + let num_signatures = &[data[0]]; + let padding = &[data[1]]; + let signature_offset = &data[2..=3]; + let signature_instruction_index = &data[4..=5]; + let public_key_offset = &data[6..=7]; + let public_key_instruction_index = &data[8..=9]; + let message_data_offset = &data[10..=11]; + let message_data_size = &data[12..=13]; + let message_instruction_index = &data[14..=15]; + + // Data + let pubkey = &data[16..16 + 32]; + let sig = &data[48..48 + 64]; + let msg = &data[112..]; + + // Expected values + let exp_public_key_offset: u16 = 16; // 2*u8 + 7*u16 + let exp_signature_offset: u16 = exp_public_key_offset + 32_u16; + let exp_message_data_offset: u16 = exp_signature_offset + 64_u16; + let exp_num_signatures: u8 = 1; + let exp_message_data_size: u16 = msg + .len() + .try_into() + .map_err(|_| SettlerError::SigVerificationFailed)?; + + // Header + if num_signatures != &exp_num_signatures.to_le_bytes() + || padding != &[0] + || signature_offset != &exp_signature_offset.to_le_bytes() + || signature_instruction_index != &u16::MAX.to_le_bytes() + || public_key_offset != &exp_public_key_offset.to_le_bytes() + || public_key_instruction_index != &u16::MAX.to_le_bytes() + || message_data_offset != &exp_message_data_offset.to_le_bytes() + || message_data_size != &exp_message_data_size.to_le_bytes() + || message_instruction_index != &u16::MAX.to_le_bytes() + { + return err!(SettlerError::SigVerificationFailed); + } + + Ok(Ed25519Args { + pubkey: pubkey + .try_into() + .map_err(|_| SettlerError::SigVerificationFailed)?, + sig: sig + .try_into() + .map_err(|_| SettlerError::SigVerificationFailed)?, + msg, + }) +} diff --git a/packages/svm/sdks/settler/Settler.ts b/packages/svm/sdks/settler/Settler.ts new file mode 100644 index 0000000..2d0978b --- /dev/null +++ b/packages/svm/sdks/settler/Settler.ts @@ -0,0 +1,317 @@ +import { BN, IdlTypes, Program, Provider, web3 } from '@coral-xyz/anchor' + +import * as SettlerIDL from '../../target/idl/settler.json' +import * as WhitelistIDL from '../../target/idl/whitelist.json' +import { Settler } from '../../target/types/settler' +import { EntityType } from '../whitelist/Whitelist' +import { + CreateIntentParams, + ExtendIntentParams, + IntentEvent, + OpType, + ProposalInstruction, + ProposalInstructionAccountMeta, + TokenFee, +} from './types' + +type TokenFeeAnchor = { + mint: web3.PublicKey + amount: BN +} + +type IntentEventAnchor = { + topic: number[] + data: Buffer +} + +type ProposalInstructionAnchor = { + programId: web3.PublicKey + accounts: ProposalInstructionAccountMeta[] + data: Buffer +} + +export default class SettlerSDK { + protected program: Program + + constructor(provider: Provider) { + this.program = new Program(SettlerIDL, provider) + } + + async initializeIx(): Promise { + const ix = await this.program.methods.initialize().instruction() + return ix + } + + async createIntentIx( + intentHashHex: string, + params: CreateIntentParams, + isFinal = true + ): Promise { + const { op, user, nonceHex, deadline, minValidations, dataHex, maxFees, eventsHex } = params + + const intentHash = this.parseIntentHashHex(intentHashHex) + const nonce = this.parseIntentNonceHex(nonceHex) + const data = Buffer.from(dataHex, 'hex') + const maxFeesBn = this.parseTokenFees(maxFees) + const events = this.parseIntentEventsHex(eventsHex) + + const ix = await this.program.methods + .createIntent( + intentHash, + data, + maxFeesBn, + events, + minValidations, + this.opTypeToAnchorEnum(op), + user, + nonce, + new BN(deadline), + isFinal + ) + .accountsPartial({ + solver: this.getSignerKey(), + solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), + }) + .instruction() + + return ix + } + + async extendIntentIx( + intentHashHex: string, + params: ExtendIntentParams, + finalize = true + ): Promise { + const { moreDataHex = '', moreMaxFees = [], moreEventsHex = [] } = params + + const moreData = Buffer.from(moreDataHex, 'hex') + const moreMaxFeesBn = this.parseTokenFees(moreMaxFees) + const moreEvents = this.parseIntentEventsHex(moreEventsHex) + + const ix = await this.program.methods + .extendIntent(moreData, moreMaxFeesBn, moreEvents, finalize) + .accountsPartial({ + intentCreator: this.getSignerKey(), + intent: this.getIntentKey(intentHashHex), + }) + .instruction() + + return ix + } + + async claimStaleIntentIx(intentHashHex: string): Promise { + const ix = await this.program.methods + .claimStaleIntent() + .accountsPartial({ + intentCreator: this.getSignerKey(), + intent: this.getIntentKey(intentHashHex), + }) + .instruction() + + return ix + } + + async createProposalIx( + intentHashHex: string, + instructions: ProposalInstruction[], + fees: TokenFee[], + deadline: number, + isFinal = true + ): Promise { + const parsedInstructions = this.parseProposalInstructions(instructions) + const parsedFees = this.parseTokenFees(fees) + + const ix = await this.program.methods + .createProposal(parsedInstructions, parsedFees, new BN(deadline), isFinal) + .accountsPartial({ + solver: this.getSignerKey(), + solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), + intent: this.getIntentKey(intentHashHex), + fulfilledIntent: this.getFulfilledIntentKey(intentHashHex), + }) + .instruction() + + return ix + } + + async addInstructionsToProposalIx( + intentHashHex: string, + moreInstructions: ProposalInstruction[], + finalize = true, + solverPubkey?: web3.PublicKey + ): Promise { + const parsedInstructions = this.parseProposalInstructions(moreInstructions) + const solver = solverPubkey || this.getSignerKey() + + const ix = await this.program.methods + .addInstructionsToProposal(parsedInstructions, finalize) + .accountsPartial({ + proposalCreator: this.getSignerKey(), + proposal: this.getProposalKey(intentHashHex, solver), + }) + .instruction() + + return ix + } + + async claimStaleProposalIx( + intentHashesHex: string[], + solverPubkey?: web3.PublicKey + ): Promise { + const ix = await this.program.methods + .claimStaleProposal() + .accountsPartial({ + proposalCreator: this.getSignerKey(), + }) + .remainingAccounts( + intentHashesHex.map((intentHashHex) => ({ + pubkey: this.getProposalKey(intentHashHex, solverPubkey), + isWritable: true, + isSigner: false, + })) + ) + .instruction() + + return ix + } + + async addValidatorSigIxs( + intent: web3.PublicKey, + intentHash: Buffer, + validator: web3.PublicKey, + signature: number[] + ): Promise { + const ed25519Ix = web3.Ed25519Program.createInstructionWithPublicKey({ + message: intentHash, + publicKey: validator.toBuffer(), + signature: Buffer.from(signature), + }) + + const ix = await this.program.methods + .addValidatorSig() + .accountsPartial({ + solver: this.getSignerKey(), + solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), + intent, + validatorRegistry: this.getEntityRegistryPubkey(EntityType.Validator, validator), + ixSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + return [ed25519Ix, ix] + } + + async addAxiaSigIxs( + proposal: web3.PublicKey, + axia: web3.PublicKey, + signature: number[] + ): Promise { + const ed25519Ix = web3.Ed25519Program.createInstructionWithPublicKey({ + message: proposal.toBuffer(), + publicKey: axia.toBuffer(), + signature: Buffer.from(signature), + }) + + const ix = await this.program.methods + .addAxiaSig() + .accountsPartial({ + solver: this.getSignerKey(), + solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), + proposal, + axiaRegistry: this.getEntityRegistryPubkey(EntityType.Axia, axia), + ixSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + return [ed25519Ix, ix] + } + + getSettlerSettingsPubkey(): web3.PublicKey { + return web3.PublicKey.findProgramAddressSync([Buffer.from('settler-settings')], this.program.programId)[0] + } + + getIntentKey(intentHashHex: string): web3.PublicKey { + const intentHash = Buffer.from(intentHashHex, 'hex') + if (intentHash.length != 32) throw new Error(`Intent hash must be 32 bytes: ${intentHashHex}`) + + return web3.PublicKey.findProgramAddressSync([Buffer.from('intent'), intentHash], this.program.programId)[0] + } + + getFulfilledIntentKey(intentHashHex: string): web3.PublicKey { + const intentHash = Buffer.from(intentHashHex, 'hex') + if (intentHash.length != 32) throw new Error(`Intent hash must be 32 bytes: ${intentHashHex}`) + + return web3.PublicKey.findProgramAddressSync( + [Buffer.from('fulfilled-intent'), intentHash], + this.program.programId + )[0] + } + + getProposalKey(intentHashHex: string, solverPubkey?: web3.PublicKey): web3.PublicKey { + const intentHash = Buffer.from(intentHashHex, 'hex') + if (intentHash.length != 32) throw new Error(`Intent hash must be 32 bytes: ${intentHashHex}`) + + const intentKey = this.getIntentKey(intentHashHex) + const solver = solverPubkey || this.getSignerKey() + + return web3.PublicKey.findProgramAddressSync( + [Buffer.from('proposal'), intentKey.toBuffer(), solver.toBuffer()], + 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()], + new web3.PublicKey(WhitelistIDL.address) + )[0] + } + + getSignerKey(): web3.PublicKey { + if (!this.program.provider.wallet) throw new Error('Must set program provider wallet') + return this.program.provider.wallet?.publicKey + } + + opTypeToAnchorEnum(op: OpType): IdlTypes['opType'] { + if (op === OpType.Transfer) return { transfer: {} } + if (op === OpType.Swap) return { swap: {} } + if (op === OpType.Call) return { call: {} } + + throw new Error(`Unsupported op ${op}`) + } + + private parseIntentHashHex(intentHashHex: string): number[] { + const intentHash = Buffer.from(intentHashHex, 'hex') + if (intentHash.length != 32) throw new Error(`Intent hash must be 32 bytes: ${intentHashHex}`) + return Array.from(intentHash) + } + + private parseIntentNonceHex(nonceHex: string): number[] { + const nonce = Buffer.from(nonceHex, 'hex') + if (nonce.length != 32) throw new Error(`Nonce must be 32 bytes: ${nonceHex}`) + return Array.from(nonce) + } + + private parseIntentEventsHex(eventsHex: IntentEvent[]): IntentEventAnchor[] { + const events = eventsHex.map((eventHex) => ({ + topic: Array.from(Uint8Array.from(Buffer.from(eventHex.topicHex, 'hex'))), + data: Buffer.from(eventHex.dataHex, 'hex'), + })) + if (events.some((event) => event.topic.length != 32)) throw new Error(`Event topics must be 32 bytes`) + return events + } + + private parseTokenFees(tokenFees: TokenFee[]): TokenFeeAnchor[] { + return tokenFees.map((tokenFee) => ({ + ...tokenFee, + amount: new BN(tokenFee.amount), + })) + } + + private parseProposalInstructions(instructions: ProposalInstruction[]): ProposalInstructionAnchor[] { + return instructions.map((instruction) => ({ + ...instruction, + data: typeof instruction.data === 'string' ? Buffer.from(instruction.data, 'hex') : instruction.data, + })) + } +} diff --git a/packages/svm/sdks/settler/types.ts b/packages/svm/sdks/settler/types.ts new file mode 100644 index 0000000..f3ac6eb --- /dev/null +++ b/packages/svm/sdks/settler/types.ts @@ -0,0 +1,49 @@ +import { web3 } from '@coral-xyz/anchor' + +export type TokenFee = { + mint: web3.PublicKey + amount: number +} + +export type IntentEvent = { + topicHex: string + dataHex: string +} + +export enum OpType { + // eslint-disable-next-line no-unused-vars + Transfer = 1, + // eslint-disable-next-line no-unused-vars + Swap = 2, + // eslint-disable-next-line no-unused-vars + Call = 3, +} + +export type CreateIntentParams = { + op: OpType + user: web3.PublicKey + nonceHex: string + deadline: number + minValidations: number + dataHex: string + maxFees: TokenFee[] + eventsHex: IntentEvent[] +} + +export type ExtendIntentParams = { + moreDataHex?: string + moreMaxFees?: TokenFee[] + moreEventsHex?: IntentEvent[] +} + +export type ProposalInstructionAccountMeta = { + pubkey: web3.PublicKey + isSigner: boolean + isWritable: boolean +} + +export type ProposalInstruction = { + programId: web3.PublicKey + accounts: ProposalInstructionAccountMeta[] + data: Buffer | string +} diff --git a/packages/svm/tests/helpers/settler-helpers.ts b/packages/svm/tests/helpers/settler-helpers.ts new file mode 100644 index 0000000..7435e83 --- /dev/null +++ b/packages/svm/tests/helpers/settler-helpers.ts @@ -0,0 +1,231 @@ +import { Program } from '@coral-xyz/anchor' +import { signAsync } from '@noble/ed25519' +import { Keypair, PublicKey } from '@solana/web3.js' +import { LiteSVMProvider } from 'anchor-litesvm' +import { expect } from 'chai' +import { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm' + +import SettlerSDK from '../../sdks/settler/Settler' +import { CreateIntentParams, IntentEvent, OpType, ProposalInstruction, TokenFee } from '../../sdks/settler/types' +import WhitelistSDK, { EntityType, WhitelistStatus } from '../../sdks/whitelist/Whitelist' +import { Settler } from '../../target/types/settler' +import { makeTxSignAndSend } from '../utils' +import { + DEFAULT_DATA_HEX, + DEFAULT_EVENT_DATA_HEX, + DEFAULT_MAX_FEE, + DEFAULT_MIN_VALIDATIONS, + DEFAULT_TOPIC_HEX, + INTENT_DEADLINE_OFFSET, + INTENT_HASH_LENGTH, + NONCE_LENGTH, +} from './constants' + +/** + * Generate a random 32-byte hex string for intent hash + */ +export function generateIntentHash(): string { + return Buffer.from(Array.from({ length: INTENT_HASH_LENGTH }, () => Math.floor(Math.random() * 256))).toString('hex') +} + +/** + * Generate a random 32-byte hex string for nonce + */ +export function generateNonce(): string { + return Buffer.from(Array.from({ length: NONCE_LENGTH }, () => Math.floor(Math.random() * 256))).toString('hex') +} + +/** + * Create a test intent with configurable parameters + */ +export async function createTestIntent( + solverSdk: SettlerSDK, + solverProvider: LiteSVMProvider, + options: { + intentHash?: string + nonce?: string + user?: PublicKey + deadline?: number + op?: OpType + minValidations?: number + dataHex?: string + maxFees?: TokenFee[] + eventsHex?: IntentEvent[] + isFinal?: boolean + } = {} +): Promise { + const intentHash = options.intentHash || generateIntentHash() + const nonce = options.nonce || generateNonce() + const user = options.user || Keypair.generate().publicKey + const client = solverProvider.client + const now = Number(client.getClock().unixTimestamp) + const deadline = options.deadline ?? now + INTENT_DEADLINE_OFFSET + + const params: CreateIntentParams = { + op: options.op || OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: options.minValidations ?? DEFAULT_MIN_VALIDATIONS, + dataHex: options.dataHex ?? DEFAULT_DATA_HEX, + maxFees: options.maxFees || [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: options.eventsHex || [ + { + topicHex: DEFAULT_TOPIC_HEX, + dataHex: DEFAULT_EVENT_DATA_HEX, + }, + ], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, options.isFinal ?? false) + const res = await makeTxSignAndSend(solverProvider, ix) + if (res instanceof FailedTransactionMetadata) { + throw new Error(`Failed to create intent: ${res.toString()}`) + } + return intentHash +} + +/** + * Create a validated intent (with validations set to meet min_validations requirement) + */ +export async function createValidatedIntent( + solverSdk: SettlerSDK, + solverProvider: LiteSVMProvider, + client: LiteSVM, + options: { + intentHash?: string + minValidations?: number + isFinal?: boolean + deadline?: number + } = {} +): Promise { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + ...options, + isFinal: options.isFinal ?? true, + }) + + // Set validations to meet min_validations requirement + const intentKey = solverSdk.getIntentKey(intentHash) + const intentAccount = client.getAccount(intentKey) + if (intentAccount) { + const intentData = Buffer.from(intentAccount.data) + // validations is at offset: 8 (disc) + 1 (op) + 32 (user) + 32 (intent_creator) + 32 (intent_hash) + 32 (nonce) + 8 (deadline) + 2 (min_validations) = 147 + // validations is u16, so 2 bytes + const minValidations = options.minValidations ?? DEFAULT_MIN_VALIDATIONS + intentData.writeUInt16LE(minValidations, 147) + client.setAccount(intentKey, { + ...intentAccount, + data: intentData, + }) + } + + return intentHash +} + +/** + * Create a finalized proposal + */ +export async function createFinalizedProposal( + solverSdk: SettlerSDK, + solverProvider: LiteSVMProvider, + client: LiteSVM, + program: Program, + options: { + intentHash?: string + deadline?: number + instructions?: ProposalInstruction[] + fees?: TokenFee[] + } = {} +): Promise<{ intentHash: string; proposalKey: PublicKey }> { + const intentHash = + options.intentHash || (await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true })) + const intent = await program.account.intent.fetch(solverSdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const proposalDeadline = options.deadline ?? now + 1800 + + const instructions = options.instructions || [ + { + programId: Keypair.generate().publicKey, + accounts: [ + { + pubkey: Keypair.generate().publicKey, + isSigner: false, + isWritable: true, + }, + ], + data: 'deadbeef', + }, + ] + + const fees = + options.fees || + (intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) as TokenFee[]) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, proposalDeadline, true) + const res = await makeTxSignAndSend(solverProvider, ix) + if (res instanceof FailedTransactionMetadata) { + throw new Error(`Failed to create proposal: ${res.toString()}`) + } + + const proposalKey = solverSdk.getProposalKey(intentHash, solverProvider.wallet.publicKey) + return { intentHash, proposalKey } +} + +/** + * Create a whitelisted entity (validator, axia, or solver) + */ +export async function createWhitelistedEntity( + whitelistSdk: WhitelistSDK, + provider: LiteSVMProvider, + entityType: EntityType, + entityKeypair?: Keypair +): Promise { + const entity = entityKeypair || Keypair.generate() + const whitelistIx = await whitelistSdk.setEntityWhitelistStatusIx( + entityType, + entity.publicKey, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(provider, whitelistIx) + return entity +} + +/** + * Create an Ed25519 signature for a validator (signs intent hash) + */ +export async function createValidatorSignature(intentHash: string, validator: Keypair): Promise { + const signature = await signAsync(Buffer.from(intentHash, 'hex'), validator.secretKey.slice(0, 32)) + return Array.from(new Uint8Array(signature)) +} + +/** + * Create an Ed25519 signature for an axia (signs proposal key) + */ +export async function createAxiaSignature(proposalKey: PublicKey, axia: Keypair): Promise { + const signature = await signAsync(proposalKey.toBuffer(), axia.secretKey.slice(0, 32)) + return Array.from(new Uint8Array(signature)) +} + +/** + * 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 index d5ff4ce..9ef7418 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -2,45 +2,2657 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Program, Wallet } from '@coral-xyz/anchor' -import { Keypair } from '@solana/web3.js' +import { signAsync } from '@noble/ed25519' +import { Ed25519Program, Keypair, SYSVAR_INSTRUCTIONS_PUBKEY, TransactionInstruction } from '@solana/web3.js' import { fromWorkspace, LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' +import fs from 'fs' +import { FailedTransactionMetadata, LiteSVM } from 'litesvm' +import os from 'os' import path from 'path' +import SettlerSDK from '../sdks/settler/Settler' +import { OpType } from '../sdks/settler/types' +import WhitelistSDK, { EntityType, WhitelistStatus } from '../sdks/whitelist/Whitelist' import * as SettlerIDL from '../target/idl/settler.json' +import * as WhitelistIDL from '../target/idl/whitelist.json' import { Settler } from '../target/types/settler' -import { extractLogs } from './utils' +import { + ACCOUNT_CLOSE_FEE, + DEFAULT_DATA_HEX, + DEFAULT_EVENT_DATA_HEX, + DEFAULT_MAX_FEE, + DEFAULT_MAX_FEE_EXCEED, + DEFAULT_MAX_FEE_HALF, + DEFAULT_MIN_VALIDATIONS, + DEFAULT_TOPIC_HEX, + DOUBLE_CLAIM_DELAY, + DOUBLE_CLAIM_DELAY_PLUS_ONE, + EMPTY_DATA_HEX, + EXPIRATION_TEST_DELAY, + EXPIRATION_TEST_DELAY_PLUS_ONE, + INTENT_DEADLINE_OFFSET, + LONG_DEADLINE, + MEDIUM_DEADLINE, + MULTIPLE_MIN_VALIDATIONS, + PROPOSAL_DEADLINE_OFFSET, + SHORT_DEADLINE, + STALE_CLAIM_DELAY, + STALE_CLAIM_DELAY_PLUS_ONE, + TEST_DATA_HEX_1, + TEST_DATA_HEX_2, + TEST_DATA_HEX_3, + VERY_SHORT_DEADLINE, + WARP_TIME_LONG, + WARP_TIME_SHORT, +} from './helpers/constants' +import { + createAxiaSignature, + createFinalizedProposal, + createTestIntent, + createValidatedIntent, + createValidatorSignature, + createWhitelistedEntity, + expectTransactionError, + generateIntentHash, + generateNonce, +} from './helpers/settler-helpers' +import { makeTxSignAndSend, warpSeconds } from './utils' describe('Settler Program', () => { - let client: any + let client: LiteSVM + let provider: LiteSVMProvider + let maliciousProvider: LiteSVMProvider + let solverProvider: LiteSVMProvider + let admin: Keypair let malicious: Keypair + let solver: Keypair + let program: Program + let sdk: SettlerSDK + let maliciousSdk: SettlerSDK + let solverSdk: SettlerSDK + + let whitelistSdk: WhitelistSDK + before(async () => { - admin = Keypair.generate() + admin = Keypair.fromSecretKey( + Uint8Array.from(JSON.parse(fs.readFileSync(path.join(os.homedir(), '.config', 'solana', 'id.json'), 'utf8'))) + ) malicious = Keypair.generate() + solver = Keypair.generate() - client = fromWorkspace(path.join(__dirname, '../')).withBuiltins() + client = fromWorkspace(path.join(__dirname, '../')).withBuiltins().withPrecompiles().withSysvars() provider = new LiteSVMProvider(client, new Wallet(admin)) + maliciousProvider = new LiteSVMProvider(client, new Wallet(malicious)) + solverProvider = new LiteSVMProvider(client, new Wallet(solver)) + program = new Program(SettlerIDL as any, provider) - // Airdrop initial lamports + sdk = new SettlerSDK(provider) + maliciousSdk = new SettlerSDK(maliciousProvider) + solverSdk = new SettlerSDK(solverProvider) + provider.client.airdrop(admin.publicKey, BigInt(100_000_000_000)) provider.client.airdrop(malicious.publicKey, BigInt(100_000_000_000)) + provider.client.airdrop(solver.publicKey, BigInt(100_000_000_000)) + + // Initialize Whitelist and whitelist Solver + whitelistSdk = new WhitelistSDK(provider) + await makeTxSignAndSend(provider, await whitelistSdk.initializeIx(admin.publicKey, 1)) + await makeTxSignAndSend( + provider, + await whitelistSdk.setEntityWhitelistStatusIx(EntityType.Solver, solver.publicKey, WhitelistStatus.Whitelisted) + ) + }) + + beforeEach(() => { + client.expireBlockhash() }) 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 + describe('initialize', () => { + it('cannot initialize if not deployer', async () => { + const ix = await maliciousSdk.initializeIx() + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, 'Only Deployer can call this instruction.') + }) + + it('should call initialize', async () => { + const ix = await sdk.initializeIx() + await makeTxSignAndSend(provider, ix) + + const settings = await program.account.settlerSettings.fetch(sdk.getSettlerSettingsPubkey()) + expect(settings.whitelistProgram.toString()).to.be.eq(WhitelistIDL.address) + expect(settings.isPaused).to.be.false + }) + + it('cannot call initialize again', async () => { + const ix = await sdk.initializeIx() + const res = await makeTxSignAndSend(provider, ix) + + expectTransactionError(res, 'already in use') + }) + }) + + describe('create_intent', () => { + it('should create an intent', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: DEFAULT_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [ + { + topicHex: DEFAULT_TOPIC_HEX, + dataHex: DEFAULT_EVENT_DATA_HEX, + }, + ], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, false) + await makeTxSignAndSend(solverProvider, ix) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intent.op).to.deep.include({ transfer: {} }) + expect(intent.user.toString()).to.be.eq(user.toString()) + expect(intent.intentCreator.toString()).to.be.eq(solver.publicKey.toString()) + expect(Buffer.from(intent.nonce).toString('hex')).to.be.eq(nonce) + expect(intent.deadline.toNumber()).to.be.eq(deadline) + expect(intent.minValidations).to.be.eq(DEFAULT_MIN_VALIDATIONS) + expect(intent.validations).to.be.eq(0) + expect(intent.isFinal).to.be.false + expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq(DEFAULT_DATA_HEX) + expect(intent.maxFees.length).to.be.eq(1) + expect(intent.maxFees[0].mint.toString()).to.be.eq(params.maxFees[0].mint.toString()) + expect(intent.maxFees[0].amount.toNumber()).to.be.eq(DEFAULT_MAX_FEE) + expect(intent.events.length).to.be.eq(1) + expect(intent.validators.length).to.be.eq(0) + expect(Buffer.from(intent.events[0].topic).toString('hex')).to.be.eq(params.eventsHex[0].topicHex) + expect(Buffer.from(intent.events[0].data).toString('hex')).to.be.eq(DEFAULT_EVENT_DATA_HEX) + }) + + it('should create an intent with empty data', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + op: OpType.Swap, + minValidations: 2, + dataHex: EMPTY_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: 2000, + }, + ], + eventsHex: [], + isFinal: true, + }) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intent.op).to.deep.include({ swap: {} }) + expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq(EMPTY_DATA_HEX) + expect(intent.isFinal).to.be.true + }) + + it('cannot create an intent with empty max_fees', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + + const params = { + op: OpType.Call, + user, + nonceHex: nonce, + deadline, + minValidations: MULTIPLE_MIN_VALIDATIONS, + dataHex: TEST_DATA_HEX_1, + maxFees: [], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, false) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, 'No max fees provided') + }) + + it('should create an intent with empty events', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + dataHex: TEST_DATA_HEX_2, + eventsHex: [], + }) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intent.events.length).to.be.eq(0) + }) + + it('should create an intent with is_final true', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + dataHex: EMPTY_DATA_HEX, + eventsHex: [], + isFinal: true, + }) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intent.isFinal).to.be.true + }) + + it('should create an intent with is_final false', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + dataHex: EMPTY_DATA_HEX, + eventsHex: [], + isFinal: false, + }) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intent.isFinal).to.be.false + }) + + it('cannot create intent if not whitelisted solver', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: EMPTY_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await maliciousSdk.createIntentIx(intentHash, params, false) + const res = await makeTxSignAndSend(maliciousProvider, ix) + expectTransactionError(res, 'AccountNotInitialized') + + const intent = client.getAccount(sdk.getIntentKey(intentHash)) + expect(intent).to.be.null + }) + + it('cannot create intent with deadline in the past', async () => { + warpSeconds(provider, WARP_TIME_LONG) + + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now - SHORT_DEADLINE + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: EMPTY_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, false) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, 'Deadline must be in the future') + }) + + it('cannot create intent with deadline equal to now', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: EMPTY_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, false) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, 'Deadline must be in the future') + }) + + it('cannot create intent if fulfilled_intent PDA already exists', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: EMPTY_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + // Mock FulfilledIntent + const fulfilledIntent = sdk.getFulfilledIntentKey(intentHash) + client.setAccount(fulfilledIntent, { + executable: false, + lamports: 1002240, + owner: program.programId, + data: Buffer.from('595168911b9267f7' + '010000000000000000', 'hex'), + }) + + const ix = await solverSdk.createIntentIx(intentHash, params, false) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, 'AccountNotSystemOwned') + }) + + it('cannot create intent with same intent_hash twice', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + isFinal: false, + }) + + client.expireBlockhash() + const params = { + op: OpType.Transfer, + user: Keypair.generate().publicKey, + nonceHex: generateNonce(), + deadline: Number(client.getClock().unixTimestamp) + INTENT_DEADLINE_OFFSET, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: EMPTY_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + const ix2 = await solverSdk.createIntentIx(intentHash, params, false) + const res = await makeTxSignAndSend(solverProvider, ix2) + + expectTransactionError(res, 'already in use') + }) + + it('cannot create intent with invalid intent_hash', async () => { + const invalidIntentHash = '123456' + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: EMPTY_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + try { + const ix = await solverSdk.createIntentIx(invalidIntentHash, params, false) + await makeTxSignAndSend(solverProvider, ix) + expect.fail('Should have thrown an error') + } catch (error: any) { + expect(error.message).to.include(`Intent hash must be 32 bytes`) + } + }) + }) + + describe('extend_intent', () => { + it('should extend an intent with more data', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + isFinal: false, + }) + + const extendParams = { + moreDataHex: TEST_DATA_HEX_1, + } + + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, false) + await makeTxSignAndSend(solverProvider, ix) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq('010203070809') + expect(intent.isFinal).to.be.false + }) + + it('should extend an intent with more max_fees', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: false }) + + const newMint = Keypair.generate().publicKey + const extendParams = { + moreMaxFees: [ + { + mint: newMint, + amount: 2000, + }, + ], + } + + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, false) + await makeTxSignAndSend(solverProvider, ix) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intent.maxFees.length).to.be.eq(2) + expect(intent.maxFees[0].amount.toNumber()).to.be.eq(DEFAULT_MAX_FEE) + expect(intent.maxFees[1].mint.toString()).to.be.eq(newMint.toString()) + expect(intent.maxFees[1].amount.toNumber()).to.be.eq(2000) + }) + + it('should extend an intent with more events', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: false }) + + const newTopic = Buffer.from(Array(32).fill(2)).toString('hex') + const extendParams = { + moreEventsHex: [ + { + topicHex: newTopic, + dataHex: TEST_DATA_HEX_2, + }, + ], + } + + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, false) + await makeTxSignAndSend(solverProvider, ix) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intent.events.length).to.be.eq(2) + expect(Buffer.from(intent.events[0].topic).toString('hex')).to.be.eq( + Buffer.from(Array(32).fill(1)).toString('hex') + ) + expect(Buffer.from(intent.events[1].topic).toString('hex')).to.be.eq(newTopic) + expect(Buffer.from(intent.events[1].data).toString('hex')).to.be.eq('0a0b0c') + }) + + it('should extend an intent with all optional fields', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: false }) + + const newMint = Keypair.generate().publicKey + const newTopic = Buffer.from(Array(32).fill(3)).toString('hex') + const extendParams = { + moreDataHex: '0d0e0f', + moreMaxFees: [ + { + mint: newMint, + amount: 3000, + }, + ], + moreEventsHex: [ + { + topicHex: newTopic, + dataHex: '101112', + }, + ], + } + + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, false) + await makeTxSignAndSend(solverProvider, ix) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq('0102030d0e0f') + expect(intent.maxFees.length).to.be.eq(2) + expect(intent.maxFees[1].amount.toNumber()).to.be.eq(3000) + expect(intent.events.length).to.be.eq(2) + expect(Buffer.from(intent.events[1].data).toString('hex')).to.be.eq('101112') + }) + + it('should extend an intent to a large size', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: false }) + const intentKey = sdk.getIntentKey(intentHash) + + for (let i = 0; i < 100; i++) { + const ix = await solverSdk.extendIntentIx(intentHash, { moreDataHex: 'f'.repeat(100) }, false) + await makeTxSignAndSend(solverProvider, ix) + client.expireBlockhash() + } + + for (let i = 0; i < 25; i++) { + const extendParams = { + moreEventsHex: [ + { topicHex: 'e'.repeat(64), dataHex: 'beef'.repeat(100) }, + { topicHex: 'd'.repeat(64), dataHex: 'beef'.repeat(100) }, + ], + } + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, false) + await makeTxSignAndSend(solverProvider, ix) + client.expireBlockhash() + } + + for (let i = 0; i < 19; i++) { + const extendParams = { + moreMaxFees: [ + { mint: Keypair.generate().publicKey, amount: i }, + { mint: Keypair.generate().publicKey, amount: i + 1000 }, + { mint: Keypair.generate().publicKey, amount: i + 2000 }, + ], + } + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, false) + await makeTxSignAndSend(solverProvider, ix) + client.expireBlockhash() + } + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const intentAcc = client.getAccount(intentKey) + expect(intent.intentData.length).to.be.eq(3 + 5000) // Keep literal for specific test case + expect(intent.maxFees.length).to.be.eq(58) + expect(intent.events.length).to.be.eq(51) + expect(intent.isFinal).to.be.false + expect(intentAcc?.data.length).to.be.eq(19361) + }) + + it('should finalize an intent', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: false }) + + const extendParams = {} + + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, true) + await makeTxSignAndSend(solverProvider, ix) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intent.isFinal).to.be.true + }) + + it('should extend and finalize an intent in one call', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: false }) + + const extendParams = { + moreDataHex: '191a1b', + } + + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, true) + await makeTxSignAndSend(solverProvider, ix) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq('010203191a1b') + expect(intent.isFinal).to.be.true + }) + + it('should extend an intent multiple times', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: false }) + + const extendParams1 = { + moreDataHex: '1c1d1e', + } + const ix1 = await solverSdk.extendIntentIx(intentHash, extendParams1, false) + await makeTxSignAndSend(solverProvider, ix1) + + const extendParams2 = { + moreDataHex: '1f2021', + } + const ix2 = await solverSdk.extendIntentIx(intentHash, extendParams2, false) + await makeTxSignAndSend(solverProvider, ix2) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq('0102031c1d1e1f2021') + expect(intent.isFinal).to.be.false + }) + + it('cannot extend intent if not intent creator', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: false }) + + const extendParams = { + moreDataHex: '222324', + } + + const ix = await maliciousSdk.extendIntentIx(intentHash, extendParams, false) + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, `Signer must be intent creator`) + }) + + it('cannot extend non-existent intent', async () => { + const intentHash = generateIntentHash() + + const extendParams = { + moreDataHex: '252627', + } + + const ix = await sdk.extendIntentIx(intentHash, extendParams, false) + const res = await makeTxSignAndSend(provider, ix) + + expectTransactionError(res, `AccountNotInitialized`) + }) + + it('cannot extend intent if already finalized', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: true }) + + const extendParams = { + moreDataHex: '28292a', + } + + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, false) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Intent is already final`) + }) + + it('cannot finalize already finalized intent', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { isFinal: true }) + + const extendParams = {} + + const ix = await solverSdk.extendIntentIx(intentHash, extendParams, true) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Intent is already final`) + }) + }) + + describe('claim_stale_intent', () => { + const createTestIntentWithDeadline = async (deadline: number, isFinal = false): Promise => { + return createTestIntent(solverSdk, solverProvider, { deadline, isFinal }) + } + + it('should claim stale intent', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + STALE_CLAIM_DELAY + const intentHash = await createTestIntentWithDeadline(deadline, false) + + const intentBefore = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect(intentBefore).to.not.be.null + + warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) + + const intentBalanceBefore = Number(provider.client.getBalance(sdk.getIntentKey(intentHash))) || 0 + const intentCreatorBalanceBefore = Number(provider.client.getBalance(intentBefore.intentCreator)) || 0 + + const ix = await solverSdk.claimStaleIntentIx(intentHash) + await makeTxSignAndSend(solverProvider, ix) + + const intentBalanceAfter = Number(provider.client.getBalance(sdk.getIntentKey(intentHash))) || 0 + const intentCreatorBalanceAfter = Number(provider.client.getBalance(intentBefore.intentCreator)) || 0 + + try { + await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + expect.fail('Intent account should be closed') + } catch (error: any) { + expect(error.message).to.include(`Account does not exist`) + } + + expect(intentCreatorBalanceAfter).to.be.eq(intentCreatorBalanceBefore + intentBalanceBefore - ACCOUNT_CLOSE_FEE) + expect(intentBalanceAfter).to.be.eq(0) + }) + + it('cannot claim intent if deadline has not passed', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + LONG_DEADLINE + const intentHash = await createTestIntentWithDeadline(deadline, false) + + warpSeconds(provider, WARP_TIME_SHORT) + + const ix = await solverSdk.claimStaleIntentIx(intentHash) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, 'Intent not yet expired') + }) + + it('cannot claim intent if deadline equals now', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + MEDIUM_DEADLINE + const intentHash = await createTestIntentWithDeadline(deadline, false) + + warpSeconds(provider, MEDIUM_DEADLINE) + + const ix = await solverSdk.claimStaleIntentIx(intentHash) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, 'Intent not yet expired') + }) + + it('cannot claim stale intent if not intent creator', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + EXPIRATION_TEST_DELAY + const intentHash = await createTestIntentWithDeadline(deadline, false) + + warpSeconds(provider, EXPIRATION_TEST_DELAY_PLUS_ONE) + + const ix = await maliciousSdk.claimStaleIntentIx(intentHash) + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, `Signer must be intent creator`) + }) + + it('cannot claim non-existent intent', async () => { + const intentHash = generateIntentHash() + + const ix = await solverSdk.claimStaleIntentIx(intentHash) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `AccountNotInitialized`) + }) + + it('cannot claim intent twice', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + DOUBLE_CLAIM_DELAY + const intentHash = await createTestIntentWithDeadline(deadline, false) + + warpSeconds(provider, DOUBLE_CLAIM_DELAY_PLUS_ONE) + + const ix = await solverSdk.claimStaleIntentIx(intentHash) + await makeTxSignAndSend(solverProvider, ix) + + client.expireBlockhash() + const ix2 = await solverSdk.claimStaleIntentIx(intentHash) + const res = await makeTxSignAndSend(solverProvider, ix2) + + const errorMsg = res.toString() + expect(errorMsg.includes(`AccountNotInitialized`)).to.be.true + }) + }) + + describe('create_proposal', () => { + it('should create a proposal', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [ + { + pubkey: Keypair.generate().publicKey, + isSigner: false, + isWritable: true, + }, + ], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(solverProvider, ix) + if (res instanceof FailedTransactionMetadata) { + throw new Error(`Failed to create proposal: ${res.toString()}`) + } + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.intent.toString()).to.be.eq(sdk.getIntentKey(intentHash).toString()) + expect(proposal.proposalCreator.toString()).to.be.eq(solver.publicKey.toString()) + expect(proposal.deadline.toNumber()).to.be.eq(deadline) + expect(proposal.isFinal).to.be.true + expect(proposal.instructions.length).to.be.eq(1) + expect(proposal.instructions[0].programId.toString()).to.be.eq(instructions[0].programId.toString()) + expect(Buffer.from(proposal.instructions[0].data).toString('hex')).to.be.eq('deadbeef') + expect(proposal.instructions[0].accounts.length).to.be.eq(1) + expect(proposal.instructions[0].accounts[0].pubkey.toString()).to.be.eq( + instructions[0].accounts[0].pubkey.toString() + ) + expect(proposal.instructions[0].accounts[0].isSigner).to.be.eq(false) + expect(proposal.instructions[0].accounts[0].isWritable).to.be.eq(true) + }) + + it('should create a proposal with multiple instructions', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [ + { + pubkey: Keypair.generate().publicKey, + isSigner: false, + isWritable: true, + }, + ], + data: '010203', + }, + { + programId: Keypair.generate().publicKey, + accounts: [ + { + pubkey: Keypair.generate().publicKey, + isSigner: true, + isWritable: false, + }, + ], + data: '040506', + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(solverProvider, ix) + if (res instanceof FailedTransactionMetadata) { + throw new Error(`Failed to create proposal: ${res.toString()}`) + } + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.instructions.length).to.be.eq(2) + expect(Buffer.from(proposal.instructions[0].data).toString('hex')).to.be.eq('010203') + expect(Buffer.from(proposal.instructions[1].data).toString('hex')).to.be.eq('040506') + expect(proposal.isFinal).to.be.true + expect(proposal.instructions[0].accounts.length).to.be.eq(1) + expect(proposal.instructions[0].accounts[0].pubkey.toString()).to.be.eq( + instructions[0].accounts[0].pubkey.toString() + ) + expect(proposal.instructions[0].accounts[0].isSigner).to.be.eq(false) + expect(proposal.instructions[0].accounts[0].isWritable).to.be.eq(true) + expect(proposal.instructions[1].accounts.length).to.be.eq(1) + expect(proposal.instructions[1].accounts[0].pubkey.toString()).to.be.eq( + instructions[1].accounts[0].pubkey.toString() + ) + expect(proposal.instructions[1].accounts[0].isSigner).to.be.eq(true) + expect(proposal.instructions[1].accounts[0].isWritable).to.be.eq(false) + }) + + it('should create a proposal with empty instructions', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions: any[] = [] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(solverProvider, ix) + if (res instanceof FailedTransactionMetadata) { + throw new Error(`Failed to create proposal: ${res.toString()}`) + } + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.instructions.length).to.be.eq(0) + }) + + it('cannot create proposal if not whitelisted solver', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await maliciousSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(maliciousProvider, ix) + expectTransactionError(res, 'AccountNotInitialized') + }) + + it('cannot create proposal with deadline in the past', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now - SHORT_DEADLINE + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Deadline must be in the future`) + }) + + it('cannot create proposal with deadline equal to now', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Deadline must be in the future`) + }) + + it('cannot create proposal if intent deadline has passed', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const intentDeadline = now + SHORT_DEADLINE + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline: intentDeadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: DEFAULT_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params) + await makeTxSignAndSend(solverProvider, ix) + + // Set validations + const intentKey = sdk.getIntentKey(intentHash) + const intentAccount = client.getAccount(intentKey) + if (intentAccount) { + const intentData = Buffer.from(intentAccount.data) + intentData.writeUInt16LE(1, 147) + client.setAccount(intentKey, { + ...intentAccount, + data: intentData, + }) + } + + warpSeconds(provider, 101) + + const proposalDeadline = now + 200 + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const ix2 = await solverSdk.createProposalIx(intentHash, instructions, [], proposalDeadline) + const res = await makeTxSignAndSend(solverProvider, ix2) + + expectTransactionError(res, `Intent has already expired`) + }) + + it('cannot create proposal if proposal deadline exceeds intent deadline', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intentDeadline = Number((await program.account.intent.fetch(sdk.getIntentKey(intentHash))).deadline) + const proposalDeadline = intentDeadline + SHORT_DEADLINE + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const ix = await solverSdk.createProposalIx(intentHash, instructions, [], proposalDeadline) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Proposal deadline can't be after the Intent's deadline`) + }) + + it('cannot create proposal if intent has insufficient validations', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: 2, + dataHex: DEFAULT_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params) + await makeTxSignAndSend(solverProvider, ix) + + // Set validations to 1 (less than min_validations of 2) + const intentKey = sdk.getIntentKey(intentHash) + const intentAccount = client.getAccount(intentKey) + if (intentAccount) { + const intentData = Buffer.from(intentAccount.data) + intentData.writeUInt16LE(1, 147) + client.setAccount(intentKey, { + ...intentAccount, + data: intentData, + }) + } + + const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const ix2 = await solverSdk.createProposalIx(intentHash, instructions, [], proposalDeadline) + const res = await makeTxSignAndSend(solverProvider, ix2) + + expectTransactionError(res, `Intent has insufficient validations`) + }) + + it('cannot create proposal if intent is not final', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: false }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Intent is not final`) + }) + + it('cannot create proposal if fulfilled_intent PDA already exists', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + // Mock FulfilledIntent + const fulfilledIntent = sdk.getFulfilledIntentKey(intentHash) + client.setAccount(fulfilledIntent, { + executable: false, + lamports: 1002240, + owner: program.programId, + data: Buffer.from('595168911b9267f7' + '010000000000000000', 'hex'), + }) + + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError( + res, + `AnchorError caused by account: fulfilled_intent. Error Code: AccountNotSystemOwned. Error Number: 3011. Error Message: The given account is not owned by the system program` + ) + }) + + it('cannot create proposal with same intent_hash and solver twice', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + await makeTxSignAndSend(solverProvider, ix) + + client.expireBlockhash() + const ix2 = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) + const res = await makeTxSignAndSend(solverProvider, ix2) + + expectTransactionError(res, `already in use`) + }) + + it('cannot create proposal for non-existent intent', async () => { + const intentHash = generateIntentHash() + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const ix = await solverSdk.createProposalIx(intentHash, instructions, [], deadline) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `AccountNotInitialized`) + }) + + it('should create proposal with fees matching intent max_fees', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + const mint = Keypair.generate().publicKey + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: DEFAULT_DATA_HEX, + maxFees: [ + { + mint, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, true) + await makeTxSignAndSend(solverProvider, ix) + + // Set validations + const intentKey = sdk.getIntentKey(intentHash) + const intentAccount = client.getAccount(intentKey) + if (intentAccount) { + const intentData = Buffer.from(intentAccount.data) + intentData.writeUInt16LE(1, 147) + client.setAccount(intentKey, { + ...intentAccount, + data: intentData, + }) + } + + const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = [ + { + mint, + amount: DEFAULT_MAX_FEE_HALF, + }, + ] + + const proposalIx = await solverSdk.createProposalIx(intentHash, instructions, fees, proposalDeadline) + await makeTxSignAndSend(solverProvider, proposalIx) + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.fees.length).to.be.eq(1) + expect(proposal.fees[0].mint.toString()).to.be.eq(mint.toString()) + expect(proposal.fees[0].amount.toNumber()).to.be.eq(DEFAULT_MAX_FEE_HALF) + }) + + it('cannot create proposal with fees exceeding max_fees', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + const mint = Keypair.generate().publicKey + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: DEFAULT_DATA_HEX, + maxFees: [ + { + mint, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, true) + await makeTxSignAndSend(solverProvider, ix) + + // Set validations + const intentKey = sdk.getIntentKey(intentHash) + const intentAccount = client.getAccount(intentKey) + if (intentAccount) { + const intentData = Buffer.from(intentAccount.data) + intentData.writeUInt16LE(1, 147) + client.setAccount(intentKey, { + ...intentAccount, + data: intentData, + }) + } + + const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = [ + { + mint, + amount: DEFAULT_MAX_FEE_EXCEED, // Exceeds max_fee + }, + ] + + const proposalIx = await solverSdk.createProposalIx(intentHash, instructions, fees, proposalDeadline) + const res = await makeTxSignAndSend(solverProvider, proposalIx) + + expect(res).to.be.instanceOf(FailedTransactionMetadata) + expect(res.toString()).to.match(/FeeAmountExceedsMaxFee|Fee amount exceeds max fee/i) + }) + + it('cannot create proposal with fees having wrong mint', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + INTENT_DEADLINE_OFFSET + const mint = Keypair.generate().publicKey + const wrongMint = Keypair.generate().publicKey + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: DEFAULT_DATA_HEX, + maxFees: [ + { + mint, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, true) + await makeTxSignAndSend(solverProvider, ix) + + // Set validations + const intentKey = sdk.getIntentKey(intentHash) + const intentAccount = client.getAccount(intentKey) + if (intentAccount) { + const intentData = Buffer.from(intentAccount.data) + intentData.writeUInt16LE(1, 147) + client.setAccount(intentKey, { + ...intentAccount, + data: intentData, + }) + } + + const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = [ + { + mint: wrongMint, // Wrong mint + amount: DEFAULT_MAX_FEE_HALF, + }, + ] + + const proposalIx = await solverSdk.createProposalIx(intentHash, instructions, fees, proposalDeadline) + const res = await makeTxSignAndSend(solverProvider, proposalIx) + + expect(res).to.be.instanceOf(FailedTransactionMetadata) + expect(res.toString()).to.match(/InvalidFeeMint|Invalid fee mint/i) + }) + }) + + describe('add_instructions_to_proposal', () => { + const createTestProposal = async (isFinal = false): Promise => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [ + { + pubkey: Keypair.generate().publicKey, + isSigner: false, + isWritable: true, + }, + ], + data: DEFAULT_DATA_HEX, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, isFinal) + await makeTxSignAndSend(solverProvider, ix) + return intentHash + } + + it('should add instructions to proposal', async () => { + const intentHash = await createTestProposal(false) + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [ + { + pubkey: Keypair.generate().publicKey, + isSigner: false, + isWritable: true, + }, + ], + data: '040506', + }, + ] + + const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions, false) + await makeTxSignAndSend(solverProvider, ix) + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.instructions.length).to.be.eq(2) + expect(Buffer.from(proposal.instructions[0].data).toString('hex')).to.be.eq('010203') + expect(Buffer.from(proposal.instructions[1].data).toString('hex')).to.be.eq('040506') + expect(proposal.isFinal).to.be.false + expect(proposal.instructions[1].accounts.length).to.be.eq(1) + expect(proposal.instructions[1].accounts[0].pubkey.toString()).to.be.eq( + moreInstructions[0].accounts[0].pubkey.toString() + ) + expect(proposal.instructions[1].accounts[0].isSigner).to.be.eq(false) + expect(proposal.instructions[1].accounts[0].isWritable).to.be.eq(true) + }) + + it('should add multiple instructions to proposal', async () => { + const intentHash = await createTestProposal(false) + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '070809', + }, + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '0a0b0c', + }, + ] + + const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions, false) + await makeTxSignAndSend(solverProvider, ix) + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.instructions.length).to.be.eq(3) + expect(Buffer.from(proposal.instructions[1].data).toString('hex')).to.be.eq('070809') + expect(Buffer.from(proposal.instructions[2].data).toString('hex')).to.be.eq('0a0b0c') + expect(proposal.isFinal).to.be.false + }) + + it('should add instructions to proposal multiple times', async () => { + const intentHash = await createTestProposal(false) + + const moreInstructions1 = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '0d0e0f', + }, + ] + const ix1 = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions1, false) + await makeTxSignAndSend(solverProvider, ix1) + + const moreInstructions2 = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '101112', + }, + ] + const ix2 = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions2, false) + await makeTxSignAndSend(solverProvider, ix2) + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.instructions.length).to.be.eq(3) + expect(Buffer.from(proposal.instructions[1].data).toString('hex')).to.be.eq('0d0e0f') + expect(Buffer.from(proposal.instructions[2].data).toString('hex')).to.be.eq('101112') + expect(proposal.isFinal).to.be.false + }) + + it('cannot add instructions if not proposal creator', async () => { + const intentHash = await createTestProposal(false) + const proposalCreator = (await program.account.proposal.fetch(solverSdk.getProposalKey(intentHash))) + .proposalCreator + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '131415', + }, + ] + + const ix = await maliciousSdk.addInstructionsToProposalIx( + intentHash, + moreInstructions, + undefined, + proposalCreator + ) + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, `Signer must be proposal creator`) + }) + + it('cannot add instructions to non-existent proposal', async () => { + const intentHash = generateIntentHash() + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '161718', + }, + ] + + const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `AccountNotInitialized`) + }) + + it('cannot add instructions if proposal deadline has passed', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + STALE_CLAIM_DELAY + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '010203', + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) + await makeTxSignAndSend(solverProvider, ix) + + warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '19202a', + }, + ] + + const ix2 = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) + const res = await makeTxSignAndSend(solverProvider, ix2) + + expectTransactionError(res, 'Proposal has already expired') + }) + + it('cannot add instructions if proposal deadline equals now', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + SHORT_DEADLINE + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '010203', + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) + await makeTxSignAndSend(solverProvider, ix) + + warpSeconds(provider, WARP_TIME_SHORT) + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '1b1c1d', + }, + ] + + const ix2 = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) + const res = await makeTxSignAndSend(solverProvider, ix2) + + expectTransactionError(res, 'Proposal has already expired') + }) + + it('cannot add instructions if proposal is final', async () => { + const intentHash = await createTestProposal(true) + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '1e1f20', + }, + ] + + const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Proposal is already final`) + }) + + it('should finalize proposal when adding instructions with finalize=true', async () => { + const intentHash = await createTestProposal(false) + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '212223', + }, + ] + + const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions, true) + await makeTxSignAndSend(solverProvider, ix) + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.isFinal).to.be.true + expect(proposal.instructions.length).to.be.eq(2) + }) + + it('should not finalize proposal when adding instructions with finalize=false', async () => { + const intentHash = await createTestProposal(false) + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '242526', + }, + ] + + const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions, false) + await makeTxSignAndSend(solverProvider, ix) + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.isFinal).to.be.false + expect(proposal.instructions.length).to.be.eq(2) + }) + + it('should finalize proposal by default when adding instructions', async () => { + const intentHash = await createTestProposal(false) + + const moreInstructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: '272829', + }, + ] + + const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) + await makeTxSignAndSend(solverProvider, ix) + + const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposal.isFinal).to.be.true + expect(proposal.instructions.length).to.be.eq(2) + }) + }) + + describe('claim_stale_proposal', () => { + const createTestProposalWithDeadline = async (deadline: number): Promise => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [ + { + pubkey: Keypair.generate().publicKey, + isSigner: false, + isWritable: true, + }, + ], + data: DEFAULT_DATA_HEX, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) + await makeTxSignAndSend(solverProvider, ix) + return intentHash + } + + it('should claim stale proposal', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + STALE_CLAIM_DELAY + const intentHash = await createTestProposalWithDeadline(deadline) + + const proposalBefore = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposalBefore).to.not.be.null + + warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) + + const proposalBalanceBefore = + Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0 + const proposalCreatorBalanceBefore = Number(provider.client.getBalance(proposalBefore.proposalCreator)) || 0 + + const ix = await solverSdk.claimStaleProposalIx([intentHash]) + await makeTxSignAndSend(solverProvider, ix) + + const proposalBalanceAfter = + Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0 + const proposalCreatorBalanceAfter = Number(provider.client.getBalance(proposalBefore.proposalCreator)) || 0 + + try { + await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect.fail('Proposal account should be closed') + } catch (error: any) { + expect(error.message).to.include(`Account does not exist`) + } + + expect(proposalCreatorBalanceAfter).to.be.eq( + proposalCreatorBalanceBefore + proposalBalanceBefore - ACCOUNT_CLOSE_FEE + ) + expect(proposalBalanceAfter).to.be.eq(0) + }) + + it('should claim multiple stale proposals', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + STALE_CLAIM_DELAY + const intentHashes = await Promise.all( + Array.from({ length: 20 }, async () => await createTestProposalWithDeadline(deadline)) + ) + + for (const intentHash of intentHashes) { + const proposalBefore = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect(proposalBefore).to.not.be.null + } + + warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) + + const proposalBalancesBefore = intentHashes.reduce( + (acc, intentHash) => + acc + Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0, + 0 + ) + const proposalCreatorBalanceBefore = Number(provider.client.getBalance(solver.publicKey)) || 0 + + const ix = await solverSdk.claimStaleProposalIx(intentHashes) + await makeTxSignAndSend(solverProvider, ix) + + const proposalBalancesAfter = intentHashes.reduce( + (acc, intentHash) => + acc + Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0, + 0 + ) + const proposalCreatorBalanceAfter = Number(provider.client.getBalance(solver.publicKey)) || 0 + + for (const intentHash of intentHashes) { + try { + await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) + expect.fail('Proposal account should be closed') + } catch (error: any) { + expect(error.message).to.include(`Account does not exist`) + } + } + + expect(proposalCreatorBalanceAfter).to.be.eq( + proposalCreatorBalanceBefore + proposalBalancesBefore - ACCOUNT_CLOSE_FEE + ) + expect(proposalBalancesAfter).to.be.eq(0) + }) + + it('cannot claim proposal if deadline has not passed', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + LONG_DEADLINE + const intentHash = await createTestProposalWithDeadline(deadline) + + warpSeconds(provider, WARP_TIME_SHORT) + + const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Proposal not yet expired`) + }) + + it('cannot claim proposal if deadline equals now', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + MEDIUM_DEADLINE + const intentHash = await createTestProposalWithDeadline(deadline) + + warpSeconds(provider, MEDIUM_DEADLINE) + + const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `Proposal not yet expired`) + }) + + it('cannot claim stale proposal if not proposal creator', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + EXPIRATION_TEST_DELAY + const intentHash = await createTestProposalWithDeadline(deadline) + + warpSeconds(provider, EXPIRATION_TEST_DELAY_PLUS_ONE) + + const ix = await maliciousSdk.claimStaleProposalIx([intentHash], solver.publicKey) + const res = await makeTxSignAndSend(maliciousProvider, ix) + + expectTransactionError(res, `Signer must be proposal creator`) + }) + + it('cannot claim non-existent proposal', async () => { + const intentHash = generateIntentHash() + + const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const res = await makeTxSignAndSend(solverProvider, ix) + + expectTransactionError(res, `AccountNotInitialized`) + }) + + it('cannot claim proposal twice', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + DOUBLE_CLAIM_DELAY + const intentHash = await createTestProposalWithDeadline(deadline) + + warpSeconds(provider, DOUBLE_CLAIM_DELAY_PLUS_ONE) + + const ix = await solverSdk.claimStaleProposalIx([intentHash]) + await makeTxSignAndSend(solverProvider, ix) + + client.expireBlockhash() + const ix2 = await solverSdk.claimStaleProposalIx([intentHash]) + const res = await makeTxSignAndSend(solverProvider, ix2) + + const errorMsg = res.toString() + expect(errorMsg.includes(`AccountNotInitialized`)).to.be.true + }) + }) + + describe('add_validator_sigs', () => { + let whitelistedValidator: Keypair + + before(async () => { + whitelistedValidator = Keypair.generate() + const whitelistValidatorIx = await whitelistSdk.setEntityWhitelistStatusIx( + EntityType.Validator, + whitelistedValidator.publicKey, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(provider, whitelistValidatorIx) + }) + + it('should add validator signature successfully', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const intentBefore = await program.account.intent.fetch(intentKey) + expect(intentBefore.validations).to.be.eq(0) + expect(intentBefore.validators.length).to.be.eq(0) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + await makeTxSignAndSend(solverProvider, ...ixs) + + const intentAfter = await program.account.intent.fetch(intentKey) + expect(intentAfter.validations).to.be.eq(1) + expect(intentAfter.validators.length).to.be.eq(1) + expect(intentAfter.validators[0].toString()).to.be.eq(whitelistedValidator.publicKey.toString()) + }) + + it('should add multiple validator signatures', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + minValidations: MULTIPLE_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const validator1 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + const validator2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + const validator3 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + + const signature1 = await createValidatorSignature(intentHash, validator1) + const ixs1 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator1.publicKey, + signature1 + ) + await makeTxSignAndSend(solverProvider, ...ixs1) + + const intentAfter1 = await program.account.intent.fetch(intentKey) + expect(intentAfter1.validations).to.be.eq(1) + expect(intentAfter1.validators.length).to.be.eq(1) + + const signature2 = await createValidatorSignature(intentHash, validator2) + const ixs2 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator2.publicKey, + signature2 + ) + await makeTxSignAndSend(solverProvider, ...ixs2) + + const intentAfter2 = await program.account.intent.fetch(intentKey) + expect(intentAfter2.validations).to.be.eq(2) + expect(intentAfter2.validators.length).to.be.eq(2) + + const signature3 = await createValidatorSignature(intentHash, validator3) + const ixs3 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator3.publicKey, + signature3 + ) + await makeTxSignAndSend(solverProvider, ...ixs3) + + const intentAfter3 = await program.account.intent.fetch(intentKey) + expect(intentAfter3.validations).to.be.eq(3) + expect(intentAfter3.validators.length).to.be.eq(3) + expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator1.publicKey.toString()) + expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator2.publicKey.toString()) + expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator3.publicKey.toString()) + }) + + it('should handle duplicate validator signature gracefully', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { minValidations: 2, isFinal: true }) + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs1 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + await makeTxSignAndSend(solverProvider, ...ixs1) + + const intentAfter1 = await program.account.intent.fetch(intentKey) + expect(intentAfter1.validations).to.be.eq(1) + expect(intentAfter1.validators.length).to.be.eq(1) + + client.expireBlockhash() + const ixs2 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + await makeTxSignAndSend(solverProvider, ...ixs2) + + const intentAfter2 = await program.account.intent.fetch(intentKey) + expect(intentAfter2.validations).to.be.eq(1) + expect(intentAfter2.validators.length).to.be.eq(1) + }) + + it('should handle when min_validations already met', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const validator1 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + const validator2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + + const signature1 = await createValidatorSignature(intentHash, validator1) + const ixs1 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator1.publicKey, + signature1 + ) + await makeTxSignAndSend(solverProvider, ...ixs1) + + const intentAfter1 = await program.account.intent.fetch(intentKey) + expect(intentAfter1.validations).to.be.eq(1) + expect(intentAfter1.validators.length).to.be.eq(1) + expect(intentAfter1.minValidations).to.be.eq(1) + + client.expireBlockhash() + const signature2 = await createValidatorSignature(intentHash, validator2) + const ixs2 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator2.publicKey, + signature2 + ) + await makeTxSignAndSend(solverProvider, ...ixs2) + + const intentAfter2 = await program.account.intent.fetch(intentKey) + expect(intentAfter2.validations).to.be.eq(1) + expect(intentAfter2.validators.length).to.be.eq(1) + expect(intentAfter2.validators[0].toString()).to.be.eq(validator1.publicKey.toString()) + }) + + it('cannot add signature if intent is not final', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: false, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `Intent is not final`) + }) + + it('cannot add signature if intent has expired', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + SHORT_DEADLINE + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: DEFAULT_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, true) + await makeTxSignAndSend(solverProvider, ix) + + const intentKey = sdk.getIntentKey(intentHash) + + warpSeconds(provider, 101) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `Intent has already expired`) + }) + + it('cannot add signature if validator is not whitelisted', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const validator = Keypair.generate() + + const signature = await createValidatorSignature(intentHash, validator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError( + res, + `AnchorError caused by account: validator_registry. Error Code: AccountNotInitialized.` + ) + }) + + it('cannot add signature if solver is not whitelisted', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs = await maliciousSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(maliciousProvider, ...ixs) + + expectTransactionError( + res, + `AnchorError caused by account: solver_registry. Error Code: AccountNotInitialized.` + ) + }) + + it('cannot add signature for non-existent intent', async () => { + const intentHash = generateIntentHash() + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ + message: Buffer.from(intentHash, 'hex'), + publicKey: whitelistedValidator.publicKey.toBuffer(), + signature: Buffer.from(signature), + }) + + const ix = await program.methods + .addValidatorSig() + .accountsPartial({ + solver: solverSdk.getSignerKey(), + solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), + intent: intentKey, + fulfilledIntent: solverSdk.getFulfilledIntentKey(intentHash), + validatorRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Validator, whitelistedValidator.publicKey), + ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) + + expectTransactionError(res, `AccountNotInitialized`) + }) + + it('cannot add signature if fulfilled_intent PDA already exists', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const fulfilledIntent = sdk.getFulfilledIntentKey(intentHash) + client.setAccount(fulfilledIntent, { + executable: false, + lamports: 1002240, + owner: program.programId, + data: Buffer.from('595168911b9267f7' + '010000000000000000', 'hex'), + }) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError( + res, + `AnchorError caused by account: fulfilled_intent. Error Code: AccountNotSystemOwned. Error Number: 3011. Error Message: The given account is not owned by the system program` + ) + }) + + it('cannot add signature with wrong intent hash', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const wrongIntentHash = generateIntentHash() + const signature = await createValidatorSignature(wrongIntentHash, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(wrongIntentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `Signature verification failed`) + }) + + it('cannot add signature with invalid signature', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const invalidSignature: number[] = Array.from({ length: 64 }, () => Math.floor(Math.random() * 256)) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + invalidSignature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expect(res.toString()).to.be.eq( + `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('cannot add valid signature but for another intent', async () => { + const intentHash1 = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey1 = sdk.getIntentKey(intentHash1) + + const intentHash2 = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + + const signature = await createValidatorSignature(intentHash2, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey1, + Buffer.from(intentHash2, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `Signature verification failed`) + }) + }) + + describe('add_axia_sig', () => { + let whitelistedAxia: Keypair + + before(async () => { + whitelistedAxia = Keypair.generate() + const whitelistAxiaIx = await whitelistSdk.setEntityWhitelistStatusIx( + EntityType.Axia, + whitelistedAxia.publicKey, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(provider, whitelistAxiaIx) + }) + + it('should add axia signature successfully', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const proposalBefore = await program.account.proposal.fetch(proposalKey) + expect(proposalBefore.isSigned).to.be.false + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs) + + const proposalAfter = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter.isSigned).to.be.true + }) + + it('should handle duplicate signature gracefully', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs1 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs1) + + const proposalAfter1 = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter1.isSigned).to.be.true + + client.expireBlockhash() + const ixs2 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs2) + + const proposalAfter2 = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter2.isSigned).to.be.true + }) + + it('should add signature multiple times safely', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs1 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs1) + + client.expireBlockhash() + const ixs2 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs2) + + client.expireBlockhash() + const ixs3 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs3) + + const proposalAfter = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter.isSigned).to.be.true + }) + + it('cannot add signature if proposal is not final', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) + await makeTxSignAndSend(solverProvider, ix) + + const proposalKey = sdk.getProposalKey(intentHash, solver.publicKey) + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, 'Proposal is not final') + }) + + it('cannot add signature if proposal has expired', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + STALE_CLAIM_DELAY + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) + + warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, 'Proposal has already expired') + }) + + it('cannot add signature if axia is not whitelisted', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const axia = Keypair.generate() + const signature = await createAxiaSignature(proposalKey, axia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, axia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) + }) + + it('cannot add signature if solver is not whitelisted', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await maliciousSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(maliciousProvider, ...ixs) + + expectTransactionError( + res, + `AnchorError caused by account: solver_registry. Error Code: AccountNotInitialized.` + ) + }) + + it('cannot add signature for non-existent proposal', async () => { + const proposalKey = Keypair.generate().publicKey + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ + message: proposalKey.toBuffer(), + publicKey: whitelistedAxia.publicKey.toBuffer(), + signature: Buffer.from(signature), + }) + + const ix = await program.methods + .addAxiaSig() + .accountsPartial({ + solver: solverSdk.getSignerKey(), + solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), + proposal: proposalKey, + axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), + ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) + + expectTransactionError( + res, + `Program log: AnchorError caused by account: proposal. Error Code: AccountNotInitialized` + ) + }) + + it('cannot add signature if proposal deadline equals now', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + SHORT_DEADLINE + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) + + warpSeconds(provider, WARP_TIME_SHORT) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, 'Proposal has already expired') + }) + + it('cannot add signature with wrong proposal key', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + const wrongProposalKey = Keypair.generate().publicKey + + const signature = await createAxiaSignature(wrongProposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expect(res.toString()).to.be.eq( + `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('cannot add signature with invalid signature', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const invalidSignature: number[] = Array.from({ length: 64 }, () => Math.floor(Math.random() * 256)) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, invalidSignature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expect(res.toString()).to.be.eq( + `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('cannot add signature from wrong axia pubkey', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const wrongAxia = Keypair.generate() + const signature = await createAxiaSignature(proposalKey, wrongAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, wrongAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) + }) + + it('cannot add signature with signature from different axia', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const axia2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Axia) + const signature = await createAxiaSignature(proposalKey, axia2) + + // Try to use axia2's signature but claim it's from whitelistedAxia + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expect(res.toString()).to.be.eq( + `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('cannot add signature with signature from validator instead of axia', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const validator = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + const signature = await createAxiaSignature(proposalKey, validator) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, validator.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) + }) + + it('cannot add signature if signed message is wrong', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + // Sign a different message (e.g., intent hash instead of proposal key) + const intentHash = generateIntentHash() + const signature = await signAsync(Buffer.from(intentHash, 'hex'), whitelistedAxia.secretKey.slice(0, 32)) + const signatureArray = Array.from(new Uint8Array(signature)) + + const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ + message: Buffer.from(intentHash, 'hex'), + publicKey: whitelistedAxia.publicKey.toBuffer(), + signature: Buffer.from(signatureArray), + }) + + const ix = await program.methods + .addAxiaSig() + .accountsPartial({ + solver: solverSdk.getSignerKey(), + solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), + proposal: proposalKey, + axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), + ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) + + expectTransactionError(res, `Signature verification failed`) + }) + + it('cannot add signature with corrupted ed25519 instruction', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + // Create corrupted Ed25519 instruction with wrong program ID + const corruptedEd25519Ix = new TransactionInstruction({ + programId: Keypair.generate().publicKey, + keys: [], + data: Buffer.from([ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + }) + + const ix = await program.methods + .addAxiaSig() + .accountsPartial({ + solver: solverSdk.getSignerKey(), + solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), + proposal: proposalKey, + axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), + ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + const res = await makeTxSignAndSend(solverProvider, corruptedEd25519Ix, ix) + + expect(res.toString()).to.be.eq( + // eslint-disable-next-line no-secrets/no-secrets + `FailedTransactionMetadata(FailedTransactionMetadata { err: InvalidProgramForExecution, meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('should add signature when proposal deadline is close', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + VERY_SHORT_DEADLINE + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs) + + const proposalAfter = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter.isSigned).to.be.true + }) }) }) }) From cc10fad36f503bfaf549e2fac8a1dccf9d1af86a Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 11:48:39 -0300 Subject: [PATCH 03/39] Settler: Proposal Lifecycle --- .../add_instructions_to_proposal.rs | 45 ++++++++ .../src/instructions/claim_stale_proposal.rs | 41 +++++++ .../src/instructions/create_proposal.rs | 100 ++++++++++++++++++ .../src/instructions/execute_proposal.rs | 89 ++++++++++++++++ .../programs/settler/src/state/proposal.rs | 81 ++++++++++++++ 5 files changed, 356 insertions(+) create mode 100644 packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs create mode 100644 packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs create mode 100644 packages/svm/programs/settler/src/instructions/create_proposal.rs create mode 100644 packages/svm/programs/settler/src/instructions/execute_proposal.rs create mode 100644 packages/svm/programs/settler/src/state/proposal.rs diff --git a/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs b/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs new file mode 100644 index 0000000..9755cee --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs @@ -0,0 +1,45 @@ +use anchor_lang::prelude::*; + +use crate::{ + errors::SettlerError, + state::{Proposal, ProposalInstruction}, +}; + +#[derive(Accounts)] +#[instruction(more_instructions: Vec)] +pub struct AddInstructionsToProposal<'info> { + #[account(mut)] + pub proposal_creator: Signer<'info>, + + #[account( + mut, + realloc = Proposal::extended_size(proposal.to_account_info().data_len(), &more_instructions)?, + realloc::payer = proposal_creator, + realloc::zero = true, + has_one = proposal_creator @ SettlerError::IncorrectProposalCreator + )] + // Any proposal + pub proposal: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn add_instructions_to_proposal( + ctx: Context, + more_instructions: Vec, + finalize: bool, +) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + let proposal = &mut ctx.accounts.proposal; + + require!(proposal.deadline > now, SettlerError::ProposalIsExpired); + require!(!proposal.is_final, SettlerError::ProposalIsFinal); + + proposal.instructions.extend_from_slice(&more_instructions); + + if finalize { + proposal.is_final = true; + } + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs b/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs new file mode 100644 index 0000000..25a7b9c --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs @@ -0,0 +1,41 @@ +use anchor_lang::prelude::*; + +use crate::{errors::SettlerError, state::Proposal}; + +#[derive(Accounts)] +pub struct ClaimStaleProposal<'info> { + #[account(mut)] + pub proposal_creator: Signer<'info>, + // + // remaining_accounts (N): + // + // #[account( + // mut, + // close = proposal_creator, + // has_one = proposal_creator @ SettlerError::IncorrectProposalCreator + // )] + // pub proposal_n: Box>, +} + +pub fn claim_stale_proposal<'info>( + ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposal<'info>>, +) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + let proposal_creator = ctx.accounts.proposal_creator.to_account_info(); + + for account_info in ctx.remaining_accounts { + let proposal: Box> = + Box::new(Account::::try_from(account_info)?); + + require_keys_eq!( + proposal.proposal_creator, + proposal_creator.key(), + SettlerError::IncorrectProposalCreator + ); + require!(now > proposal.deadline, SettlerError::ProposalNotYetExpired); + + proposal.close(ctx.accounts.proposal_creator.to_account_info())?; + } + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/create_proposal.rs b/packages/svm/programs/settler/src/instructions/create_proposal.rs new file mode 100644 index 0000000..c0e892d --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/create_proposal.rs @@ -0,0 +1,100 @@ +use anchor_lang::prelude::*; + +use crate::{ + errors::SettlerError, + state::{Intent, Proposal, ProposalInstruction}, + types::TokenFee, + whitelist::{ + accounts::EntityRegistry, + types::{EntityType, WhitelistStatus}, + }, +}; + +#[derive(Accounts)] +#[instruction(instructions: Vec, fees: Vec,)] +pub struct CreateProposal<'info> { + #[account(mut)] + pub solver: Signer<'info>, + + #[account( + seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], + bump = solver_registry.bump, + seeds::program = crate::whitelist::ID, + constraint = + solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + )] + pub solver_registry: Box>, + + /// Any intent + pub intent: Box>, + + #[account( + seeds = [b"fulfilled-intent", intent.intent_hash.as_ref()], + bump + )] + /// This PDA must be uninitialized + pub fulfilled_intent: SystemAccount<'info>, + + #[account( + init, + seeds = [b"proposal", intent.key().as_ref(), solver.key().as_ref()], + bump, + payer = solver, + space = Proposal::total_size(&instructions, fees.len())? + )] + pub proposal: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn create_proposal( + ctx: Context, + instructions: Vec, + fees: Vec, + deadline: u64, + is_final: bool, +) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + let intent = &ctx.accounts.intent; + + require!(deadline > now, SettlerError::DeadlineIsInThePast); + require!(intent.deadline > now, SettlerError::IntentIsExpired); + require!( + deadline <= intent.deadline, + SettlerError::ProposalDeadlineExceedsIntentDeadline + ); + require!( + intent.validations >= intent.min_validations, + SettlerError::InsufficientIntentValidations + ); + require!(intent.is_final, SettlerError::IntentIsNotFinal); + require!( + fees.len() == intent.max_fees.len(), + SettlerError::InvalidFeeMint + ); + + fees.iter() + .zip(&intent.max_fees) + .try_for_each(|(fee, max_fee)| { + require_keys_eq!(fee.mint, max_fee.mint, SettlerError::InvalidFeeMint); + require_gte!( + max_fee.amount, + fee.amount, + SettlerError::FeeAmountExceedsMaxFee + ); + Ok(()) + })?; + + let proposal = &mut ctx.accounts.proposal; + + proposal.intent = intent.key(); + proposal.proposal_creator = ctx.accounts.solver.key(); + proposal.deadline = deadline; + proposal.is_final = is_final; + proposal.is_signed = false; + proposal.instructions = instructions; + proposal.fees = fees; + proposal.bump = ctx.bumps.proposal; + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/execute_proposal.rs b/packages/svm/programs/settler/src/instructions/execute_proposal.rs new file mode 100644 index 0000000..a77fbbd --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/execute_proposal.rs @@ -0,0 +1,89 @@ +use anchor_lang::prelude::*; + +use crate::{ + errors::SettlerError, + state::{FulfilledIntent, Intent, Proposal}, + types::IntentEvent, + whitelist::{ + accounts::EntityRegistry, + types::{EntityType, WhitelistStatus}, + }, +}; + +#[derive(Accounts)] +pub struct ExecuteProposal<'info> { + #[account(mut)] + pub solver: Signer<'info>, + + #[account( + seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], + bump = solver_registry.bump, + seeds::program = crate::whitelist::ID, + constraint = + solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + )] + pub solver_registry: Box>, + + /// CHECK: account defined in proposal + #[account(mut)] + pub proposal_creator: UncheckedAccount<'info>, + + #[account( + mut, + has_one = intent @ SettlerError::IncorrectIntentForProposal, + has_one = proposal_creator @ SettlerError::IncorrectProposalCreator, + constraint = proposal.is_signed @ SettlerError::ProposalIsNotSigned, + close = proposal_creator + )] + pub proposal: Box>, + + /// CHECK: account defined in intent + #[account(mut)] + pub intent_creator: UncheckedAccount<'info>, + + #[account( + mut, + has_one = intent_creator @ SettlerError::IncorrectIntentCreator, + close = intent_creator + )] + pub intent: Box>, + + #[account( + init, + seeds = [b"fulfilled-intent", intent.intent_hash.as_ref()], + bump, + space = 8 + FulfilledIntent::INIT_SPACE, + payer = solver + )] + pub fulfilled_intent: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn execute_proposal(ctx: Context) -> Result<()> { + let now = Clock::get()?.unix_timestamp as u64; + let proposal = &ctx.accounts.proposal; + let intent = &ctx.accounts.intent; + + require!(proposal.deadline > now, SettlerError::ProposalIsExpired); + + // TODO: Execute proposal + + // TODO: Validate execution + + // TODO: Emit events + intent.events.iter().for_each(|event| { + emit!(IntentEventEvent { + event: event.clone() + }) + }); + + // TODO: Pay fees to Solver + + Ok(()) +} + +#[event] +pub struct IntentEventEvent { + event: IntentEvent, +} diff --git a/packages/svm/programs/settler/src/state/proposal.rs b/packages/svm/programs/settler/src/state/proposal.rs new file mode 100644 index 0000000..5487320 --- /dev/null +++ b/packages/svm/programs/settler/src/state/proposal.rs @@ -0,0 +1,81 @@ +use anchor_lang::prelude::*; + +use crate::{ + types::TokenFee, + utils::{add, mul, sub}, +}; + +#[account] +pub struct Proposal { + pub intent: Pubkey, + pub proposal_creator: Pubkey, + pub deadline: u64, + pub is_final: bool, + pub is_signed: bool, + pub instructions: Vec, + pub fees: Vec, + pub bump: u8, +} + +impl Proposal { + /// Doesn't take into account size of variable fields + pub const BASE_LEN: usize = + 32 + // intent + 32 + // proposal_creator + 8 + // deadline + 1 + // is_final + 1 + // is_signed + 1 // bump + ; + + pub fn total_size(instructions: &Vec, fees_len: usize) -> Result { + let size = add(8, Proposal::BASE_LEN)?; + let size = add(size, Proposal::instructions_size(instructions)?)?; + let size = add(size, Proposal::fees_size(fees_len)?)?; + Ok(size) + } + + pub fn instructions_size(instructions: &Vec) -> Result { + let sum = instructions + .iter() + .try_fold(0usize, |acc, ix| add(acc, ix.size()))?; + add(4, sum) + } + + pub fn fees_size(len: usize) -> Result { + add(4, mul(TokenFee::INIT_SPACE, len)?) + } + + pub fn extended_size( + size: usize, + more_instructions: &Vec, + ) -> Result { + sub( + add(size, Proposal::instructions_size(more_instructions)?)?, + 4, + ) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct ProposalInstruction { + pub program_id: Pubkey, + pub accounts: Vec, + pub data: Vec, +} + +impl ProposalInstruction { + pub fn size(&self) -> usize { + let accounts_size = 4 + self.accounts.len() * (32 + 1 + 1); + let data_size = 4 + self.data.len(); + + 32 + accounts_size + data_size + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct ProposalInstructionAccountMeta { + pub pubkey: Pubkey, + pub is_signer: bool, + pub is_writable: bool, +} From cf0e619b2c29ab1c256dbed09b034b2510f42295 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 11:50:23 -0300 Subject: [PATCH 04/39] Settler: Signatures --- .../settler/src/instructions/add_axia_sig.rs | 83 +++++++++++++ .../src/instructions/add_validator_sig.rs | 112 ++++++++++++++++++ .../instructions/change_whitelist_program.rs | 9 ++ 3 files changed, 204 insertions(+) create mode 100644 packages/svm/programs/settler/src/instructions/add_axia_sig.rs create mode 100644 packages/svm/programs/settler/src/instructions/add_validator_sig.rs create mode 100644 packages/svm/programs/settler/src/instructions/change_whitelist_program.rs diff --git a/packages/svm/programs/settler/src/instructions/add_axia_sig.rs b/packages/svm/programs/settler/src/instructions/add_axia_sig.rs new file mode 100644 index 0000000..afebef9 --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/add_axia_sig.rs @@ -0,0 +1,83 @@ +use anchor_lang::{ + prelude::{instruction::Instruction, sysvar::instructions::get_instruction_relative, *}, + solana_program::sysvar::instructions::ID as IX_ID, +}; + +use crate::{ + errors::SettlerError, + state::Proposal, + utils::{check_ed25519_ix, get_args_from_ed25519_ix_data, Ed25519Args}, + whitelist::{ + accounts::EntityRegistry, + types::{EntityType, WhitelistStatus}, + }, +}; + +#[derive(Accounts)] +pub struct AddAxiaSig<'info> { + #[account(mut)] + pub solver: Signer<'info>, + + #[account( + seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], + bump = solver_registry.bump, + seeds::program = crate::whitelist::ID, + constraint = + solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + )] + pub solver_registry: Box>, + + #[account( + seeds = [b"entity-registry", &[EntityType::Axia as u8 + 1], axia_registry.entity_pubkey.as_ref()], + bump = axia_registry.bump, + seeds::program = crate::whitelist::ID, + constraint = + axia_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::AxiaNotWhitelisted + )] + pub axia_registry: Box>, + + /// CHECK: Any proposal + #[account(mut)] + pub proposal: Box>, + + /// CHECK: The address check is needed because otherwise + /// the supplied Sysvar could be anything else. + #[account(address = IX_ID)] + pub ix_sysvar: AccountInfo<'info>, +} + +pub fn add_axia_sig(ctx: Context) -> Result<()> { + let proposal = &mut ctx.accounts.proposal; + + // NOP if already signed + if proposal.is_signed { + return Ok(()); + } + + let now = Clock::get()?.unix_timestamp as u64; + + require!(proposal.deadline > now, SettlerError::ProposalIsExpired); + require!(proposal.is_final, SettlerError::ProposalIsNotFinal); + + // Get Ed25519 instruction + let ed25519_ix: Instruction = get_instruction_relative(-1, &ctx.accounts.ix_sysvar)?; + let ed25519_ix_args: Ed25519Args = get_args_from_ed25519_ix_data(&ed25519_ix.data)?; + + // Verify correct program and accounts + check_ed25519_ix(&ed25519_ix)?; + + // Verify correct message was signed + if ed25519_ix_args.msg != proposal.key().as_array() { + return err!(SettlerError::SigVerificationFailed); + } + + // Verify pubkey is whitelisted Axia + if ed25519_ix_args.pubkey != &ctx.accounts.axia_registry.entity_pubkey.to_bytes() { + return err!(SettlerError::AxiaNotWhitelisted); + } + + // Updates proposal as signed + proposal.is_signed = true; + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/add_validator_sig.rs b/packages/svm/programs/settler/src/instructions/add_validator_sig.rs new file mode 100644 index 0000000..f08c3b6 --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/add_validator_sig.rs @@ -0,0 +1,112 @@ +use anchor_lang::{ + prelude::{instruction::Instruction, sysvar::instructions::get_instruction_relative, *}, + solana_program::sysvar::instructions::ID as IX_ID, +}; + +use crate::{ + errors::SettlerError, + state::Intent, + utils::{check_ed25519_ix, get_args_from_ed25519_ix_data, Ed25519Args}, + whitelist::{ + accounts::EntityRegistry, + types::{EntityType, WhitelistStatus}, + }, +}; + +#[derive(Accounts)] +pub struct AddValidatorSig<'info> { + #[account(mut)] + pub solver: Signer<'info>, + + #[account( + seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], + bump = solver_registry.bump, + seeds::program = crate::whitelist::ID, + constraint = + solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + )] + pub solver_registry: Box>, + + // Any Intent + #[account( + mut, + constraint = intent.is_final @ SettlerError::IntentIsNotFinal + )] + pub intent: Box>, + + #[account( + seeds = [b"fulfilled-intent", intent.intent_hash.as_ref()], + bump + )] + /// This PDA must be uninitialized + pub fulfilled_intent: SystemAccount<'info>, + + /// CHECK: other checks in ix body + #[account( + constraint = + validator_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::ValidatorNotWhitelisted + )] + pub validator_registry: Box>, + + /// CHECK: The address check is needed because otherwise + /// the supplied Sysvar could be anything else. + #[account(address = IX_ID)] + pub ix_sysvar: AccountInfo<'info>, +} + +pub fn add_validator_sig(ctx: Context) -> Result<()> { + // Verify Intent is not expired + let now = Clock::get()?.unix_timestamp as u64; + let intent = &mut ctx.accounts.intent; + + require!(intent.deadline > now, SettlerError::IntentIsExpired); + + // Get Ed25519 instruction + let ed25519_ix: Instruction = get_instruction_relative(-1, &ctx.accounts.ix_sysvar)?; + let ed25519_ix_args: Ed25519Args = get_args_from_ed25519_ix_data(&ed25519_ix.data)?; + + // Verify correct program and accounts + check_ed25519_ix(&ed25519_ix)?; + + // Verify correct message was signed + if ed25519_ix_args.msg != intent.intent_hash { + return err!(SettlerError::SigVerificationFailed); + } + + // Verify pubkey is a whitelisted Validator + require_keys_eq!( + ctx.accounts.validator_registry.key(), + Pubkey::create_program_address( + &[ + b"entity-registry", + &[EntityType::Validator as u8 + 1], + ed25519_ix_args.pubkey, + &[ctx.accounts.validator_registry.bump] + ], + &crate::whitelist::ID, + ) + .map_err(|_| SettlerError::ValidatorNotWhitelisted)?, + SettlerError::ValidatorNotWhitelisted, + ); + + // Updates intent PDA if signature not present and min_validations not met + + if intent.validators.len() == intent.min_validations as usize { + return Ok(()); + } + + let ed25519_pubkey = Pubkey::try_from_slice(ed25519_ix_args.pubkey)?; + + if intent.validators.contains(&ed25519_pubkey) { + return Ok(()); + } + + intent.validations = intent + .validations + .checked_add(1) + .ok_or(SettlerError::MathError)?; + + intent.validators.push(ed25519_pubkey); + + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/change_whitelist_program.rs b/packages/svm/programs/settler/src/instructions/change_whitelist_program.rs new file mode 100644 index 0000000..ca70398 --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/change_whitelist_program.rs @@ -0,0 +1,9 @@ +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct ChangeWhitelistProgram {} + +pub fn change_whitelist_program(ctx: Context) -> Result<()> { + // TODO: check against crate::whitelist::ID + Ok(()) +} From f5e81b93c57366aabb35ebceb33287846d917d62 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 12:01:18 -0300 Subject: [PATCH 05/39] 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 f1545be1a79e054c0dc1dc9396d0d0c64869da48 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 12:14:13 -0300 Subject: [PATCH 06/39] Trim tests to this PRs features --- .../programs/settler/src/instructions/mod.rs | 14 - packages/svm/programs/settler/src/lib.rs | 42 +- .../svm/programs/settler/src/state/mod.rs | 2 - packages/svm/tests/settler.test.ts | 1856 ----------------- 4 files changed, 1 insertion(+), 1913 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/mod.rs b/packages/svm/programs/settler/src/instructions/mod.rs index f335e7a..a1c371a 100644 --- a/packages/svm/programs/settler/src/instructions/mod.rs +++ b/packages/svm/programs/settler/src/instructions/mod.rs @@ -1,25 +1,11 @@ -pub mod add_axia_sig; -pub mod add_instructions_to_proposal; -pub mod add_validator_sig; -pub mod change_whitelist_program; pub mod claim_stale_intent; -pub mod claim_stale_proposal; pub mod create_intent; -pub mod create_proposal; -pub mod execute_proposal; pub mod extend_intent; pub mod initialize; pub mod set_paused_state; -pub use add_axia_sig::*; -pub use add_instructions_to_proposal::*; -pub use add_validator_sig::*; -pub use change_whitelist_program::*; pub use claim_stale_intent::*; -pub use claim_stale_proposal::*; pub use create_intent::*; -pub use create_proposal::*; -pub use execute_proposal::*; pub use extend_intent::*; pub use initialize::*; pub use set_paused_state::*; diff --git a/packages/svm/programs/settler/src/lib.rs b/packages/svm/programs/settler/src/lib.rs index 77058f3..50db194 100644 --- a/packages/svm/programs/settler/src/lib.rs +++ b/packages/svm/programs/settler/src/lib.rs @@ -10,42 +10,16 @@ pub mod state; pub mod types; pub mod utils; -use crate::{instructions::*, state::*, types::*}; +use crate::{instructions::*, types::*}; #[program] pub mod settler { use super::*; - pub fn add_axia_sig(ctx: Context) -> Result<()> { - instructions::add_axia_sig(ctx) - } - - pub fn add_instructions_to_proposal( - ctx: Context, - more_instructions: Vec, - finalize: bool, - ) -> Result<()> { - instructions::add_instructions_to_proposal(ctx, more_instructions, finalize) - } - - pub fn add_validator_sig(ctx: Context) -> Result<()> { - instructions::add_validator_sig(ctx) - } - - pub fn change_whitelist_program(ctx: Context) -> Result<()> { - instructions::change_whitelist_program(ctx) - } - pub fn claim_stale_intent(ctx: Context) -> Result<()> { instructions::claim_stale_intent(ctx) } - pub fn claim_stale_proposal<'info>( - ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposal<'info>>, - ) -> Result<()> { - instructions::claim_stale_proposal(ctx) - } - pub fn create_intent( ctx: Context, intent_hash: [u8; 32], @@ -74,20 +48,6 @@ pub mod settler { ) } - pub fn create_proposal( - ctx: Context, - instructions: Vec, - fees: Vec, - deadline: u64, - is_final: bool, - ) -> Result<()> { - instructions::create_proposal(ctx, instructions, fees, deadline, is_final) - } - - pub fn execute_proposal(ctx: Context) -> Result<()> { - instructions::execute_proposal(ctx) - } - pub fn extend_intent( ctx: Context, more_data: Option>, diff --git a/packages/svm/programs/settler/src/state/mod.rs b/packages/svm/programs/settler/src/state/mod.rs index fb0b3ba..a192198 100644 --- a/packages/svm/programs/settler/src/state/mod.rs +++ b/packages/svm/programs/settler/src/state/mod.rs @@ -1,9 +1,7 @@ pub mod fulfilled_intent; pub mod intent; -pub mod proposal; pub mod settler_settings; pub use fulfilled_intent::*; pub use intent::*; -pub use proposal::*; pub use settler_settings::*; diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 9ef7418..eed34ef 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -798,1861 +798,5 @@ describe('Settler Program', () => { expect(errorMsg.includes(`AccountNotInitialized`)).to.be.true }) }) - - describe('create_proposal', () => { - it('should create a proposal', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [ - { - pubkey: Keypair.generate().publicKey, - isSigner: false, - isWritable: true, - }, - ], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(solverProvider, ix) - if (res instanceof FailedTransactionMetadata) { - throw new Error(`Failed to create proposal: ${res.toString()}`) - } - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.intent.toString()).to.be.eq(sdk.getIntentKey(intentHash).toString()) - expect(proposal.proposalCreator.toString()).to.be.eq(solver.publicKey.toString()) - expect(proposal.deadline.toNumber()).to.be.eq(deadline) - expect(proposal.isFinal).to.be.true - expect(proposal.instructions.length).to.be.eq(1) - expect(proposal.instructions[0].programId.toString()).to.be.eq(instructions[0].programId.toString()) - expect(Buffer.from(proposal.instructions[0].data).toString('hex')).to.be.eq('deadbeef') - expect(proposal.instructions[0].accounts.length).to.be.eq(1) - expect(proposal.instructions[0].accounts[0].pubkey.toString()).to.be.eq( - instructions[0].accounts[0].pubkey.toString() - ) - expect(proposal.instructions[0].accounts[0].isSigner).to.be.eq(false) - expect(proposal.instructions[0].accounts[0].isWritable).to.be.eq(true) - }) - - it('should create a proposal with multiple instructions', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [ - { - pubkey: Keypair.generate().publicKey, - isSigner: false, - isWritable: true, - }, - ], - data: '010203', - }, - { - programId: Keypair.generate().publicKey, - accounts: [ - { - pubkey: Keypair.generate().publicKey, - isSigner: true, - isWritable: false, - }, - ], - data: '040506', - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(solverProvider, ix) - if (res instanceof FailedTransactionMetadata) { - throw new Error(`Failed to create proposal: ${res.toString()}`) - } - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.instructions.length).to.be.eq(2) - expect(Buffer.from(proposal.instructions[0].data).toString('hex')).to.be.eq('010203') - expect(Buffer.from(proposal.instructions[1].data).toString('hex')).to.be.eq('040506') - expect(proposal.isFinal).to.be.true - expect(proposal.instructions[0].accounts.length).to.be.eq(1) - expect(proposal.instructions[0].accounts[0].pubkey.toString()).to.be.eq( - instructions[0].accounts[0].pubkey.toString() - ) - expect(proposal.instructions[0].accounts[0].isSigner).to.be.eq(false) - expect(proposal.instructions[0].accounts[0].isWritable).to.be.eq(true) - expect(proposal.instructions[1].accounts.length).to.be.eq(1) - expect(proposal.instructions[1].accounts[0].pubkey.toString()).to.be.eq( - instructions[1].accounts[0].pubkey.toString() - ) - expect(proposal.instructions[1].accounts[0].isSigner).to.be.eq(true) - expect(proposal.instructions[1].accounts[0].isWritable).to.be.eq(false) - }) - - it('should create a proposal with empty instructions', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions: any[] = [] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(solverProvider, ix) - if (res instanceof FailedTransactionMetadata) { - throw new Error(`Failed to create proposal: ${res.toString()}`) - } - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.instructions.length).to.be.eq(0) - }) - - it('cannot create proposal if not whitelisted solver', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await maliciousSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(maliciousProvider, ix) - expectTransactionError(res, 'AccountNotInitialized') - }) - - it('cannot create proposal with deadline in the past', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now - SHORT_DEADLINE - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `Deadline must be in the future`) - }) - - it('cannot create proposal with deadline equal to now', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `Deadline must be in the future`) - }) - - it('cannot create proposal if intent deadline has passed', async () => { - const intentHash = generateIntentHash() - const nonce = generateNonce() - const user = Keypair.generate().publicKey - const now = Number(client.getClock().unixTimestamp) - const intentDeadline = now + SHORT_DEADLINE - - const params = { - op: OpType.Transfer, - user, - nonceHex: nonce, - deadline: intentDeadline, - minValidations: DEFAULT_MIN_VALIDATIONS, - dataHex: DEFAULT_DATA_HEX, - maxFees: [ - { - mint: Keypair.generate().publicKey, - amount: DEFAULT_MAX_FEE, - }, - ], - eventsHex: [], - } - - const ix = await solverSdk.createIntentIx(intentHash, params) - await makeTxSignAndSend(solverProvider, ix) - - // Set validations - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } - - warpSeconds(provider, 101) - - const proposalDeadline = now + 200 - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const ix2 = await solverSdk.createProposalIx(intentHash, instructions, [], proposalDeadline) - const res = await makeTxSignAndSend(solverProvider, ix2) - - expectTransactionError(res, `Intent has already expired`) - }) - - it('cannot create proposal if proposal deadline exceeds intent deadline', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intentDeadline = Number((await program.account.intent.fetch(sdk.getIntentKey(intentHash))).deadline) - const proposalDeadline = intentDeadline + SHORT_DEADLINE - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const ix = await solverSdk.createProposalIx(intentHash, instructions, [], proposalDeadline) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `Proposal deadline can't be after the Intent's deadline`) - }) - - it('cannot create proposal if intent has insufficient validations', async () => { - const intentHash = generateIntentHash() - const nonce = generateNonce() - const user = Keypair.generate().publicKey - const now = Number(client.getClock().unixTimestamp) - const deadline = now + INTENT_DEADLINE_OFFSET - - const params = { - op: OpType.Transfer, - user, - nonceHex: nonce, - deadline, - minValidations: 2, - dataHex: DEFAULT_DATA_HEX, - maxFees: [ - { - mint: Keypair.generate().publicKey, - amount: DEFAULT_MAX_FEE, - }, - ], - eventsHex: [], - } - - const ix = await solverSdk.createIntentIx(intentHash, params) - await makeTxSignAndSend(solverProvider, ix) - - // Set validations to 1 (less than min_validations of 2) - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } - - const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const ix2 = await solverSdk.createProposalIx(intentHash, instructions, [], proposalDeadline) - const res = await makeTxSignAndSend(solverProvider, ix2) - - expectTransactionError(res, `Intent has insufficient validations`) - }) - - it('cannot create proposal if intent is not final', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: false }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `Intent is not final`) - }) - - it('cannot create proposal if fulfilled_intent PDA already exists', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - // Mock FulfilledIntent - const fulfilledIntent = sdk.getFulfilledIntentKey(intentHash) - client.setAccount(fulfilledIntent, { - executable: false, - lamports: 1002240, - owner: program.programId, - data: Buffer.from('595168911b9267f7' + '010000000000000000', 'hex'), - }) - - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError( - res, - `AnchorError caused by account: fulfilled_intent. Error Code: AccountNotSystemOwned. Error Number: 3011. Error Message: The given account is not owned by the system program` - ) - }) - - it('cannot create proposal with same intent_hash and solver twice', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - await makeTxSignAndSend(solverProvider, ix) - - client.expireBlockhash() - const ix2 = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline) - const res = await makeTxSignAndSend(solverProvider, ix2) - - expectTransactionError(res, `already in use`) - }) - - it('cannot create proposal for non-existent intent', async () => { - const intentHash = generateIntentHash() - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const ix = await solverSdk.createProposalIx(intentHash, instructions, [], deadline) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `AccountNotInitialized`) - }) - - it('should create proposal with fees matching intent max_fees', async () => { - const intentHash = generateIntentHash() - const nonce = generateNonce() - const user = Keypair.generate().publicKey - const now = Number(client.getClock().unixTimestamp) - const deadline = now + INTENT_DEADLINE_OFFSET - const mint = Keypair.generate().publicKey - - const params = { - op: OpType.Transfer, - user, - nonceHex: nonce, - deadline, - minValidations: DEFAULT_MIN_VALIDATIONS, - dataHex: DEFAULT_DATA_HEX, - maxFees: [ - { - mint, - amount: DEFAULT_MAX_FEE, - }, - ], - eventsHex: [], - } - - const ix = await solverSdk.createIntentIx(intentHash, params, true) - await makeTxSignAndSend(solverProvider, ix) - - // Set validations - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } - - const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = [ - { - mint, - amount: DEFAULT_MAX_FEE_HALF, - }, - ] - - const proposalIx = await solverSdk.createProposalIx(intentHash, instructions, fees, proposalDeadline) - await makeTxSignAndSend(solverProvider, proposalIx) - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.fees.length).to.be.eq(1) - expect(proposal.fees[0].mint.toString()).to.be.eq(mint.toString()) - expect(proposal.fees[0].amount.toNumber()).to.be.eq(DEFAULT_MAX_FEE_HALF) - }) - - it('cannot create proposal with fees exceeding max_fees', async () => { - const intentHash = generateIntentHash() - const nonce = generateNonce() - const user = Keypair.generate().publicKey - const now = Number(client.getClock().unixTimestamp) - const deadline = now + INTENT_DEADLINE_OFFSET - const mint = Keypair.generate().publicKey - - const params = { - op: OpType.Transfer, - user, - nonceHex: nonce, - deadline, - minValidations: DEFAULT_MIN_VALIDATIONS, - dataHex: DEFAULT_DATA_HEX, - maxFees: [ - { - mint, - amount: DEFAULT_MAX_FEE, - }, - ], - eventsHex: [], - } - - const ix = await solverSdk.createIntentIx(intentHash, params, true) - await makeTxSignAndSend(solverProvider, ix) - - // Set validations - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } - - const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = [ - { - mint, - amount: DEFAULT_MAX_FEE_EXCEED, // Exceeds max_fee - }, - ] - - const proposalIx = await solverSdk.createProposalIx(intentHash, instructions, fees, proposalDeadline) - const res = await makeTxSignAndSend(solverProvider, proposalIx) - - expect(res).to.be.instanceOf(FailedTransactionMetadata) - expect(res.toString()).to.match(/FeeAmountExceedsMaxFee|Fee amount exceeds max fee/i) - }) - - it('cannot create proposal with fees having wrong mint', async () => { - const intentHash = generateIntentHash() - const nonce = generateNonce() - const user = Keypair.generate().publicKey - const now = Number(client.getClock().unixTimestamp) - const deadline = now + INTENT_DEADLINE_OFFSET - const mint = Keypair.generate().publicKey - const wrongMint = Keypair.generate().publicKey - - const params = { - op: OpType.Transfer, - user, - nonceHex: nonce, - deadline, - minValidations: DEFAULT_MIN_VALIDATIONS, - dataHex: DEFAULT_DATA_HEX, - maxFees: [ - { - mint, - amount: DEFAULT_MAX_FEE, - }, - ], - eventsHex: [], - } - - const ix = await solverSdk.createIntentIx(intentHash, params, true) - await makeTxSignAndSend(solverProvider, ix) - - // Set validations - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } - - const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = [ - { - mint: wrongMint, // Wrong mint - amount: DEFAULT_MAX_FEE_HALF, - }, - ] - - const proposalIx = await solverSdk.createProposalIx(intentHash, instructions, fees, proposalDeadline) - const res = await makeTxSignAndSend(solverProvider, proposalIx) - - expect(res).to.be.instanceOf(FailedTransactionMetadata) - expect(res.toString()).to.match(/InvalidFeeMint|Invalid fee mint/i) - }) - }) - - describe('add_instructions_to_proposal', () => { - const createTestProposal = async (isFinal = false): Promise => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [ - { - pubkey: Keypair.generate().publicKey, - isSigner: false, - isWritable: true, - }, - ], - data: DEFAULT_DATA_HEX, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, isFinal) - await makeTxSignAndSend(solverProvider, ix) - return intentHash - } - - it('should add instructions to proposal', async () => { - const intentHash = await createTestProposal(false) - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [ - { - pubkey: Keypair.generate().publicKey, - isSigner: false, - isWritable: true, - }, - ], - data: '040506', - }, - ] - - const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions, false) - await makeTxSignAndSend(solverProvider, ix) - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.instructions.length).to.be.eq(2) - expect(Buffer.from(proposal.instructions[0].data).toString('hex')).to.be.eq('010203') - expect(Buffer.from(proposal.instructions[1].data).toString('hex')).to.be.eq('040506') - expect(proposal.isFinal).to.be.false - expect(proposal.instructions[1].accounts.length).to.be.eq(1) - expect(proposal.instructions[1].accounts[0].pubkey.toString()).to.be.eq( - moreInstructions[0].accounts[0].pubkey.toString() - ) - expect(proposal.instructions[1].accounts[0].isSigner).to.be.eq(false) - expect(proposal.instructions[1].accounts[0].isWritable).to.be.eq(true) - }) - - it('should add multiple instructions to proposal', async () => { - const intentHash = await createTestProposal(false) - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '070809', - }, - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '0a0b0c', - }, - ] - - const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions, false) - await makeTxSignAndSend(solverProvider, ix) - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.instructions.length).to.be.eq(3) - expect(Buffer.from(proposal.instructions[1].data).toString('hex')).to.be.eq('070809') - expect(Buffer.from(proposal.instructions[2].data).toString('hex')).to.be.eq('0a0b0c') - expect(proposal.isFinal).to.be.false - }) - - it('should add instructions to proposal multiple times', async () => { - const intentHash = await createTestProposal(false) - - const moreInstructions1 = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '0d0e0f', - }, - ] - const ix1 = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions1, false) - await makeTxSignAndSend(solverProvider, ix1) - - const moreInstructions2 = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '101112', - }, - ] - const ix2 = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions2, false) - await makeTxSignAndSend(solverProvider, ix2) - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.instructions.length).to.be.eq(3) - expect(Buffer.from(proposal.instructions[1].data).toString('hex')).to.be.eq('0d0e0f') - expect(Buffer.from(proposal.instructions[2].data).toString('hex')).to.be.eq('101112') - expect(proposal.isFinal).to.be.false - }) - - it('cannot add instructions if not proposal creator', async () => { - const intentHash = await createTestProposal(false) - const proposalCreator = (await program.account.proposal.fetch(solverSdk.getProposalKey(intentHash))) - .proposalCreator - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '131415', - }, - ] - - const ix = await maliciousSdk.addInstructionsToProposalIx( - intentHash, - moreInstructions, - undefined, - proposalCreator - ) - const res = await makeTxSignAndSend(maliciousProvider, ix) - - expectTransactionError(res, `Signer must be proposal creator`) - }) - - it('cannot add instructions to non-existent proposal', async () => { - const intentHash = generateIntentHash() - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '161718', - }, - ] - - const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `AccountNotInitialized`) - }) - - it('cannot add instructions if proposal deadline has passed', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + STALE_CLAIM_DELAY - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '010203', - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) - await makeTxSignAndSend(solverProvider, ix) - - warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '19202a', - }, - ] - - const ix2 = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) - const res = await makeTxSignAndSend(solverProvider, ix2) - - expectTransactionError(res, 'Proposal has already expired') - }) - - it('cannot add instructions if proposal deadline equals now', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + SHORT_DEADLINE - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '010203', - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) - await makeTxSignAndSend(solverProvider, ix) - - warpSeconds(provider, WARP_TIME_SHORT) - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '1b1c1d', - }, - ] - - const ix2 = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) - const res = await makeTxSignAndSend(solverProvider, ix2) - - expectTransactionError(res, 'Proposal has already expired') - }) - - it('cannot add instructions if proposal is final', async () => { - const intentHash = await createTestProposal(true) - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '1e1f20', - }, - ] - - const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `Proposal is already final`) - }) - - it('should finalize proposal when adding instructions with finalize=true', async () => { - const intentHash = await createTestProposal(false) - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '212223', - }, - ] - - const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions, true) - await makeTxSignAndSend(solverProvider, ix) - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.isFinal).to.be.true - expect(proposal.instructions.length).to.be.eq(2) - }) - - it('should not finalize proposal when adding instructions with finalize=false', async () => { - const intentHash = await createTestProposal(false) - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '242526', - }, - ] - - const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions, false) - await makeTxSignAndSend(solverProvider, ix) - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.isFinal).to.be.false - expect(proposal.instructions.length).to.be.eq(2) - }) - - it('should finalize proposal by default when adding instructions', async () => { - const intentHash = await createTestProposal(false) - - const moreInstructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: '272829', - }, - ] - - const ix = await solverSdk.addInstructionsToProposalIx(intentHash, moreInstructions) - await makeTxSignAndSend(solverProvider, ix) - - const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposal.isFinal).to.be.true - expect(proposal.instructions.length).to.be.eq(2) - }) - }) - - describe('claim_stale_proposal', () => { - const createTestProposalWithDeadline = async (deadline: number): Promise => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [ - { - pubkey: Keypair.generate().publicKey, - isSigner: false, - isWritable: true, - }, - ], - data: DEFAULT_DATA_HEX, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) - await makeTxSignAndSend(solverProvider, ix) - return intentHash - } - - it('should claim stale proposal', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + STALE_CLAIM_DELAY - const intentHash = await createTestProposalWithDeadline(deadline) - - const proposalBefore = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposalBefore).to.not.be.null - - warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) - - const proposalBalanceBefore = - Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0 - const proposalCreatorBalanceBefore = Number(provider.client.getBalance(proposalBefore.proposalCreator)) || 0 - - const ix = await solverSdk.claimStaleProposalIx([intentHash]) - await makeTxSignAndSend(solverProvider, ix) - - const proposalBalanceAfter = - Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0 - const proposalCreatorBalanceAfter = Number(provider.client.getBalance(proposalBefore.proposalCreator)) || 0 - - try { - await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect.fail('Proposal account should be closed') - } catch (error: any) { - expect(error.message).to.include(`Account does not exist`) - } - - expect(proposalCreatorBalanceAfter).to.be.eq( - proposalCreatorBalanceBefore + proposalBalanceBefore - ACCOUNT_CLOSE_FEE - ) - expect(proposalBalanceAfter).to.be.eq(0) - }) - - it('should claim multiple stale proposals', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + STALE_CLAIM_DELAY - const intentHashes = await Promise.all( - Array.from({ length: 20 }, async () => await createTestProposalWithDeadline(deadline)) - ) - - for (const intentHash of intentHashes) { - const proposalBefore = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposalBefore).to.not.be.null - } - - warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) - - const proposalBalancesBefore = intentHashes.reduce( - (acc, intentHash) => - acc + Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0, - 0 - ) - const proposalCreatorBalanceBefore = Number(provider.client.getBalance(solver.publicKey)) || 0 - - const ix = await solverSdk.claimStaleProposalIx(intentHashes) - await makeTxSignAndSend(solverProvider, ix) - - const proposalBalancesAfter = intentHashes.reduce( - (acc, intentHash) => - acc + Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0, - 0 - ) - const proposalCreatorBalanceAfter = Number(provider.client.getBalance(solver.publicKey)) || 0 - - for (const intentHash of intentHashes) { - try { - await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect.fail('Proposal account should be closed') - } catch (error: any) { - expect(error.message).to.include(`Account does not exist`) - } - } - - expect(proposalCreatorBalanceAfter).to.be.eq( - proposalCreatorBalanceBefore + proposalBalancesBefore - ACCOUNT_CLOSE_FEE - ) - expect(proposalBalancesAfter).to.be.eq(0) - }) - - it('cannot claim proposal if deadline has not passed', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + LONG_DEADLINE - const intentHash = await createTestProposalWithDeadline(deadline) - - warpSeconds(provider, WARP_TIME_SHORT) - - const ix = await solverSdk.claimStaleProposalIx([intentHash]) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `Proposal not yet expired`) - }) - - it('cannot claim proposal if deadline equals now', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + MEDIUM_DEADLINE - const intentHash = await createTestProposalWithDeadline(deadline) - - warpSeconds(provider, MEDIUM_DEADLINE) - - const ix = await solverSdk.claimStaleProposalIx([intentHash]) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `Proposal not yet expired`) - }) - - it('cannot claim stale proposal if not proposal creator', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + EXPIRATION_TEST_DELAY - const intentHash = await createTestProposalWithDeadline(deadline) - - warpSeconds(provider, EXPIRATION_TEST_DELAY_PLUS_ONE) - - const ix = await maliciousSdk.claimStaleProposalIx([intentHash], solver.publicKey) - const res = await makeTxSignAndSend(maliciousProvider, ix) - - expectTransactionError(res, `Signer must be proposal creator`) - }) - - it('cannot claim non-existent proposal', async () => { - const intentHash = generateIntentHash() - - const ix = await solverSdk.claimStaleProposalIx([intentHash]) - const res = await makeTxSignAndSend(solverProvider, ix) - - expectTransactionError(res, `AccountNotInitialized`) - }) - - it('cannot claim proposal twice', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + DOUBLE_CLAIM_DELAY - const intentHash = await createTestProposalWithDeadline(deadline) - - warpSeconds(provider, DOUBLE_CLAIM_DELAY_PLUS_ONE) - - const ix = await solverSdk.claimStaleProposalIx([intentHash]) - await makeTxSignAndSend(solverProvider, ix) - - client.expireBlockhash() - const ix2 = await solverSdk.claimStaleProposalIx([intentHash]) - const res = await makeTxSignAndSend(solverProvider, ix2) - - const errorMsg = res.toString() - expect(errorMsg.includes(`AccountNotInitialized`)).to.be.true - }) - }) - - describe('add_validator_sigs', () => { - let whitelistedValidator: Keypair - - before(async () => { - whitelistedValidator = Keypair.generate() - const whitelistValidatorIx = await whitelistSdk.setEntityWhitelistStatusIx( - EntityType.Validator, - whitelistedValidator.publicKey, - WhitelistStatus.Whitelisted - ) - await makeTxSignAndSend(provider, whitelistValidatorIx) - }) - - it('should add validator signature successfully', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const intentBefore = await program.account.intent.fetch(intentKey) - expect(intentBefore.validations).to.be.eq(0) - expect(intentBefore.validators.length).to.be.eq(0) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - await makeTxSignAndSend(solverProvider, ...ixs) - - const intentAfter = await program.account.intent.fetch(intentKey) - expect(intentAfter.validations).to.be.eq(1) - expect(intentAfter.validators.length).to.be.eq(1) - expect(intentAfter.validators[0].toString()).to.be.eq(whitelistedValidator.publicKey.toString()) - }) - - it('should add multiple validator signatures', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - minValidations: MULTIPLE_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const validator1 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - const validator2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - const validator3 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - - const signature1 = await createValidatorSignature(intentHash, validator1) - const ixs1 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator1.publicKey, - signature1 - ) - await makeTxSignAndSend(solverProvider, ...ixs1) - - const intentAfter1 = await program.account.intent.fetch(intentKey) - expect(intentAfter1.validations).to.be.eq(1) - expect(intentAfter1.validators.length).to.be.eq(1) - - const signature2 = await createValidatorSignature(intentHash, validator2) - const ixs2 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator2.publicKey, - signature2 - ) - await makeTxSignAndSend(solverProvider, ...ixs2) - - const intentAfter2 = await program.account.intent.fetch(intentKey) - expect(intentAfter2.validations).to.be.eq(2) - expect(intentAfter2.validators.length).to.be.eq(2) - - const signature3 = await createValidatorSignature(intentHash, validator3) - const ixs3 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator3.publicKey, - signature3 - ) - await makeTxSignAndSend(solverProvider, ...ixs3) - - const intentAfter3 = await program.account.intent.fetch(intentKey) - expect(intentAfter3.validations).to.be.eq(3) - expect(intentAfter3.validators.length).to.be.eq(3) - expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator1.publicKey.toString()) - expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator2.publicKey.toString()) - expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator3.publicKey.toString()) - }) - - it('should handle duplicate validator signature gracefully', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { minValidations: 2, isFinal: true }) - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs1 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - await makeTxSignAndSend(solverProvider, ...ixs1) - - const intentAfter1 = await program.account.intent.fetch(intentKey) - expect(intentAfter1.validations).to.be.eq(1) - expect(intentAfter1.validators.length).to.be.eq(1) - - client.expireBlockhash() - const ixs2 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - await makeTxSignAndSend(solverProvider, ...ixs2) - - const intentAfter2 = await program.account.intent.fetch(intentKey) - expect(intentAfter2.validations).to.be.eq(1) - expect(intentAfter2.validators.length).to.be.eq(1) - }) - - it('should handle when min_validations already met', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const validator1 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - const validator2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - - const signature1 = await createValidatorSignature(intentHash, validator1) - const ixs1 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator1.publicKey, - signature1 - ) - await makeTxSignAndSend(solverProvider, ...ixs1) - - const intentAfter1 = await program.account.intent.fetch(intentKey) - expect(intentAfter1.validations).to.be.eq(1) - expect(intentAfter1.validators.length).to.be.eq(1) - expect(intentAfter1.minValidations).to.be.eq(1) - - client.expireBlockhash() - const signature2 = await createValidatorSignature(intentHash, validator2) - const ixs2 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator2.publicKey, - signature2 - ) - await makeTxSignAndSend(solverProvider, ...ixs2) - - const intentAfter2 = await program.account.intent.fetch(intentKey) - expect(intentAfter2.validations).to.be.eq(1) - expect(intentAfter2.validators.length).to.be.eq(1) - expect(intentAfter2.validators[0].toString()).to.be.eq(validator1.publicKey.toString()) - }) - - it('cannot add signature if intent is not final', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: false, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `Intent is not final`) - }) - - it('cannot add signature if intent has expired', async () => { - const intentHash = generateIntentHash() - const nonce = generateNonce() - const user = Keypair.generate().publicKey - const now = Number(client.getClock().unixTimestamp) - const deadline = now + SHORT_DEADLINE - - const params = { - op: OpType.Transfer, - user, - nonceHex: nonce, - deadline, - minValidations: DEFAULT_MIN_VALIDATIONS, - dataHex: DEFAULT_DATA_HEX, - maxFees: [ - { - mint: Keypair.generate().publicKey, - amount: DEFAULT_MAX_FEE, - }, - ], - eventsHex: [], - } - - const ix = await solverSdk.createIntentIx(intentHash, params, true) - await makeTxSignAndSend(solverProvider, ix) - - const intentKey = sdk.getIntentKey(intentHash) - - warpSeconds(provider, 101) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `Intent has already expired`) - }) - - it('cannot add signature if validator is not whitelisted', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const validator = Keypair.generate() - - const signature = await createValidatorSignature(intentHash, validator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError( - res, - `AnchorError caused by account: validator_registry. Error Code: AccountNotInitialized.` - ) - }) - - it('cannot add signature if solver is not whitelisted', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs = await maliciousSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(maliciousProvider, ...ixs) - - expectTransactionError( - res, - `AnchorError caused by account: solver_registry. Error Code: AccountNotInitialized.` - ) - }) - - it('cannot add signature for non-existent intent', async () => { - const intentHash = generateIntentHash() - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ - message: Buffer.from(intentHash, 'hex'), - publicKey: whitelistedValidator.publicKey.toBuffer(), - signature: Buffer.from(signature), - }) - - const ix = await program.methods - .addValidatorSig() - .accountsPartial({ - solver: solverSdk.getSignerKey(), - solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), - intent: intentKey, - fulfilledIntent: solverSdk.getFulfilledIntentKey(intentHash), - validatorRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Validator, whitelistedValidator.publicKey), - ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) - - expectTransactionError(res, `AccountNotInitialized`) - }) - - it('cannot add signature if fulfilled_intent PDA already exists', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const fulfilledIntent = sdk.getFulfilledIntentKey(intentHash) - client.setAccount(fulfilledIntent, { - executable: false, - lamports: 1002240, - owner: program.programId, - data: Buffer.from('595168911b9267f7' + '010000000000000000', 'hex'), - }) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError( - res, - `AnchorError caused by account: fulfilled_intent. Error Code: AccountNotSystemOwned. Error Number: 3011. Error Message: The given account is not owned by the system program` - ) - }) - - it('cannot add signature with wrong intent hash', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const wrongIntentHash = generateIntentHash() - const signature = await createValidatorSignature(wrongIntentHash, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(wrongIntentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `Signature verification failed`) - }) - - it('cannot add signature with invalid signature', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const invalidSignature: number[] = Array.from({ length: 64 }, () => Math.floor(Math.random() * 256)) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - invalidSignature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expect(res.toString()).to.be.eq( - `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('cannot add valid signature but for another intent', async () => { - const intentHash1 = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey1 = sdk.getIntentKey(intentHash1) - - const intentHash2 = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - - const signature = await createValidatorSignature(intentHash2, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey1, - Buffer.from(intentHash2, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `Signature verification failed`) - }) - }) - - describe('add_axia_sig', () => { - let whitelistedAxia: Keypair - - before(async () => { - whitelistedAxia = Keypair.generate() - const whitelistAxiaIx = await whitelistSdk.setEntityWhitelistStatusIx( - EntityType.Axia, - whitelistedAxia.publicKey, - WhitelistStatus.Whitelisted - ) - await makeTxSignAndSend(provider, whitelistAxiaIx) - }) - - it('should add axia signature successfully', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const proposalBefore = await program.account.proposal.fetch(proposalKey) - expect(proposalBefore.isSigned).to.be.false - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs) - - const proposalAfter = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter.isSigned).to.be.true - }) - - it('should handle duplicate signature gracefully', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs1 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs1) - - const proposalAfter1 = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter1.isSigned).to.be.true - - client.expireBlockhash() - const ixs2 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs2) - - const proposalAfter2 = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter2.isSigned).to.be.true - }) - - it('should add signature multiple times safely', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs1 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs1) - - client.expireBlockhash() - const ixs2 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs2) - - client.expireBlockhash() - const ixs3 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs3) - - const proposalAfter = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter.isSigned).to.be.true - }) - - it('cannot add signature if proposal is not final', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) - await makeTxSignAndSend(solverProvider, ix) - - const proposalKey = sdk.getProposalKey(intentHash, solver.publicKey) - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, 'Proposal is not final') - }) - - it('cannot add signature if proposal has expired', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + STALE_CLAIM_DELAY - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) - - warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, 'Proposal has already expired') - }) - - it('cannot add signature if axia is not whitelisted', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const axia = Keypair.generate() - const signature = await createAxiaSignature(proposalKey, axia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, axia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) - }) - - it('cannot add signature if solver is not whitelisted', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await maliciousSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(maliciousProvider, ...ixs) - - expectTransactionError( - res, - `AnchorError caused by account: solver_registry. Error Code: AccountNotInitialized.` - ) - }) - - it('cannot add signature for non-existent proposal', async () => { - const proposalKey = Keypair.generate().publicKey - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ - message: proposalKey.toBuffer(), - publicKey: whitelistedAxia.publicKey.toBuffer(), - signature: Buffer.from(signature), - }) - - const ix = await program.methods - .addAxiaSig() - .accountsPartial({ - solver: solverSdk.getSignerKey(), - solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), - proposal: proposalKey, - axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), - ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) - - expectTransactionError( - res, - `Program log: AnchorError caused by account: proposal. Error Code: AccountNotInitialized` - ) - }) - - it('cannot add signature if proposal deadline equals now', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + SHORT_DEADLINE - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) - - warpSeconds(provider, WARP_TIME_SHORT) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, 'Proposal has already expired') - }) - - it('cannot add signature with wrong proposal key', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - const wrongProposalKey = Keypair.generate().publicKey - - const signature = await createAxiaSignature(wrongProposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expect(res.toString()).to.be.eq( - `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('cannot add signature with invalid signature', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const invalidSignature: number[] = Array.from({ length: 64 }, () => Math.floor(Math.random() * 256)) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, invalidSignature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expect(res.toString()).to.be.eq( - `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('cannot add signature from wrong axia pubkey', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const wrongAxia = Keypair.generate() - const signature = await createAxiaSignature(proposalKey, wrongAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, wrongAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) - }) - - it('cannot add signature with signature from different axia', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const axia2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Axia) - const signature = await createAxiaSignature(proposalKey, axia2) - - // Try to use axia2's signature but claim it's from whitelistedAxia - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expect(res.toString()).to.be.eq( - `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('cannot add signature with signature from validator instead of axia', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const validator = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - const signature = await createAxiaSignature(proposalKey, validator) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, validator.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) - }) - - it('cannot add signature if signed message is wrong', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - // Sign a different message (e.g., intent hash instead of proposal key) - const intentHash = generateIntentHash() - const signature = await signAsync(Buffer.from(intentHash, 'hex'), whitelistedAxia.secretKey.slice(0, 32)) - const signatureArray = Array.from(new Uint8Array(signature)) - - const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ - message: Buffer.from(intentHash, 'hex'), - publicKey: whitelistedAxia.publicKey.toBuffer(), - signature: Buffer.from(signatureArray), - }) - - const ix = await program.methods - .addAxiaSig() - .accountsPartial({ - solver: solverSdk.getSignerKey(), - solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), - proposal: proposalKey, - axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), - ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) - - expectTransactionError(res, `Signature verification failed`) - }) - - it('cannot add signature with corrupted ed25519 instruction', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - // Create corrupted Ed25519 instruction with wrong program ID - const corruptedEd25519Ix = new TransactionInstruction({ - programId: Keypair.generate().publicKey, - keys: [], - data: Buffer.from([ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ]), - }) - - const ix = await program.methods - .addAxiaSig() - .accountsPartial({ - solver: solverSdk.getSignerKey(), - solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), - proposal: proposalKey, - axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), - ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - const res = await makeTxSignAndSend(solverProvider, corruptedEd25519Ix, ix) - - expect(res.toString()).to.be.eq( - // eslint-disable-next-line no-secrets/no-secrets - `FailedTransactionMetadata(FailedTransactionMetadata { err: InvalidProgramForExecution, meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('should add signature when proposal deadline is close', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + VERY_SHORT_DEADLINE - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs) - - const proposalAfter = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter.isSigned).to.be.true - }) - }) }) }) From 9bd0e0e295b9719ed5a66fa8704b6476bd2ba274 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 12:15:10 -0300 Subject: [PATCH 07/39] Rm signatures utils as not relevant in this PR --- .../svm/programs/settler/src/utils/mod.rs | 2 - .../svm/programs/settler/src/utils/sigs.rs | 75 ------------------- 2 files changed, 77 deletions(-) delete mode 100644 packages/svm/programs/settler/src/utils/sigs.rs diff --git a/packages/svm/programs/settler/src/utils/mod.rs b/packages/svm/programs/settler/src/utils/mod.rs index bd9ec41..ed1f5ec 100644 --- a/packages/svm/programs/settler/src/utils/mod.rs +++ b/packages/svm/programs/settler/src/utils/mod.rs @@ -1,5 +1,3 @@ pub mod math; -pub mod sigs; pub use math::*; -pub use sigs::*; diff --git a/packages/svm/programs/settler/src/utils/sigs.rs b/packages/svm/programs/settler/src/utils/sigs.rs deleted file mode 100644 index a5ce59b..0000000 --- a/packages/svm/programs/settler/src/utils/sigs.rs +++ /dev/null @@ -1,75 +0,0 @@ -use anchor_lang::prelude::{instruction::Instruction, *}; - -use crate::errors::SettlerError; - -pub fn check_ed25519_ix(ix: &Instruction) -> Result<()> { - if ix.program_id.to_string() != "Ed25519SigVerify111111111111111111111111111" - || ix.accounts.len() != 0 - { - return err!(SettlerError::SigVerificationFailed); - } - - Ok(()) -} - -pub struct Ed25519Args<'a> { - pub pubkey: &'a [u8; 32], - pub sig: &'a [u8; 64], - pub msg: &'a [u8], -} - -pub fn get_args_from_ed25519_ix_data(data: &[u8]) -> Result> { - if data.len() < 112 { - return err!(SettlerError::SigVerificationFailed); - } - - // Header - let num_signatures = &[data[0]]; - let padding = &[data[1]]; - let signature_offset = &data[2..=3]; - let signature_instruction_index = &data[4..=5]; - let public_key_offset = &data[6..=7]; - let public_key_instruction_index = &data[8..=9]; - let message_data_offset = &data[10..=11]; - let message_data_size = &data[12..=13]; - let message_instruction_index = &data[14..=15]; - - // Data - let pubkey = &data[16..16 + 32]; - let sig = &data[48..48 + 64]; - let msg = &data[112..]; - - // Expected values - let exp_public_key_offset: u16 = 16; // 2*u8 + 7*u16 - let exp_signature_offset: u16 = exp_public_key_offset + 32_u16; - let exp_message_data_offset: u16 = exp_signature_offset + 64_u16; - let exp_num_signatures: u8 = 1; - let exp_message_data_size: u16 = msg - .len() - .try_into() - .map_err(|_| SettlerError::SigVerificationFailed)?; - - // Header - if num_signatures != &exp_num_signatures.to_le_bytes() - || padding != &[0] - || signature_offset != &exp_signature_offset.to_le_bytes() - || signature_instruction_index != &u16::MAX.to_le_bytes() - || public_key_offset != &exp_public_key_offset.to_le_bytes() - || public_key_instruction_index != &u16::MAX.to_le_bytes() - || message_data_offset != &exp_message_data_offset.to_le_bytes() - || message_data_size != &exp_message_data_size.to_le_bytes() - || message_instruction_index != &u16::MAX.to_le_bytes() - { - return err!(SettlerError::SigVerificationFailed); - } - - Ok(Ed25519Args { - pubkey: pubkey - .try_into() - .map_err(|_| SettlerError::SigVerificationFailed)?, - sig: sig - .try_into() - .map_err(|_| SettlerError::SigVerificationFailed)?, - msg, - }) -} From ea311183cf6f092ccccdf2d1ac7f8966d008fc1c Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 12:18:43 -0300 Subject: [PATCH 08/39] Trim SDK to match this PRs features --- packages/svm/sdks/settler/Settler.ts | 151 +----------------- packages/svm/sdks/settler/types.ts | 12 -- packages/svm/tests/helpers/settler-helpers.ts | 73 +-------- packages/svm/tests/settler.test.ts | 22 +-- 4 files changed, 5 insertions(+), 253 deletions(-) diff --git a/packages/svm/sdks/settler/Settler.ts b/packages/svm/sdks/settler/Settler.ts index 2d0978b..6925901 100644 --- a/packages/svm/sdks/settler/Settler.ts +++ b/packages/svm/sdks/settler/Settler.ts @@ -4,15 +4,7 @@ import * as SettlerIDL from '../../target/idl/settler.json' import * as WhitelistIDL from '../../target/idl/whitelist.json' import { Settler } from '../../target/types/settler' import { EntityType } from '../whitelist/Whitelist' -import { - CreateIntentParams, - ExtendIntentParams, - IntentEvent, - OpType, - ProposalInstruction, - ProposalInstructionAccountMeta, - TokenFee, -} from './types' +import { CreateIntentParams, ExtendIntentParams, IntentEvent, OpType, TokenFee } from './types' type TokenFeeAnchor = { mint: web3.PublicKey @@ -24,12 +16,6 @@ type IntentEventAnchor = { data: Buffer } -type ProposalInstructionAnchor = { - programId: web3.PublicKey - accounts: ProposalInstructionAccountMeta[] - data: Buffer -} - export default class SettlerSDK { protected program: Program @@ -111,121 +97,6 @@ export default class SettlerSDK { return ix } - async createProposalIx( - intentHashHex: string, - instructions: ProposalInstruction[], - fees: TokenFee[], - deadline: number, - isFinal = true - ): Promise { - const parsedInstructions = this.parseProposalInstructions(instructions) - const parsedFees = this.parseTokenFees(fees) - - const ix = await this.program.methods - .createProposal(parsedInstructions, parsedFees, new BN(deadline), isFinal) - .accountsPartial({ - solver: this.getSignerKey(), - solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), - intent: this.getIntentKey(intentHashHex), - fulfilledIntent: this.getFulfilledIntentKey(intentHashHex), - }) - .instruction() - - return ix - } - - async addInstructionsToProposalIx( - intentHashHex: string, - moreInstructions: ProposalInstruction[], - finalize = true, - solverPubkey?: web3.PublicKey - ): Promise { - const parsedInstructions = this.parseProposalInstructions(moreInstructions) - const solver = solverPubkey || this.getSignerKey() - - const ix = await this.program.methods - .addInstructionsToProposal(parsedInstructions, finalize) - .accountsPartial({ - proposalCreator: this.getSignerKey(), - proposal: this.getProposalKey(intentHashHex, solver), - }) - .instruction() - - return ix - } - - async claimStaleProposalIx( - intentHashesHex: string[], - solverPubkey?: web3.PublicKey - ): Promise { - const ix = await this.program.methods - .claimStaleProposal() - .accountsPartial({ - proposalCreator: this.getSignerKey(), - }) - .remainingAccounts( - intentHashesHex.map((intentHashHex) => ({ - pubkey: this.getProposalKey(intentHashHex, solverPubkey), - isWritable: true, - isSigner: false, - })) - ) - .instruction() - - return ix - } - - async addValidatorSigIxs( - intent: web3.PublicKey, - intentHash: Buffer, - validator: web3.PublicKey, - signature: number[] - ): Promise { - const ed25519Ix = web3.Ed25519Program.createInstructionWithPublicKey({ - message: intentHash, - publicKey: validator.toBuffer(), - signature: Buffer.from(signature), - }) - - const ix = await this.program.methods - .addValidatorSig() - .accountsPartial({ - solver: this.getSignerKey(), - solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), - intent, - validatorRegistry: this.getEntityRegistryPubkey(EntityType.Validator, validator), - ixSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - return [ed25519Ix, ix] - } - - async addAxiaSigIxs( - proposal: web3.PublicKey, - axia: web3.PublicKey, - signature: number[] - ): Promise { - const ed25519Ix = web3.Ed25519Program.createInstructionWithPublicKey({ - message: proposal.toBuffer(), - publicKey: axia.toBuffer(), - signature: Buffer.from(signature), - }) - - const ix = await this.program.methods - .addAxiaSig() - .accountsPartial({ - solver: this.getSignerKey(), - solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), - proposal, - axiaRegistry: this.getEntityRegistryPubkey(EntityType.Axia, axia), - ixSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - return [ed25519Ix, ix] - } - getSettlerSettingsPubkey(): web3.PublicKey { return web3.PublicKey.findProgramAddressSync([Buffer.from('settler-settings')], this.program.programId)[0] } @@ -247,19 +118,6 @@ export default class SettlerSDK { )[0] } - getProposalKey(intentHashHex: string, solverPubkey?: web3.PublicKey): web3.PublicKey { - const intentHash = Buffer.from(intentHashHex, 'hex') - if (intentHash.length != 32) throw new Error(`Intent hash must be 32 bytes: ${intentHashHex}`) - - const intentKey = this.getIntentKey(intentHashHex) - const solver = solverPubkey || this.getSignerKey() - - return web3.PublicKey.findProgramAddressSync( - [Buffer.from('proposal'), intentKey.toBuffer(), solver.toBuffer()], - 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()], @@ -307,11 +165,4 @@ export default class SettlerSDK { amount: new BN(tokenFee.amount), })) } - - private parseProposalInstructions(instructions: ProposalInstruction[]): ProposalInstructionAnchor[] { - return instructions.map((instruction) => ({ - ...instruction, - data: typeof instruction.data === 'string' ? Buffer.from(instruction.data, 'hex') : instruction.data, - })) - } } diff --git a/packages/svm/sdks/settler/types.ts b/packages/svm/sdks/settler/types.ts index f3ac6eb..c8dce69 100644 --- a/packages/svm/sdks/settler/types.ts +++ b/packages/svm/sdks/settler/types.ts @@ -35,15 +35,3 @@ export type ExtendIntentParams = { moreMaxFees?: TokenFee[] moreEventsHex?: IntentEvent[] } - -export type ProposalInstructionAccountMeta = { - pubkey: web3.PublicKey - isSigner: boolean - isWritable: boolean -} - -export type ProposalInstruction = { - programId: web3.PublicKey - accounts: ProposalInstructionAccountMeta[] - data: Buffer | string -} diff --git a/packages/svm/tests/helpers/settler-helpers.ts b/packages/svm/tests/helpers/settler-helpers.ts index 7435e83..02ad7d9 100644 --- a/packages/svm/tests/helpers/settler-helpers.ts +++ b/packages/svm/tests/helpers/settler-helpers.ts @@ -1,14 +1,11 @@ -import { Program } from '@coral-xyz/anchor' -import { signAsync } from '@noble/ed25519' import { Keypair, PublicKey } from '@solana/web3.js' import { LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' import { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm' import SettlerSDK from '../../sdks/settler/Settler' -import { CreateIntentParams, IntentEvent, OpType, ProposalInstruction, TokenFee } from '../../sdks/settler/types' +import { CreateIntentParams, IntentEvent, OpType, TokenFee } from '../../sdks/settler/types' import WhitelistSDK, { EntityType, WhitelistStatus } from '../../sdks/whitelist/Whitelist' -import { Settler } from '../../target/types/settler' import { makeTxSignAndSend } from '../utils' import { DEFAULT_DATA_HEX, @@ -127,58 +124,6 @@ export async function createValidatedIntent( return intentHash } -/** - * Create a finalized proposal - */ -export async function createFinalizedProposal( - solverSdk: SettlerSDK, - solverProvider: LiteSVMProvider, - client: LiteSVM, - program: Program, - options: { - intentHash?: string - deadline?: number - instructions?: ProposalInstruction[] - fees?: TokenFee[] - } = {} -): Promise<{ intentHash: string; proposalKey: PublicKey }> { - const intentHash = - options.intentHash || (await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true })) - const intent = await program.account.intent.fetch(solverSdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const proposalDeadline = options.deadline ?? now + 1800 - - const instructions = options.instructions || [ - { - programId: Keypair.generate().publicKey, - accounts: [ - { - pubkey: Keypair.generate().publicKey, - isSigner: false, - isWritable: true, - }, - ], - data: 'deadbeef', - }, - ] - - const fees = - options.fees || - (intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) as TokenFee[]) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, proposalDeadline, true) - const res = await makeTxSignAndSend(solverProvider, ix) - if (res instanceof FailedTransactionMetadata) { - throw new Error(`Failed to create proposal: ${res.toString()}`) - } - - const proposalKey = solverSdk.getProposalKey(intentHash, solverProvider.wallet.publicKey) - return { intentHash, proposalKey } -} - /** * Create a whitelisted entity (validator, axia, or solver) */ @@ -198,22 +143,6 @@ export async function createWhitelistedEntity( return entity } -/** - * Create an Ed25519 signature for a validator (signs intent hash) - */ -export async function createValidatorSignature(intentHash: string, validator: Keypair): Promise { - const signature = await signAsync(Buffer.from(intentHash, 'hex'), validator.secretKey.slice(0, 32)) - return Array.from(new Uint8Array(signature)) -} - -/** - * Create an Ed25519 signature for an axia (signs proposal key) - */ -export async function createAxiaSignature(proposalKey: PublicKey, axia: Keypair): Promise { - const signature = await signAsync(proposalKey.toBuffer(), axia.secretKey.slice(0, 32)) - return Array.from(new Uint8Array(signature)) -} - /** * Helper to expect transaction errors consistently */ diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index eed34ef..1e011c9 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -2,12 +2,11 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Program, Wallet } from '@coral-xyz/anchor' -import { signAsync } from '@noble/ed25519' -import { Ed25519Program, Keypair, SYSVAR_INSTRUCTIONS_PUBKEY, TransactionInstruction } from '@solana/web3.js' +import { Keypair } from '@solana/web3.js' import { fromWorkspace, LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' import fs from 'fs' -import { FailedTransactionMetadata, LiteSVM } from 'litesvm' +import { LiteSVM } from 'litesvm' import os from 'os' import path from 'path' @@ -22,8 +21,6 @@ import { DEFAULT_DATA_HEX, DEFAULT_EVENT_DATA_HEX, DEFAULT_MAX_FEE, - DEFAULT_MAX_FEE_EXCEED, - DEFAULT_MAX_FEE_HALF, DEFAULT_MIN_VALIDATIONS, DEFAULT_TOPIC_HEX, DOUBLE_CLAIM_DELAY, @@ -35,28 +32,15 @@ import { LONG_DEADLINE, MEDIUM_DEADLINE, MULTIPLE_MIN_VALIDATIONS, - PROPOSAL_DEADLINE_OFFSET, SHORT_DEADLINE, STALE_CLAIM_DELAY, STALE_CLAIM_DELAY_PLUS_ONE, TEST_DATA_HEX_1, TEST_DATA_HEX_2, - TEST_DATA_HEX_3, - VERY_SHORT_DEADLINE, WARP_TIME_LONG, WARP_TIME_SHORT, } from './helpers/constants' -import { - createAxiaSignature, - createFinalizedProposal, - createTestIntent, - createValidatedIntent, - createValidatorSignature, - createWhitelistedEntity, - expectTransactionError, - generateIntentHash, - generateNonce, -} from './helpers/settler-helpers' +import { createTestIntent, expectTransactionError, generateIntentHash, generateNonce } from './helpers/settler-helpers' import { makeTxSignAndSend, warpSeconds } from './utils' describe('Settler Program', () => { From 0707a850c09a630047bc931aa96a9038c5baa2af Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 12:30:46 -0300 Subject: [PATCH 09/39] Trim tests to this PRs features --- .../programs/settler/src/instructions/mod.rs | 6 - packages/svm/programs/settler/src/lib.rs | 12 - packages/svm/sdks/settler/Settler.ts | 51 -- packages/svm/tests/helpers/settler-helpers.ts | 17 - packages/svm/tests/settler.test.ts | 743 +----------------- 5 files changed, 1 insertion(+), 828 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/mod.rs b/packages/svm/programs/settler/src/instructions/mod.rs index f335e7a..ebab145 100644 --- a/packages/svm/programs/settler/src/instructions/mod.rs +++ b/packages/svm/programs/settler/src/instructions/mod.rs @@ -1,7 +1,4 @@ -pub mod add_axia_sig; pub mod add_instructions_to_proposal; -pub mod add_validator_sig; -pub mod change_whitelist_program; pub mod claim_stale_intent; pub mod claim_stale_proposal; pub mod create_intent; @@ -11,10 +8,7 @@ pub mod extend_intent; pub mod initialize; pub mod set_paused_state; -pub use add_axia_sig::*; pub use add_instructions_to_proposal::*; -pub use add_validator_sig::*; -pub use change_whitelist_program::*; pub use claim_stale_intent::*; pub use claim_stale_proposal::*; pub use create_intent::*; diff --git a/packages/svm/programs/settler/src/lib.rs b/packages/svm/programs/settler/src/lib.rs index 77058f3..560811f 100644 --- a/packages/svm/programs/settler/src/lib.rs +++ b/packages/svm/programs/settler/src/lib.rs @@ -16,10 +16,6 @@ use crate::{instructions::*, state::*, types::*}; pub mod settler { use super::*; - pub fn add_axia_sig(ctx: Context) -> Result<()> { - instructions::add_axia_sig(ctx) - } - pub fn add_instructions_to_proposal( ctx: Context, more_instructions: Vec, @@ -28,14 +24,6 @@ pub mod settler { instructions::add_instructions_to_proposal(ctx, more_instructions, finalize) } - pub fn add_validator_sig(ctx: Context) -> Result<()> { - instructions::add_validator_sig(ctx) - } - - pub fn change_whitelist_program(ctx: Context) -> Result<()> { - instructions::change_whitelist_program(ctx) - } - pub fn claim_stale_intent(ctx: Context) -> Result<()> { instructions::claim_stale_intent(ctx) } diff --git a/packages/svm/sdks/settler/Settler.ts b/packages/svm/sdks/settler/Settler.ts index 2d0978b..a878277 100644 --- a/packages/svm/sdks/settler/Settler.ts +++ b/packages/svm/sdks/settler/Settler.ts @@ -175,57 +175,6 @@ export default class SettlerSDK { return ix } - async addValidatorSigIxs( - intent: web3.PublicKey, - intentHash: Buffer, - validator: web3.PublicKey, - signature: number[] - ): Promise { - const ed25519Ix = web3.Ed25519Program.createInstructionWithPublicKey({ - message: intentHash, - publicKey: validator.toBuffer(), - signature: Buffer.from(signature), - }) - - const ix = await this.program.methods - .addValidatorSig() - .accountsPartial({ - solver: this.getSignerKey(), - solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), - intent, - validatorRegistry: this.getEntityRegistryPubkey(EntityType.Validator, validator), - ixSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - return [ed25519Ix, ix] - } - - async addAxiaSigIxs( - proposal: web3.PublicKey, - axia: web3.PublicKey, - signature: number[] - ): Promise { - const ed25519Ix = web3.Ed25519Program.createInstructionWithPublicKey({ - message: proposal.toBuffer(), - publicKey: axia.toBuffer(), - signature: Buffer.from(signature), - }) - - const ix = await this.program.methods - .addAxiaSig() - .accountsPartial({ - solver: this.getSignerKey(), - solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), - proposal, - axiaRegistry: this.getEntityRegistryPubkey(EntityType.Axia, axia), - ixSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - return [ed25519Ix, ix] - } - getSettlerSettingsPubkey(): web3.PublicKey { return web3.PublicKey.findProgramAddressSync([Buffer.from('settler-settings')], this.program.programId)[0] } diff --git a/packages/svm/tests/helpers/settler-helpers.ts b/packages/svm/tests/helpers/settler-helpers.ts index 7435e83..5e5475e 100644 --- a/packages/svm/tests/helpers/settler-helpers.ts +++ b/packages/svm/tests/helpers/settler-helpers.ts @@ -1,5 +1,4 @@ import { Program } from '@coral-xyz/anchor' -import { signAsync } from '@noble/ed25519' import { Keypair, PublicKey } from '@solana/web3.js' import { LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' @@ -198,22 +197,6 @@ export async function createWhitelistedEntity( return entity } -/** - * Create an Ed25519 signature for a validator (signs intent hash) - */ -export async function createValidatorSignature(intentHash: string, validator: Keypair): Promise { - const signature = await signAsync(Buffer.from(intentHash, 'hex'), validator.secretKey.slice(0, 32)) - return Array.from(new Uint8Array(signature)) -} - -/** - * Create an Ed25519 signature for an axia (signs proposal key) - */ -export async function createAxiaSignature(proposalKey: PublicKey, axia: Keypair): Promise { - const signature = await signAsync(proposalKey.toBuffer(), axia.secretKey.slice(0, 32)) - return Array.from(new Uint8Array(signature)) -} - /** * Helper to expect transaction errors consistently */ diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 9ef7418..f7a8813 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -2,8 +2,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Program, Wallet } from '@coral-xyz/anchor' -import { signAsync } from '@noble/ed25519' -import { Ed25519Program, Keypair, SYSVAR_INSTRUCTIONS_PUBKEY, TransactionInstruction } from '@solana/web3.js' +import { Keypair } from '@solana/web3.js' import { fromWorkspace, LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' import fs from 'fs' @@ -42,17 +41,12 @@ import { TEST_DATA_HEX_1, TEST_DATA_HEX_2, TEST_DATA_HEX_3, - VERY_SHORT_DEADLINE, WARP_TIME_LONG, WARP_TIME_SHORT, } from './helpers/constants' import { - createAxiaSignature, - createFinalizedProposal, createTestIntent, createValidatedIntent, - createValidatorSignature, - createWhitelistedEntity, expectTransactionError, generateIntentHash, generateNonce, @@ -1919,740 +1913,5 @@ describe('Settler Program', () => { expect(errorMsg.includes(`AccountNotInitialized`)).to.be.true }) }) - - describe('add_validator_sigs', () => { - let whitelistedValidator: Keypair - - before(async () => { - whitelistedValidator = Keypair.generate() - const whitelistValidatorIx = await whitelistSdk.setEntityWhitelistStatusIx( - EntityType.Validator, - whitelistedValidator.publicKey, - WhitelistStatus.Whitelisted - ) - await makeTxSignAndSend(provider, whitelistValidatorIx) - }) - - it('should add validator signature successfully', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const intentBefore = await program.account.intent.fetch(intentKey) - expect(intentBefore.validations).to.be.eq(0) - expect(intentBefore.validators.length).to.be.eq(0) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - await makeTxSignAndSend(solverProvider, ...ixs) - - const intentAfter = await program.account.intent.fetch(intentKey) - expect(intentAfter.validations).to.be.eq(1) - expect(intentAfter.validators.length).to.be.eq(1) - expect(intentAfter.validators[0].toString()).to.be.eq(whitelistedValidator.publicKey.toString()) - }) - - it('should add multiple validator signatures', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - minValidations: MULTIPLE_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const validator1 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - const validator2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - const validator3 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - - const signature1 = await createValidatorSignature(intentHash, validator1) - const ixs1 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator1.publicKey, - signature1 - ) - await makeTxSignAndSend(solverProvider, ...ixs1) - - const intentAfter1 = await program.account.intent.fetch(intentKey) - expect(intentAfter1.validations).to.be.eq(1) - expect(intentAfter1.validators.length).to.be.eq(1) - - const signature2 = await createValidatorSignature(intentHash, validator2) - const ixs2 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator2.publicKey, - signature2 - ) - await makeTxSignAndSend(solverProvider, ...ixs2) - - const intentAfter2 = await program.account.intent.fetch(intentKey) - expect(intentAfter2.validations).to.be.eq(2) - expect(intentAfter2.validators.length).to.be.eq(2) - - const signature3 = await createValidatorSignature(intentHash, validator3) - const ixs3 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator3.publicKey, - signature3 - ) - await makeTxSignAndSend(solverProvider, ...ixs3) - - const intentAfter3 = await program.account.intent.fetch(intentKey) - expect(intentAfter3.validations).to.be.eq(3) - expect(intentAfter3.validators.length).to.be.eq(3) - expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator1.publicKey.toString()) - expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator2.publicKey.toString()) - expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator3.publicKey.toString()) - }) - - it('should handle duplicate validator signature gracefully', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { minValidations: 2, isFinal: true }) - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs1 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - await makeTxSignAndSend(solverProvider, ...ixs1) - - const intentAfter1 = await program.account.intent.fetch(intentKey) - expect(intentAfter1.validations).to.be.eq(1) - expect(intentAfter1.validators.length).to.be.eq(1) - - client.expireBlockhash() - const ixs2 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - await makeTxSignAndSend(solverProvider, ...ixs2) - - const intentAfter2 = await program.account.intent.fetch(intentKey) - expect(intentAfter2.validations).to.be.eq(1) - expect(intentAfter2.validators.length).to.be.eq(1) - }) - - it('should handle when min_validations already met', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const validator1 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - const validator2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - - const signature1 = await createValidatorSignature(intentHash, validator1) - const ixs1 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator1.publicKey, - signature1 - ) - await makeTxSignAndSend(solverProvider, ...ixs1) - - const intentAfter1 = await program.account.intent.fetch(intentKey) - expect(intentAfter1.validations).to.be.eq(1) - expect(intentAfter1.validators.length).to.be.eq(1) - expect(intentAfter1.minValidations).to.be.eq(1) - - client.expireBlockhash() - const signature2 = await createValidatorSignature(intentHash, validator2) - const ixs2 = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator2.publicKey, - signature2 - ) - await makeTxSignAndSend(solverProvider, ...ixs2) - - const intentAfter2 = await program.account.intent.fetch(intentKey) - expect(intentAfter2.validations).to.be.eq(1) - expect(intentAfter2.validators.length).to.be.eq(1) - expect(intentAfter2.validators[0].toString()).to.be.eq(validator1.publicKey.toString()) - }) - - it('cannot add signature if intent is not final', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: false, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `Intent is not final`) - }) - - it('cannot add signature if intent has expired', async () => { - const intentHash = generateIntentHash() - const nonce = generateNonce() - const user = Keypair.generate().publicKey - const now = Number(client.getClock().unixTimestamp) - const deadline = now + SHORT_DEADLINE - - const params = { - op: OpType.Transfer, - user, - nonceHex: nonce, - deadline, - minValidations: DEFAULT_MIN_VALIDATIONS, - dataHex: DEFAULT_DATA_HEX, - maxFees: [ - { - mint: Keypair.generate().publicKey, - amount: DEFAULT_MAX_FEE, - }, - ], - eventsHex: [], - } - - const ix = await solverSdk.createIntentIx(intentHash, params, true) - await makeTxSignAndSend(solverProvider, ix) - - const intentKey = sdk.getIntentKey(intentHash) - - warpSeconds(provider, 101) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `Intent has already expired`) - }) - - it('cannot add signature if validator is not whitelisted', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const validator = Keypair.generate() - - const signature = await createValidatorSignature(intentHash, validator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - validator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError( - res, - `AnchorError caused by account: validator_registry. Error Code: AccountNotInitialized.` - ) - }) - - it('cannot add signature if solver is not whitelisted', async () => { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs = await maliciousSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(maliciousProvider, ...ixs) - - expectTransactionError( - res, - `AnchorError caused by account: solver_registry. Error Code: AccountNotInitialized.` - ) - }) - - it('cannot add signature for non-existent intent', async () => { - const intentHash = generateIntentHash() - const intentKey = sdk.getIntentKey(intentHash) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ - message: Buffer.from(intentHash, 'hex'), - publicKey: whitelistedValidator.publicKey.toBuffer(), - signature: Buffer.from(signature), - }) - - const ix = await program.methods - .addValidatorSig() - .accountsPartial({ - solver: solverSdk.getSignerKey(), - solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), - intent: intentKey, - fulfilledIntent: solverSdk.getFulfilledIntentKey(intentHash), - validatorRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Validator, whitelistedValidator.publicKey), - ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) - - expectTransactionError(res, `AccountNotInitialized`) - }) - - it('cannot add signature if fulfilled_intent PDA already exists', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const fulfilledIntent = sdk.getFulfilledIntentKey(intentHash) - client.setAccount(fulfilledIntent, { - executable: false, - lamports: 1002240, - owner: program.programId, - data: Buffer.from('595168911b9267f7' + '010000000000000000', 'hex'), - }) - - const signature = await createValidatorSignature(intentHash, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError( - res, - `AnchorError caused by account: fulfilled_intent. Error Code: AccountNotSystemOwned. Error Number: 3011. Error Message: The given account is not owned by the system program` - ) - }) - - it('cannot add signature with wrong intent hash', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const wrongIntentHash = generateIntentHash() - const signature = await createValidatorSignature(wrongIntentHash, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(wrongIntentHash, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `Signature verification failed`) - }) - - it('cannot add signature with invalid signature', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey = sdk.getIntentKey(intentHash) - - const invalidSignature: number[] = Array.from({ length: 64 }, () => Math.floor(Math.random() * 256)) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey, - Buffer.from(intentHash, 'hex'), - whitelistedValidator.publicKey, - invalidSignature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expect(res.toString()).to.be.eq( - `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('cannot add valid signature but for another intent', async () => { - const intentHash1 = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - const intentKey1 = sdk.getIntentKey(intentHash1) - - const intentHash2 = await createTestIntent(solverSdk, solverProvider, { - minValidations: DEFAULT_MIN_VALIDATIONS, - isFinal: true, - }) - - const signature = await createValidatorSignature(intentHash2, whitelistedValidator) - - const ixs = await solverSdk.addValidatorSigIxs( - intentKey1, - Buffer.from(intentHash2, 'hex'), - whitelistedValidator.publicKey, - signature - ) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `Signature verification failed`) - }) - }) - - describe('add_axia_sig', () => { - let whitelistedAxia: Keypair - - before(async () => { - whitelistedAxia = Keypair.generate() - const whitelistAxiaIx = await whitelistSdk.setEntityWhitelistStatusIx( - EntityType.Axia, - whitelistedAxia.publicKey, - WhitelistStatus.Whitelisted - ) - await makeTxSignAndSend(provider, whitelistAxiaIx) - }) - - it('should add axia signature successfully', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const proposalBefore = await program.account.proposal.fetch(proposalKey) - expect(proposalBefore.isSigned).to.be.false - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs) - - const proposalAfter = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter.isSigned).to.be.true - }) - - it('should handle duplicate signature gracefully', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs1 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs1) - - const proposalAfter1 = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter1.isSigned).to.be.true - - client.expireBlockhash() - const ixs2 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs2) - - const proposalAfter2 = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter2.isSigned).to.be.true - }) - - it('should add signature multiple times safely', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs1 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs1) - - client.expireBlockhash() - const ixs2 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs2) - - client.expireBlockhash() - const ixs3 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs3) - - const proposalAfter = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter.isSigned).to.be.true - }) - - it('cannot add signature if proposal is not final', async () => { - const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) - const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - const now = Number(client.getClock().unixTimestamp) - const deadline = now + PROPOSAL_DEADLINE_OFFSET - - const instructions = [ - { - programId: Keypair.generate().publicKey, - accounts: [], - data: TEST_DATA_HEX_3, - }, - ] - - const fees = intent.maxFees.map((maxFee) => ({ - mint: maxFee.mint, - amount: maxFee.amount.toNumber(), - })) - - const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) - await makeTxSignAndSend(solverProvider, ix) - - const proposalKey = sdk.getProposalKey(intentHash, solver.publicKey) - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, 'Proposal is not final') - }) - - it('cannot add signature if proposal has expired', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + STALE_CLAIM_DELAY - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) - - warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, 'Proposal has already expired') - }) - - it('cannot add signature if axia is not whitelisted', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const axia = Keypair.generate() - const signature = await createAxiaSignature(proposalKey, axia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, axia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) - }) - - it('cannot add signature if solver is not whitelisted', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await maliciousSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(maliciousProvider, ...ixs) - - expectTransactionError( - res, - `AnchorError caused by account: solver_registry. Error Code: AccountNotInitialized.` - ) - }) - - it('cannot add signature for non-existent proposal', async () => { - const proposalKey = Keypair.generate().publicKey - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ - message: proposalKey.toBuffer(), - publicKey: whitelistedAxia.publicKey.toBuffer(), - signature: Buffer.from(signature), - }) - - const ix = await program.methods - .addAxiaSig() - .accountsPartial({ - solver: solverSdk.getSignerKey(), - solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), - proposal: proposalKey, - axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), - ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) - - expectTransactionError( - res, - `Program log: AnchorError caused by account: proposal. Error Code: AccountNotInitialized` - ) - }) - - it('cannot add signature if proposal deadline equals now', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + SHORT_DEADLINE - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) - - warpSeconds(provider, WARP_TIME_SHORT) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, 'Proposal has already expired') - }) - - it('cannot add signature with wrong proposal key', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - const wrongProposalKey = Keypair.generate().publicKey - - const signature = await createAxiaSignature(wrongProposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expect(res.toString()).to.be.eq( - `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('cannot add signature with invalid signature', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const invalidSignature: number[] = Array.from({ length: 64 }, () => Math.floor(Math.random() * 256)) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, invalidSignature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expect(res.toString()).to.be.eq( - `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('cannot add signature from wrong axia pubkey', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const wrongAxia = Keypair.generate() - const signature = await createAxiaSignature(proposalKey, wrongAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, wrongAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) - }) - - it('cannot add signature with signature from different axia', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const axia2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Axia) - const signature = await createAxiaSignature(proposalKey, axia2) - - // Try to use axia2's signature but claim it's from whitelistedAxia - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expect(res.toString()).to.be.eq( - `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('cannot add signature with signature from validator instead of axia', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - const validator = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) - const signature = await createAxiaSignature(proposalKey, validator) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, validator.publicKey, signature) - const res = await makeTxSignAndSend(solverProvider, ...ixs) - - expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) - }) - - it('cannot add signature if signed message is wrong', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - // Sign a different message (e.g., intent hash instead of proposal key) - const intentHash = generateIntentHash() - const signature = await signAsync(Buffer.from(intentHash, 'hex'), whitelistedAxia.secretKey.slice(0, 32)) - const signatureArray = Array.from(new Uint8Array(signature)) - - const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ - message: Buffer.from(intentHash, 'hex'), - publicKey: whitelistedAxia.publicKey.toBuffer(), - signature: Buffer.from(signatureArray), - }) - - const ix = await program.methods - .addAxiaSig() - .accountsPartial({ - solver: solverSdk.getSignerKey(), - solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), - proposal: proposalKey, - axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), - ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) - - expectTransactionError(res, `Signature verification failed`) - }) - - it('cannot add signature with corrupted ed25519 instruction', async () => { - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - - // Create corrupted Ed25519 instruction with wrong program ID - const corruptedEd25519Ix = new TransactionInstruction({ - programId: Keypair.generate().publicKey, - keys: [], - data: Buffer.from([ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ]), - }) - - const ix = await program.methods - .addAxiaSig() - .accountsPartial({ - solver: solverSdk.getSignerKey(), - solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), - proposal: proposalKey, - axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), - ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, - }) - .instruction() - - const res = await makeTxSignAndSend(solverProvider, corruptedEd25519Ix, ix) - - expect(res.toString()).to.be.eq( - // eslint-disable-next-line no-secrets/no-secrets - `FailedTransactionMetadata(FailedTransactionMetadata { err: InvalidProgramForExecution, meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` - ) - }) - - it('should add signature when proposal deadline is close', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + VERY_SHORT_DEADLINE - const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) - - const signature = await createAxiaSignature(proposalKey, whitelistedAxia) - - const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) - await makeTxSignAndSend(solverProvider, ...ixs) - - const proposalAfter = await program.account.proposal.fetch(proposalKey) - expect(proposalAfter.isSigned).to.be.true - }) - }) }) }) From d05f12873b5121bd644211aaabd5716115613065 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 17 Dec 2025 12:57:12 -0300 Subject: [PATCH 10/39] Add remaining files from original PR --- .../programs/settler/src/instructions/mod.rs | 6 + packages/svm/programs/settler/src/lib.rs | 12 + .../svm/programs/settler/src/utils/mod.rs | 2 + .../svm/programs/settler/src/utils/sigs.rs | 75 ++ packages/svm/sdks/settler/Settler.ts | 51 ++ packages/svm/tests/helpers/settler-helpers.ts | 17 + packages/svm/tests/settler.test.ts | 743 +++++++++++++++++- 7 files changed, 905 insertions(+), 1 deletion(-) create mode 100644 packages/svm/programs/settler/src/utils/sigs.rs diff --git a/packages/svm/programs/settler/src/instructions/mod.rs b/packages/svm/programs/settler/src/instructions/mod.rs index ebab145..f335e7a 100644 --- a/packages/svm/programs/settler/src/instructions/mod.rs +++ b/packages/svm/programs/settler/src/instructions/mod.rs @@ -1,4 +1,7 @@ +pub mod add_axia_sig; pub mod add_instructions_to_proposal; +pub mod add_validator_sig; +pub mod change_whitelist_program; pub mod claim_stale_intent; pub mod claim_stale_proposal; pub mod create_intent; @@ -8,7 +11,10 @@ pub mod extend_intent; pub mod initialize; pub mod set_paused_state; +pub use add_axia_sig::*; pub use add_instructions_to_proposal::*; +pub use add_validator_sig::*; +pub use change_whitelist_program::*; pub use claim_stale_intent::*; pub use claim_stale_proposal::*; pub use create_intent::*; diff --git a/packages/svm/programs/settler/src/lib.rs b/packages/svm/programs/settler/src/lib.rs index 560811f..77058f3 100644 --- a/packages/svm/programs/settler/src/lib.rs +++ b/packages/svm/programs/settler/src/lib.rs @@ -16,6 +16,10 @@ use crate::{instructions::*, state::*, types::*}; pub mod settler { use super::*; + pub fn add_axia_sig(ctx: Context) -> Result<()> { + instructions::add_axia_sig(ctx) + } + pub fn add_instructions_to_proposal( ctx: Context, more_instructions: Vec, @@ -24,6 +28,14 @@ pub mod settler { instructions::add_instructions_to_proposal(ctx, more_instructions, finalize) } + pub fn add_validator_sig(ctx: Context) -> Result<()> { + instructions::add_validator_sig(ctx) + } + + pub fn change_whitelist_program(ctx: Context) -> Result<()> { + instructions::change_whitelist_program(ctx) + } + pub fn claim_stale_intent(ctx: Context) -> Result<()> { instructions::claim_stale_intent(ctx) } diff --git a/packages/svm/programs/settler/src/utils/mod.rs b/packages/svm/programs/settler/src/utils/mod.rs index ed1f5ec..bd9ec41 100644 --- a/packages/svm/programs/settler/src/utils/mod.rs +++ b/packages/svm/programs/settler/src/utils/mod.rs @@ -1,3 +1,5 @@ pub mod math; +pub mod sigs; pub use math::*; +pub use sigs::*; diff --git a/packages/svm/programs/settler/src/utils/sigs.rs b/packages/svm/programs/settler/src/utils/sigs.rs new file mode 100644 index 0000000..a5ce59b --- /dev/null +++ b/packages/svm/programs/settler/src/utils/sigs.rs @@ -0,0 +1,75 @@ +use anchor_lang::prelude::{instruction::Instruction, *}; + +use crate::errors::SettlerError; + +pub fn check_ed25519_ix(ix: &Instruction) -> Result<()> { + if ix.program_id.to_string() != "Ed25519SigVerify111111111111111111111111111" + || ix.accounts.len() != 0 + { + return err!(SettlerError::SigVerificationFailed); + } + + Ok(()) +} + +pub struct Ed25519Args<'a> { + pub pubkey: &'a [u8; 32], + pub sig: &'a [u8; 64], + pub msg: &'a [u8], +} + +pub fn get_args_from_ed25519_ix_data(data: &[u8]) -> Result> { + if data.len() < 112 { + return err!(SettlerError::SigVerificationFailed); + } + + // Header + let num_signatures = &[data[0]]; + let padding = &[data[1]]; + let signature_offset = &data[2..=3]; + let signature_instruction_index = &data[4..=5]; + let public_key_offset = &data[6..=7]; + let public_key_instruction_index = &data[8..=9]; + let message_data_offset = &data[10..=11]; + let message_data_size = &data[12..=13]; + let message_instruction_index = &data[14..=15]; + + // Data + let pubkey = &data[16..16 + 32]; + let sig = &data[48..48 + 64]; + let msg = &data[112..]; + + // Expected values + let exp_public_key_offset: u16 = 16; // 2*u8 + 7*u16 + let exp_signature_offset: u16 = exp_public_key_offset + 32_u16; + let exp_message_data_offset: u16 = exp_signature_offset + 64_u16; + let exp_num_signatures: u8 = 1; + let exp_message_data_size: u16 = msg + .len() + .try_into() + .map_err(|_| SettlerError::SigVerificationFailed)?; + + // Header + if num_signatures != &exp_num_signatures.to_le_bytes() + || padding != &[0] + || signature_offset != &exp_signature_offset.to_le_bytes() + || signature_instruction_index != &u16::MAX.to_le_bytes() + || public_key_offset != &exp_public_key_offset.to_le_bytes() + || public_key_instruction_index != &u16::MAX.to_le_bytes() + || message_data_offset != &exp_message_data_offset.to_le_bytes() + || message_data_size != &exp_message_data_size.to_le_bytes() + || message_instruction_index != &u16::MAX.to_le_bytes() + { + return err!(SettlerError::SigVerificationFailed); + } + + Ok(Ed25519Args { + pubkey: pubkey + .try_into() + .map_err(|_| SettlerError::SigVerificationFailed)?, + sig: sig + .try_into() + .map_err(|_| SettlerError::SigVerificationFailed)?, + msg, + }) +} diff --git a/packages/svm/sdks/settler/Settler.ts b/packages/svm/sdks/settler/Settler.ts index a878277..2d0978b 100644 --- a/packages/svm/sdks/settler/Settler.ts +++ b/packages/svm/sdks/settler/Settler.ts @@ -175,6 +175,57 @@ export default class SettlerSDK { return ix } + async addValidatorSigIxs( + intent: web3.PublicKey, + intentHash: Buffer, + validator: web3.PublicKey, + signature: number[] + ): Promise { + const ed25519Ix = web3.Ed25519Program.createInstructionWithPublicKey({ + message: intentHash, + publicKey: validator.toBuffer(), + signature: Buffer.from(signature), + }) + + const ix = await this.program.methods + .addValidatorSig() + .accountsPartial({ + solver: this.getSignerKey(), + solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), + intent, + validatorRegistry: this.getEntityRegistryPubkey(EntityType.Validator, validator), + ixSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + return [ed25519Ix, ix] + } + + async addAxiaSigIxs( + proposal: web3.PublicKey, + axia: web3.PublicKey, + signature: number[] + ): Promise { + const ed25519Ix = web3.Ed25519Program.createInstructionWithPublicKey({ + message: proposal.toBuffer(), + publicKey: axia.toBuffer(), + signature: Buffer.from(signature), + }) + + const ix = await this.program.methods + .addAxiaSig() + .accountsPartial({ + solver: this.getSignerKey(), + solverRegistry: this.getEntityRegistryPubkey(EntityType.Solver, this.getSignerKey()), + proposal, + axiaRegistry: this.getEntityRegistryPubkey(EntityType.Axia, axia), + ixSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + return [ed25519Ix, ix] + } + getSettlerSettingsPubkey(): web3.PublicKey { return web3.PublicKey.findProgramAddressSync([Buffer.from('settler-settings')], this.program.programId)[0] } diff --git a/packages/svm/tests/helpers/settler-helpers.ts b/packages/svm/tests/helpers/settler-helpers.ts index 5e5475e..7435e83 100644 --- a/packages/svm/tests/helpers/settler-helpers.ts +++ b/packages/svm/tests/helpers/settler-helpers.ts @@ -1,4 +1,5 @@ import { Program } from '@coral-xyz/anchor' +import { signAsync } from '@noble/ed25519' import { Keypair, PublicKey } from '@solana/web3.js' import { LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' @@ -197,6 +198,22 @@ export async function createWhitelistedEntity( return entity } +/** + * Create an Ed25519 signature for a validator (signs intent hash) + */ +export async function createValidatorSignature(intentHash: string, validator: Keypair): Promise { + const signature = await signAsync(Buffer.from(intentHash, 'hex'), validator.secretKey.slice(0, 32)) + return Array.from(new Uint8Array(signature)) +} + +/** + * Create an Ed25519 signature for an axia (signs proposal key) + */ +export async function createAxiaSignature(proposalKey: PublicKey, axia: Keypair): Promise { + const signature = await signAsync(proposalKey.toBuffer(), axia.secretKey.slice(0, 32)) + return Array.from(new Uint8Array(signature)) +} + /** * Helper to expect transaction errors consistently */ diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index f7a8813..9ef7418 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -2,7 +2,8 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Program, Wallet } from '@coral-xyz/anchor' -import { Keypair } from '@solana/web3.js' +import { signAsync } from '@noble/ed25519' +import { Ed25519Program, Keypair, SYSVAR_INSTRUCTIONS_PUBKEY, TransactionInstruction } from '@solana/web3.js' import { fromWorkspace, LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' import fs from 'fs' @@ -41,12 +42,17 @@ import { TEST_DATA_HEX_1, TEST_DATA_HEX_2, TEST_DATA_HEX_3, + VERY_SHORT_DEADLINE, WARP_TIME_LONG, WARP_TIME_SHORT, } from './helpers/constants' import { + createAxiaSignature, + createFinalizedProposal, createTestIntent, createValidatedIntent, + createValidatorSignature, + createWhitelistedEntity, expectTransactionError, generateIntentHash, generateNonce, @@ -1913,5 +1919,740 @@ describe('Settler Program', () => { expect(errorMsg.includes(`AccountNotInitialized`)).to.be.true }) }) + + describe('add_validator_sigs', () => { + let whitelistedValidator: Keypair + + before(async () => { + whitelistedValidator = Keypair.generate() + const whitelistValidatorIx = await whitelistSdk.setEntityWhitelistStatusIx( + EntityType.Validator, + whitelistedValidator.publicKey, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(provider, whitelistValidatorIx) + }) + + it('should add validator signature successfully', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const intentBefore = await program.account.intent.fetch(intentKey) + expect(intentBefore.validations).to.be.eq(0) + expect(intentBefore.validators.length).to.be.eq(0) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + await makeTxSignAndSend(solverProvider, ...ixs) + + const intentAfter = await program.account.intent.fetch(intentKey) + expect(intentAfter.validations).to.be.eq(1) + expect(intentAfter.validators.length).to.be.eq(1) + expect(intentAfter.validators[0].toString()).to.be.eq(whitelistedValidator.publicKey.toString()) + }) + + it('should add multiple validator signatures', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + minValidations: MULTIPLE_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const validator1 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + const validator2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + const validator3 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + + const signature1 = await createValidatorSignature(intentHash, validator1) + const ixs1 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator1.publicKey, + signature1 + ) + await makeTxSignAndSend(solverProvider, ...ixs1) + + const intentAfter1 = await program.account.intent.fetch(intentKey) + expect(intentAfter1.validations).to.be.eq(1) + expect(intentAfter1.validators.length).to.be.eq(1) + + const signature2 = await createValidatorSignature(intentHash, validator2) + const ixs2 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator2.publicKey, + signature2 + ) + await makeTxSignAndSend(solverProvider, ...ixs2) + + const intentAfter2 = await program.account.intent.fetch(intentKey) + expect(intentAfter2.validations).to.be.eq(2) + expect(intentAfter2.validators.length).to.be.eq(2) + + const signature3 = await createValidatorSignature(intentHash, validator3) + const ixs3 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator3.publicKey, + signature3 + ) + await makeTxSignAndSend(solverProvider, ...ixs3) + + const intentAfter3 = await program.account.intent.fetch(intentKey) + expect(intentAfter3.validations).to.be.eq(3) + expect(intentAfter3.validators.length).to.be.eq(3) + expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator1.publicKey.toString()) + expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator2.publicKey.toString()) + expect(intentAfter3.validators.map((v) => v.toString())).to.include(validator3.publicKey.toString()) + }) + + it('should handle duplicate validator signature gracefully', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { minValidations: 2, isFinal: true }) + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs1 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + await makeTxSignAndSend(solverProvider, ...ixs1) + + const intentAfter1 = await program.account.intent.fetch(intentKey) + expect(intentAfter1.validations).to.be.eq(1) + expect(intentAfter1.validators.length).to.be.eq(1) + + client.expireBlockhash() + const ixs2 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + await makeTxSignAndSend(solverProvider, ...ixs2) + + const intentAfter2 = await program.account.intent.fetch(intentKey) + expect(intentAfter2.validations).to.be.eq(1) + expect(intentAfter2.validators.length).to.be.eq(1) + }) + + it('should handle when min_validations already met', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const validator1 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + const validator2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + + const signature1 = await createValidatorSignature(intentHash, validator1) + const ixs1 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator1.publicKey, + signature1 + ) + await makeTxSignAndSend(solverProvider, ...ixs1) + + const intentAfter1 = await program.account.intent.fetch(intentKey) + expect(intentAfter1.validations).to.be.eq(1) + expect(intentAfter1.validators.length).to.be.eq(1) + expect(intentAfter1.minValidations).to.be.eq(1) + + client.expireBlockhash() + const signature2 = await createValidatorSignature(intentHash, validator2) + const ixs2 = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator2.publicKey, + signature2 + ) + await makeTxSignAndSend(solverProvider, ...ixs2) + + const intentAfter2 = await program.account.intent.fetch(intentKey) + expect(intentAfter2.validations).to.be.eq(1) + expect(intentAfter2.validators.length).to.be.eq(1) + expect(intentAfter2.validators[0].toString()).to.be.eq(validator1.publicKey.toString()) + }) + + it('cannot add signature if intent is not final', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: false, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `Intent is not final`) + }) + + it('cannot add signature if intent has expired', async () => { + const intentHash = generateIntentHash() + const nonce = generateNonce() + const user = Keypair.generate().publicKey + const now = Number(client.getClock().unixTimestamp) + const deadline = now + SHORT_DEADLINE + + const params = { + op: OpType.Transfer, + user, + nonceHex: nonce, + deadline, + minValidations: DEFAULT_MIN_VALIDATIONS, + dataHex: DEFAULT_DATA_HEX, + maxFees: [ + { + mint: Keypair.generate().publicKey, + amount: DEFAULT_MAX_FEE, + }, + ], + eventsHex: [], + } + + const ix = await solverSdk.createIntentIx(intentHash, params, true) + await makeTxSignAndSend(solverProvider, ix) + + const intentKey = sdk.getIntentKey(intentHash) + + warpSeconds(provider, 101) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `Intent has already expired`) + }) + + it('cannot add signature if validator is not whitelisted', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const validator = Keypair.generate() + + const signature = await createValidatorSignature(intentHash, validator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + validator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError( + res, + `AnchorError caused by account: validator_registry. Error Code: AccountNotInitialized.` + ) + }) + + it('cannot add signature if solver is not whitelisted', async () => { + const intentHash = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs = await maliciousSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(maliciousProvider, ...ixs) + + expectTransactionError( + res, + `AnchorError caused by account: solver_registry. Error Code: AccountNotInitialized.` + ) + }) + + it('cannot add signature for non-existent intent', async () => { + const intentHash = generateIntentHash() + const intentKey = sdk.getIntentKey(intentHash) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ + message: Buffer.from(intentHash, 'hex'), + publicKey: whitelistedValidator.publicKey.toBuffer(), + signature: Buffer.from(signature), + }) + + const ix = await program.methods + .addValidatorSig() + .accountsPartial({ + solver: solverSdk.getSignerKey(), + solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), + intent: intentKey, + fulfilledIntent: solverSdk.getFulfilledIntentKey(intentHash), + validatorRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Validator, whitelistedValidator.publicKey), + ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) + + expectTransactionError(res, `AccountNotInitialized`) + }) + + it('cannot add signature if fulfilled_intent PDA already exists', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const fulfilledIntent = sdk.getFulfilledIntentKey(intentHash) + client.setAccount(fulfilledIntent, { + executable: false, + lamports: 1002240, + owner: program.programId, + data: Buffer.from('595168911b9267f7' + '010000000000000000', 'hex'), + }) + + const signature = await createValidatorSignature(intentHash, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError( + res, + `AnchorError caused by account: fulfilled_intent. Error Code: AccountNotSystemOwned. Error Number: 3011. Error Message: The given account is not owned by the system program` + ) + }) + + it('cannot add signature with wrong intent hash', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const wrongIntentHash = generateIntentHash() + const signature = await createValidatorSignature(wrongIntentHash, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(wrongIntentHash, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `Signature verification failed`) + }) + + it('cannot add signature with invalid signature', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey = sdk.getIntentKey(intentHash) + + const invalidSignature: number[] = Array.from({ length: 64 }, () => Math.floor(Math.random() * 256)) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey, + Buffer.from(intentHash, 'hex'), + whitelistedValidator.publicKey, + invalidSignature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expect(res.toString()).to.be.eq( + `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('cannot add valid signature but for another intent', async () => { + const intentHash1 = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + const intentKey1 = sdk.getIntentKey(intentHash1) + + const intentHash2 = await createTestIntent(solverSdk, solverProvider, { + minValidations: DEFAULT_MIN_VALIDATIONS, + isFinal: true, + }) + + const signature = await createValidatorSignature(intentHash2, whitelistedValidator) + + const ixs = await solverSdk.addValidatorSigIxs( + intentKey1, + Buffer.from(intentHash2, 'hex'), + whitelistedValidator.publicKey, + signature + ) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `Signature verification failed`) + }) + }) + + describe('add_axia_sig', () => { + let whitelistedAxia: Keypair + + before(async () => { + whitelistedAxia = Keypair.generate() + const whitelistAxiaIx = await whitelistSdk.setEntityWhitelistStatusIx( + EntityType.Axia, + whitelistedAxia.publicKey, + WhitelistStatus.Whitelisted + ) + await makeTxSignAndSend(provider, whitelistAxiaIx) + }) + + it('should add axia signature successfully', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const proposalBefore = await program.account.proposal.fetch(proposalKey) + expect(proposalBefore.isSigned).to.be.false + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs) + + const proposalAfter = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter.isSigned).to.be.true + }) + + it('should handle duplicate signature gracefully', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs1 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs1) + + const proposalAfter1 = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter1.isSigned).to.be.true + + client.expireBlockhash() + const ixs2 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs2) + + const proposalAfter2 = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter2.isSigned).to.be.true + }) + + it('should add signature multiple times safely', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs1 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs1) + + client.expireBlockhash() + const ixs2 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs2) + + client.expireBlockhash() + const ixs3 = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs3) + + const proposalAfter = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter.isSigned).to.be.true + }) + + it('cannot add signature if proposal is not final', async () => { + const intentHash = await createValidatedIntent(solverSdk, solverProvider, client, { isFinal: true }) + const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) + const now = Number(client.getClock().unixTimestamp) + const deadline = now + PROPOSAL_DEADLINE_OFFSET + + const instructions = [ + { + programId: Keypair.generate().publicKey, + accounts: [], + data: TEST_DATA_HEX_3, + }, + ] + + const fees = intent.maxFees.map((maxFee) => ({ + mint: maxFee.mint, + amount: maxFee.amount.toNumber(), + })) + + const ix = await solverSdk.createProposalIx(intentHash, instructions, fees, deadline, false) + await makeTxSignAndSend(solverProvider, ix) + + const proposalKey = sdk.getProposalKey(intentHash, solver.publicKey) + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, 'Proposal is not final') + }) + + it('cannot add signature if proposal has expired', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + STALE_CLAIM_DELAY + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) + + warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, 'Proposal has already expired') + }) + + it('cannot add signature if axia is not whitelisted', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const axia = Keypair.generate() + const signature = await createAxiaSignature(proposalKey, axia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, axia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) + }) + + it('cannot add signature if solver is not whitelisted', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await maliciousSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(maliciousProvider, ...ixs) + + expectTransactionError( + res, + `AnchorError caused by account: solver_registry. Error Code: AccountNotInitialized.` + ) + }) + + it('cannot add signature for non-existent proposal', async () => { + const proposalKey = Keypair.generate().publicKey + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ + message: proposalKey.toBuffer(), + publicKey: whitelistedAxia.publicKey.toBuffer(), + signature: Buffer.from(signature), + }) + + const ix = await program.methods + .addAxiaSig() + .accountsPartial({ + solver: solverSdk.getSignerKey(), + solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), + proposal: proposalKey, + axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), + ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) + + expectTransactionError( + res, + `Program log: AnchorError caused by account: proposal. Error Code: AccountNotInitialized` + ) + }) + + it('cannot add signature if proposal deadline equals now', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + SHORT_DEADLINE + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) + + warpSeconds(provider, WARP_TIME_SHORT) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, 'Proposal has already expired') + }) + + it('cannot add signature with wrong proposal key', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + const wrongProposalKey = Keypair.generate().publicKey + + const signature = await createAxiaSignature(wrongProposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expect(res.toString()).to.be.eq( + `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('cannot add signature with invalid signature', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const invalidSignature: number[] = Array.from({ length: 64 }, () => Math.floor(Math.random() * 256)) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, invalidSignature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expect(res.toString()).to.be.eq( + `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('cannot add signature from wrong axia pubkey', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const wrongAxia = Keypair.generate() + const signature = await createAxiaSignature(proposalKey, wrongAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, wrongAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) + }) + + it('cannot add signature with signature from different axia', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const axia2 = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Axia) + const signature = await createAxiaSignature(proposalKey, axia2) + + // Try to use axia2's signature but claim it's from whitelistedAxia + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expect(res.toString()).to.be.eq( + `FailedTransactionMetadata(FailedTransactionMetadata { err: InstructionError(0, Custom(2)), meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('cannot add signature with signature from validator instead of axia', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + const validator = await createWhitelistedEntity(whitelistSdk, provider, EntityType.Validator) + const signature = await createAxiaSignature(proposalKey, validator) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, validator.publicKey, signature) + const res = await makeTxSignAndSend(solverProvider, ...ixs) + + expectTransactionError(res, `AnchorError caused by account: axia_registry. Error Code: AccountNotInitialized.`) + }) + + it('cannot add signature if signed message is wrong', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + // Sign a different message (e.g., intent hash instead of proposal key) + const intentHash = generateIntentHash() + const signature = await signAsync(Buffer.from(intentHash, 'hex'), whitelistedAxia.secretKey.slice(0, 32)) + const signatureArray = Array.from(new Uint8Array(signature)) + + const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({ + message: Buffer.from(intentHash, 'hex'), + publicKey: whitelistedAxia.publicKey.toBuffer(), + signature: Buffer.from(signatureArray), + }) + + const ix = await program.methods + .addAxiaSig() + .accountsPartial({ + solver: solverSdk.getSignerKey(), + solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), + proposal: proposalKey, + axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), + ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + const res = await makeTxSignAndSend(solverProvider, ed25519Ix, ix) + + expectTransactionError(res, `Signature verification failed`) + }) + + it('cannot add signature with corrupted ed25519 instruction', async () => { + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) + + // Create corrupted Ed25519 instruction with wrong program ID + const corruptedEd25519Ix = new TransactionInstruction({ + programId: Keypair.generate().publicKey, + keys: [], + data: Buffer.from([ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + }) + + const ix = await program.methods + .addAxiaSig() + .accountsPartial({ + solver: solverSdk.getSignerKey(), + solverRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Solver, solverSdk.getSignerKey()), + proposal: proposalKey, + axiaRegistry: solverSdk.getEntityRegistryPubkey(EntityType.Axia, whitelistedAxia.publicKey), + ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .instruction() + + const res = await makeTxSignAndSend(solverProvider, corruptedEd25519Ix, ix) + + expect(res.toString()).to.be.eq( + // eslint-disable-next-line no-secrets/no-secrets + `FailedTransactionMetadata(FailedTransactionMetadata { err: InvalidProgramForExecution, meta: TransactionMetadata { signature: 1111111111111111111111111111111111111111111111111111111111111111, logs: [], inner_instructions: [], compute_units_consumed: 0, return_data: TransactionReturnData { program_id: 11111111111111111111111111111111, data: [] } } })` + ) + }) + + it('should add signature when proposal deadline is close', async () => { + const now = Number(client.getClock().unixTimestamp) + const deadline = now + VERY_SHORT_DEADLINE + const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program, { deadline }) + + const signature = await createAxiaSignature(proposalKey, whitelistedAxia) + + const ixs = await solverSdk.addAxiaSigIxs(proposalKey, whitelistedAxia.publicKey, signature) + await makeTxSignAndSend(solverProvider, ...ixs) + + const proposalAfter = await program.account.proposal.fetch(proposalKey) + expect(proposalAfter.isSigned).to.be.true + }) + }) }) }) From 3d93df345c894880662932e908c3552248c2e94a Mon Sep 17 00:00:00 2001 From: Tintoretto Date: Wed, 17 Dec 2025 18:16:31 -0300 Subject: [PATCH 11/39] EVM: Deploy contracts to Ethereum (#51) --- packages/evm/hardhat.config.ts | 6 + .../Create3Controller#Controller.dbg.json | 4 + .../Create3Controller#Controller.json | 392 +++++ .../Create3Controller#ICreateX.dbg.json | 4 + .../artifacts/Create3Controller#ICreateX.json | 51 + .../Create3Settler#ICreateX.dbg.json | 4 + .../artifacts/Create3Settler#ICreateX.json | 51 + .../artifacts/Create3Settler#Settler.dbg.json | 4 + .../artifacts/Create3Settler#Settler.json | 1488 +++++++++++++++++ ...22028aefba6db9743fbac66c2677f7feca3ad.json | 266 +++ .../chain-1/deployed_addresses.json | 6 + .../deployments/chain-1/journal.jsonl | 20 + 12 files changed, 2296 insertions(+) create mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.dbg.json create mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.json create mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.dbg.json create mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.json create mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.dbg.json create mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.json create mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.dbg.json create mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.json create mode 100644 packages/evm/ignition/deployments/chain-1/build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json create mode 100644 packages/evm/ignition/deployments/chain-1/deployed_addresses.json create mode 100644 packages/evm/ignition/deployments/chain-1/journal.jsonl diff --git a/packages/evm/hardhat.config.ts b/packages/evm/hardhat.config.ts index 28aea2c..b7317e5 100644 --- a/packages/evm/hardhat.config.ts +++ b/packages/evm/hardhat.config.ts @@ -21,6 +21,12 @@ const config: HardhatUserConfig = { }, }, networks: { + ethereum: { + type: 'http', + chainId: 1, + url: process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com', + accounts: process.env.DEPLOYER_PRIVATE_KEY ? [process.env.DEPLOYER_PRIVATE_KEY] : [], + }, optimism: { type: 'http', chainId: 10, diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.dbg.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.dbg.json new file mode 100644 index 0000000..e872235 --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.dbg.json @@ -0,0 +1,4 @@ +{ + "_format": "hh-sol-dbg-1", + "buildInfo": "../build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.json new file mode 100644 index 0000000..91966d3 --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.json @@ -0,0 +1,392 @@ +{ + "_format": "hh3-artifact-1", + "contractName": "Controller", + "sourceName": "contracts/Controller.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "solvers", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "executors", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "proposalSigners", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "validators", + "type": "address[]" + }, + { + "internalType": "uint8", + "name": "_minValidations", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ControllerInputInvalidLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "ExecutorAllowedSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint8", + "name": "newMinValidation", + "type": "uint8" + } + ], + "name": "MinValidationSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proposalSigner", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "ProposalSignerAllowedSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "solver", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "SolverAllowedSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "ValidatorAllowedSet", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isExecutorAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isProposalSignerAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isSolverAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isValidatorAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minValidations", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "executors", + "type": "address[]" + }, + { + "internalType": "bool[]", + "name": "alloweds", + "type": "bool[]" + } + ], + "name": "setAllowedExecutors", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "signers", + "type": "address[]" + }, + { + "internalType": "bool[]", + "name": "alloweds", + "type": "bool[]" + } + ], + "name": "setAllowedProposalSigners", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "solvers", + "type": "address[]" + }, + { + "internalType": "bool[]", + "name": "alloweds", + "type": "bool[]" + } + ], + "name": "setAllowedSolvers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "validators", + "type": "address[]" + }, + { + "internalType": "bool[]", + "name": "alloweds", + "type": "bool[]" + } + ], + "name": "setAllowedValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "newMinValidations", + "type": "uint8" + } + ], + "name": "setMinValidations", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561000f575f5ffd5b50604051610e6c380380610e6c83398101604081905261002e91610444565b856001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b61006581610172565b505f5b85518110156100a35761009b8682815181106100865761008661051f565b602002602001015160016101c160201b60201c565b600101610068565b505f5b84518110156100e1576100d98582815181106100c4576100c461051f565b6020026020010151600161022060201b60201c565b6001016100a6565b505f5b835181101561011f576101178482815181106101025761010261051f565b6020026020010151600161027760201b60201c565b6001016100e4565b505f5b825181101561015d576101558382815181106101405761014061051f565b602002602001015160016102ce60201b60201c565b600101610122565b5061016781610325565b505050505050610533565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f15091015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610214565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c39101610214565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610214565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b80516001600160a01b0381168114610378575f5ffd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126103a0575f5ffd5b81516001600160401b038111156103b9576103b961037d565b604051600582901b90603f8201601f191681016001600160401b03811182821017156103e7576103e761037d565b604052918252602081850181019290810186841115610404575f5ffd5b6020860192505b8383101561042a5761041c83610362565b81526020928301920161040b565b5095945050505050565b805160ff81168114610378575f5ffd5b5f5f5f5f5f5f60c08789031215610459575f5ffd5b61046287610362565b60208801519096506001600160401b0381111561047d575f5ffd5b61048989828a01610391565b604089015190965090506001600160401b038111156104a6575f5ffd5b6104b289828a01610391565b606089015190955090506001600160401b038111156104cf575f5ffd5b6104db89828a01610391565b608089015190945090506001600160401b038111156104f8575f5ffd5b61050489828a01610391565b92505061051360a08801610434565b90509295509295509295565b634e487b7160e01b5f52603260045260245ffd5b61092c806105405f395ff3fe608060405234801561000f575f5ffd5b50600436106100da575f3560e01c80637d701102116100885780638da5cb5b116100635780638da5cb5b146101ce5780638f72c136146101e8578063d9d8edb9146101fb578063f2fde38b1461021d575f5ffd5b80637d701102146101865780637e8d9637146101a857806384aaa224146101bb575f5ffd5b806361655670116100b8578063616556701461013d578063680038c71461015f578063715018a61461017e575f5ffd5b8063090de7df146100de578063163eed9e146100f35780633d41132f1461012a575b5f5ffd5b6100f16100ec3660046107df565b610230565b005b6101156101013660046108a2565b60036020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100f16101383660046107df565b6102ae565b61011561014b3660046108a2565b60046020525f908152604090205460ff1681565b60055461016c9060ff1681565b60405160ff9091168152602001610121565b6100f1610327565b6101156101943660046108a2565b60026020525f908152604090205460ff1681565b6100f16101b63660046108c2565b61033a565b6100f16101c93660046107df565b61034e565b5f546040516001600160a01b039091168152602001610121565b6100f16101f63660046107df565b6103c7565b6101156102093660046108a2565b60016020525f908152604090205460ff1681565b6100f161022b3660046108a2565b610440565b610238610498565b805182511461025a57604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576102a183828151811061027a5761027a6108e2565b6020026020010151838381518110610294576102946108e2565b60200260200101516104dd565b60010161025c565b505050565b6102b6610498565b80518251146102d857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a95761031f8382815181106102f8576102f86108e2565b6020026020010151838381518110610312576103126108e2565b602002602001015161053c565b6001016102da565b61032f610498565b6103385f610593565b565b610342610498565b61034b816105fa565b50565b610356610498565b805182511461037857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576103bf838281518110610398576103986108e2565b60200260200101518383815181106103b2576103b26108e2565b6020026020010151610637565b60010161037a565b6103cf610498565b80518251146103f157604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a957610438838281518110610411576104116108e2565b602002602001015183838151811061042b5761042b6108e2565b602002602001015161068e565b6001016103f3565b610448610498565b6001600160a01b03811661048f576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61034b81610593565b5f546001600160a01b03163314610338576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610486565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c391015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610530565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610530565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f1509101610530565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610722576107226106e5565b604052919050565b5f67ffffffffffffffff821115610743576107436106e5565b5060051b60200190565b80356001600160a01b0381168114610763575f5ffd5b919050565b5f82601f830112610777575f5ffd5b813561078a6107858261072a565b6106f9565b8082825260208201915060208360051b8601019250858311156107ab575f5ffd5b602085015b838110156107d557803580151581146107c7575f5ffd5b8352602092830192016107b0565b5095945050505050565b5f5f604083850312156107f0575f5ffd5b823567ffffffffffffffff811115610806575f5ffd5b8301601f81018513610816575f5ffd5b80356108246107858261072a565b8082825260208201915060208360051b850101925087831115610845575f5ffd5b6020840193505b8284101561086e5761085d8461074d565b82526020938401939091019061084c565b9450505050602083013567ffffffffffffffff81111561088c575f5ffd5b61089885828601610768565b9150509250929050565b5f602082840312156108b2575f5ffd5b6108bb8261074d565b9392505050565b5f602082840312156108d2575f5ffd5b813560ff811681146108bb575f5ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212204e1a03491d67aedff2296b499cf5fee36529062002d67b286fb08859eac0e13664736f6c634300081c0033", + "deployedBytecode": "0x608060405234801561000f575f5ffd5b50600436106100da575f3560e01c80637d701102116100885780638da5cb5b116100635780638da5cb5b146101ce5780638f72c136146101e8578063d9d8edb9146101fb578063f2fde38b1461021d575f5ffd5b80637d701102146101865780637e8d9637146101a857806384aaa224146101bb575f5ffd5b806361655670116100b8578063616556701461013d578063680038c71461015f578063715018a61461017e575f5ffd5b8063090de7df146100de578063163eed9e146100f35780633d41132f1461012a575b5f5ffd5b6100f16100ec3660046107df565b610230565b005b6101156101013660046108a2565b60036020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100f16101383660046107df565b6102ae565b61011561014b3660046108a2565b60046020525f908152604090205460ff1681565b60055461016c9060ff1681565b60405160ff9091168152602001610121565b6100f1610327565b6101156101943660046108a2565b60026020525f908152604090205460ff1681565b6100f16101b63660046108c2565b61033a565b6100f16101c93660046107df565b61034e565b5f546040516001600160a01b039091168152602001610121565b6100f16101f63660046107df565b6103c7565b6101156102093660046108a2565b60016020525f908152604090205460ff1681565b6100f161022b3660046108a2565b610440565b610238610498565b805182511461025a57604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576102a183828151811061027a5761027a6108e2565b6020026020010151838381518110610294576102946108e2565b60200260200101516104dd565b60010161025c565b505050565b6102b6610498565b80518251146102d857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a95761031f8382815181106102f8576102f86108e2565b6020026020010151838381518110610312576103126108e2565b602002602001015161053c565b6001016102da565b61032f610498565b6103385f610593565b565b610342610498565b61034b816105fa565b50565b610356610498565b805182511461037857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576103bf838281518110610398576103986108e2565b60200260200101518383815181106103b2576103b26108e2565b6020026020010151610637565b60010161037a565b6103cf610498565b80518251146103f157604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a957610438838281518110610411576104116108e2565b602002602001015183838151811061042b5761042b6108e2565b602002602001015161068e565b6001016103f3565b610448610498565b6001600160a01b03811661048f576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61034b81610593565b5f546001600160a01b03163314610338576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610486565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c391015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610530565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610530565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f1509101610530565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610722576107226106e5565b604052919050565b5f67ffffffffffffffff821115610743576107436106e5565b5060051b60200190565b80356001600160a01b0381168114610763575f5ffd5b919050565b5f82601f830112610777575f5ffd5b813561078a6107858261072a565b6106f9565b8082825260208201915060208360051b8601019250858311156107ab575f5ffd5b602085015b838110156107d557803580151581146107c7575f5ffd5b8352602092830192016107b0565b5095945050505050565b5f5f604083850312156107f0575f5ffd5b823567ffffffffffffffff811115610806575f5ffd5b8301601f81018513610816575f5ffd5b80356108246107858261072a565b8082825260208201915060208360051b850101925087831115610845575f5ffd5b6020840193505b8284101561086e5761085d8461074d565b82526020938401939091019061084c565b9450505050602083013567ffffffffffffffff81111561088c575f5ffd5b61089885828601610768565b9150509250929050565b5f602082840312156108b2575f5ffd5b6108bb8261074d565b9392505050565b5f602082840312156108d2575f5ffd5b813560ff811681146108bb575f5ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212204e1a03491d67aedff2296b499cf5fee36529062002d67b286fb08859eac0e13664736f6c634300081c0033", + "linkReferences": {}, + "deployedLinkReferences": {}, + "immutableReferences": {}, + "inputSourceName": "project/contracts/Controller.sol", + "buildInfoId": "27822028aefba6db9743fbac66c2677f7feca3ad" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.dbg.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.dbg.json new file mode 100644 index 0000000..e872235 --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.dbg.json @@ -0,0 +1,4 @@ +{ + "_format": "hh-sol-dbg-1", + "buildInfo": "../build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.json new file mode 100644 index 0000000..0b1d6cc --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.json @@ -0,0 +1,51 @@ +{ + "_format": "hh3-artifact-1", + "contractName": "ICreateX", + "sourceName": "contracts/interfaces/ICreateX.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newContract", + "type": "address" + } + ], + "name": "ContractCreation", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + } + ], + "name": "deployCreate3", + "outputs": [ + { + "internalType": "address", + "name": "newContract", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {}, + "immutableReferences": {}, + "inputSourceName": "project/contracts/interfaces/ICreateX.sol", + "buildInfoId": "27822028aefba6db9743fbac66c2677f7feca3ad" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.dbg.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.dbg.json new file mode 100644 index 0000000..e872235 --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.dbg.json @@ -0,0 +1,4 @@ +{ + "_format": "hh-sol-dbg-1", + "buildInfo": "../build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.json new file mode 100644 index 0000000..0b1d6cc --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.json @@ -0,0 +1,51 @@ +{ + "_format": "hh3-artifact-1", + "contractName": "ICreateX", + "sourceName": "contracts/interfaces/ICreateX.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newContract", + "type": "address" + } + ], + "name": "ContractCreation", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + } + ], + "name": "deployCreate3", + "outputs": [ + { + "internalType": "address", + "name": "newContract", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {}, + "immutableReferences": {}, + "inputSourceName": "project/contracts/interfaces/ICreateX.sol", + "buildInfoId": "27822028aefba6db9743fbac66c2677f7feca3ad" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.dbg.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.dbg.json new file mode 100644 index 0000000..e872235 --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.dbg.json @@ -0,0 +1,4 @@ +{ + "_format": "hh-sol-dbg-1", + "buildInfo": "../build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.json new file mode 100644 index 0000000..d6affec --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.json @@ -0,0 +1,1488 @@ +{ + "_format": "hh3-artifact-1", + "contractName": "Settler", + "sourceName": "contracts/Settler.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_controller", + "type": "address" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [], + "name": "ECDSAInvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [], + "name": "FailedCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShortString", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposed", + "type": "uint256" + } + ], + "name": "SettlerAmountOutLtProposed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "executor", + "type": "address" + } + ], + "name": "SettlerExecutorNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "SettlerIntentPastDeadline", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "current", + "type": "uint256" + } + ], + "name": "SettlerIntentValidationsNotEnough", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + } + ], + "name": "SettlerInvalidChain", + "type": "error" + }, + { + "inputs": [], + "name": "SettlerInvalidProposedAmounts", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "SettlerInvalidRecipient", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "settler", + "type": "address" + } + ], + "name": "SettlerInvalidSettler", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "nonce", + "type": "bytes32" + } + ], + "name": "SettlerNonceAlreadyUsed", + "type": "error" + }, + { + "inputs": [], + "name": "SettlerNonceZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "post", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pre", + "type": "uint256" + } + ], + "name": "SettlerPostBalanceOutLtPre", + "type": "error" + }, + { + "inputs": [], + "name": "SettlerProposalDataNotEmpty", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "SettlerProposalPastDeadline", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "SettlerProposalSignerNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmount", + "type": "uint256" + } + ], + "name": "SettlerProposedAmountLtMinAmount", + "type": "error" + }, + { + "inputs": [], + "name": "SettlerRescueFundsRecipientZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gasUsed", + "type": "uint256" + } + ], + "name": "SettlerSimulationSuccess", + "type": "error" + }, + { + "inputs": [], + "name": "SettlerSolverFeeInvalidLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "max", + "type": "uint256" + } + ], + "name": "SettlerSolverFeeTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "solver", + "type": "address" + } + ], + "name": "SettlerSolverNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "lengthRequested", + "type": "uint256" + } + ], + "name": "SettlerTooManySafeguards", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "op", + "type": "uint8" + } + ], + "name": "SettlerUnknownIntentType", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "SettlerUserNotSmartAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "previous", + "type": "address" + }, + { + "internalType": "address", + "name": "current", + "type": "address" + } + ], + "name": "SettlerValidatorDuplicatedOrUnsorted", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "SettlerValidatorNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "SmartAccountsHandlerZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "StringTooLong", + "type": "error" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "FundsRescued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "topic", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint8", + "name": "op", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "op", + "type": "uint8" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "settler", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "nonce", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct MaxFee[]", + "name": "maxFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "topic", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IntentEvent[]", + "name": "events", + "type": "tuple[]" + }, + { + "internalType": "bytes", + "name": "configSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minValidations", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "validations", + "type": "bytes[]" + } + ], + "indexed": false, + "internalType": "struct Intent", + "name": "intent", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "fees", + "type": "uint256[]" + } + ], + "indexed": false, + "internalType": "struct Proposal", + "name": "proposal", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "output", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "IntentExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "intentsValidator", + "type": "address" + } + ], + "name": "IntentsValidatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "ProposalExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "SafeguardSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "smartAccountsHandler", + "type": "address" + } + ], + "name": "SmartAccountsHandlerSet", + "type": "event" + }, + { + "inputs": [], + "name": "controller", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint8", + "name": "op", + "type": "uint8" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "settler", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "nonce", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct MaxFee[]", + "name": "maxFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "topic", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IntentEvent[]", + "name": "events", + "type": "tuple[]" + }, + { + "internalType": "bytes", + "name": "configSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minValidations", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "validations", + "type": "bytes[]" + } + ], + "internalType": "struct Intent", + "name": "intent", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "fees", + "type": "uint256[]" + } + ], + "internalType": "struct Proposal", + "name": "proposal", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct Execution[]", + "name": "executions", + "type": "tuple[]" + } + ], + "name": "execute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint8", + "name": "op", + "type": "uint8" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "settler", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "nonce", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct MaxFee[]", + "name": "maxFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "topic", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IntentEvent[]", + "name": "events", + "type": "tuple[]" + }, + { + "internalType": "bytes", + "name": "configSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minValidations", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "validations", + "type": "bytes[]" + } + ], + "internalType": "struct Intent", + "name": "intent", + "type": "tuple" + } + ], + "name": "getIntentHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "getNonceBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "fees", + "type": "uint256[]" + } + ], + "internalType": "struct Proposal", + "name": "proposal", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "op", + "type": "uint8" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "settler", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "nonce", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct MaxFee[]", + "name": "maxFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "topic", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IntentEvent[]", + "name": "events", + "type": "tuple[]" + }, + { + "internalType": "bytes", + "name": "configSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minValidations", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "validations", + "type": "bytes[]" + } + ], + "internalType": "struct Intent", + "name": "intent", + "type": "tuple" + }, + { + "internalType": "address", + "name": "solver", + "type": "address" + } + ], + "name": "getProposalHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getUserSafeguard", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "intentsValidator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "rescueFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newIntentsValidator", + "type": "address" + } + ], + "name": "setIntentsValidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "safeguard", + "type": "bytes" + } + ], + "name": "setSafeguard", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newSmartAccountsHandler", + "type": "address" + } + ], + "name": "setSmartAccountsHandler", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint8", + "name": "op", + "type": "uint8" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "settler", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "nonce", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct MaxFee[]", + "name": "maxFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "topic", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IntentEvent[]", + "name": "events", + "type": "tuple[]" + }, + { + "internalType": "bytes", + "name": "configSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minValidations", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "validations", + "type": "bytes[]" + } + ], + "internalType": "struct Intent", + "name": "intent", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "fees", + "type": "uint256[]" + } + ], + "internalType": "struct Proposal", + "name": "proposal", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct Execution[]", + "name": "executions", + "type": "tuple[]" + } + ], + "name": "simulate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "smartAccountsHandler", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "bytecode": "0x610180604052348015610010575f5ffd5b506040516150e63803806150e683398101604081905261002f916102aa565b604080518082018252601681527f4d696d69632050726f746f636f6c20536574746c657200000000000000000000602080830191909152825180840190935260018352603160f81b9083015290826001600160a01b0381166100ab57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6100b4816101c4565b50600180556100c4826002610213565b610120526100d3816003610213565b61014052815160208084019190912060e052815190820120610100524660a05261015f60e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60805250503060c0526001600160a01b0382166101605260405161018290610282565b604051809103905ff08015801561019b573d5f5f3e3d5ffd5b50600480546001600160a01b0319166001600160a01b0392909216919091179055506104859050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f60208351101561022e5761022783610245565b905061023f565b816102398482610373565b5060ff90505b92915050565b5f5f829050601f8151111561026f578260405163305a27a960e01b81526004016100a2919061042d565b805161027a82610462565b179392505050565b6109c58061472183390190565b80516001600160a01b03811681146102a5575f5ffd5b919050565b5f5f604083850312156102bb575f5ffd5b6102c48361028f565b91506102d26020840161028f565b90509250929050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061030357607f821691505b60208210810361032157634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561036e57805f5260205f20601f840160051c8101602085101561034c5750805b601f840160051c820191505b8181101561036b575f8155600101610358565b50505b505050565b81516001600160401b0381111561038c5761038c6102db565b6103a08161039a84546102ef565b84610327565b6020601f8211600181146103d2575f83156103bb5750848201515b5f19600385901b1c1916600184901b17845561036b565b5f84815260208120601f198516915b8281101561040157878501518255602094850194600190920191016103e1565b508482101561041e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80516020808301519190811015610321575f1960209190910360031b1b16919050565b60805160a05160c05160e0516101005161012051610140516101605161421761050a5f395f81816103440152818161049c015281816106cd015281816112770152818161147401528181611585015261223b01525f610bd601525f610ba401525f61294301525f61291b01525f61287601525f6128a001525f6128ca01526142175ff3fe60806040526004361061010c575f3560e01c80638da5cb5b116100a1578063cbc9104511610071578063f2fde38b11610057578063f2fde38b14610314578063f77c479114610333578063fee2b7a714610366575f5ffd5b8063cbc91045146102d6578063f0801868146102f5575f5ffd5b80638da5cb5b14610239578063911929df14610255578063c422661f14610281578063c4bb6af5146102a0575f5ffd5b8063715018a6116100dc578063715018a6146101c057806377c8fb6b146101d45780637f411c79146101f357806384b0196e14610212575f5ffd5b80630fc27cf6146101175780632e5a2458146101385780634987ed2f1461016a5780636ccae054146101a1575f5ffd5b3661011357005b5f5ffd5b348015610122575f5ffd5b50610136610131366004612dc9565b610385565b005b348015610143575f5ffd5b50610157610152366004613366565b610399565b6040519081526020015b60405180910390f35b348015610175575f5ffd5b50600454610189906001600160a01b031681565b6040516001600160a01b039091168152602001610161565b3480156101ac575f5ffd5b506101366101bb3660046133de565b6103af565b3480156101cb575f5ffd5b50610136610465565b3480156101df575f5ffd5b50600554610189906001600160a01b031681565b3480156101fe575f5ffd5b5061013661020d36600461341c565b610478565b34801561021d575f5ffd5b50610226610587565b60405161016197969594939291906135ca565b348015610244575f5ffd5b505f546001600160a01b0316610189565b348015610260575f5ffd5b5061027461026f366004612dc9565b6105e5565b6040516101619190613653565b34801561028c575f5ffd5b5061013661029b366004612dc9565b61068e565b3480156102ab575f5ffd5b506101576102ba366004613665565b600660209081525f928352604080842090915290825290205481565b3480156102e1575f5ffd5b506101366102f036600461368f565b61069f565b348015610300575f5ffd5b5061013661030f36600461341c565b6106a9565b34801561031f575f5ffd5b5061013661032e366004612dc9565b61076e565b34801561033e575f5ffd5b506101897f000000000000000000000000000000000000000000000000000000000000000081565b348015610371575f5ffd5b506101576103803660046136c9565b6107c1565b61038d6107d1565b61039681610816565b50565b5f6103a58484846108ac565b90505b9392505050565b6103b76107d1565b6103bf610948565b6001600160a01b0382166103ff576040517f54328a0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61040a83838361098b565b816001600160a01b0316836001600160a01b03167fed2837b80b3489773c5f1ed30dd2884b4c90dbf7b87428ea03c49b24ef59a8058360405161044f91815260200190565b60405180910390a361046060018055565b505050565b61046d6107d1565b6104765f6109c4565b565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa1580156104e3573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050791906136fb565b6105345760405163a7ac57a960e01b81526001600160a01b03821660048201526024015b60405180910390fd5b5f5a9050610543836001610a20565b5f5a61054f908361371a565b9050806040517fa523ba4900000000000000000000000000000000000000000000000000000000815260040161052b91815260200190565b5f6060805f5f5f6060610598610b9d565b6105a0610bcf565b604080515f808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009b939a50919850469750309650945092509050565b6001600160a01b0381165f90815260076020526040902080546060919061060b90613739565b80601f016020809104026020016040519081016040528092919081815260200182805461063790613739565b80156106825780601f1061065957610100808354040283529160200191610682565b820191905f5260205f20905b81548152906001019060200180831161066557829003601f168201915b50505050509050919050565b6106966107d1565b61039681610bfc565b6103963382610c52565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa158015610714573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061073891906136fb565b6107605760405163a7ac57a960e01b81526001600160a01b038216600482015260240161052b565b61076a825f610a20565b5050565b6107766107d1565b6001600160a01b0381166107b8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f600482015260240161052b565b610396816109c4565b5f6107cb82610ccb565b92915050565b5f546001600160a01b03163314610476576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052b565b6001600160a01b038116610856576040517f42af048a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f93fa00498c991ad852fbb3361afb0e3507720aa9ff69a8386009b76134a6e654905f90a250565b5f7fabd98b1a5498f7ce6ce5fd671f1a1c2ad437b155c4c12281f925612823c72e886108d784610ccb565b83865f01518760200151805190602001206108f58960400151610d6e565b6040805160208101979097528601949094526001600160a01b039092166060850152608084015260a083015260c082015260e0016040516020818303038152906040528051906020012090509392505050565b600260015403610984576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036109b9576104608282610d80565b610460838383610e2b565b5f80546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610a28610948565b5f5b8251811015610b93575f838281518110610a4657610a4661376b565b60200260200101515f015190505f848381518110610a6657610a6661376b565b60200260200101516020015190505f858481518110610a8757610a8761376b565b6020026020010151604001519050610aa183838388610e9f565b6020838101516001600160a01b03165f9081526006825260408082206060870151835290925220439055825160ff16610ae357610ade838361164c565b610b4a565b825160ff165f1901610af957610ade83836119d0565b825160ff1660011901610b1057610ade8383611a90565b82516040517f7cdbcbf100000000000000000000000000000000000000000000000000000000815260ff909116600482015260240161052b565b610b558284336108ac565b6040518581527f120985525cfb78f6c03f96986f9687f49caf81e29350d21fa052e8c0df211ebb9060200160405180910390a2505050600101610a2a565b5061076a60018055565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006002611bb6565b905090565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006003611bb6565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f493e783f4ed464086f434b9d42b1b8fb97375d1fdc91692c73551281526d22be905f90a250565b6001600160a01b0382165f908152600760205260408120610c7291612d60565b6001600160a01b0382165f908152600760205260409020610c9382826137d7565b506040516001600160a01b038316907f5ac98b81e10e07689d7de8e9ec09900564d65647de8d039328d4cbdf575b28f4905f90a25050565b5f7fbd51147cb801db5aa8dc80920aabcbe45ea95c1767e31bc3339f62d8f0d046f9825f015183602001518460400151856060015186608001518760a0015180519060200120610d1e8960c00151611c60565b610d2b8a60e00151611da6565b8a61010001518b6101200151604051602001610d519b9a99989796959493929190613892565b604051602081830303815290604052805190602001209050919050565b5f81604051602001610d51919061390c565b80471015610dc3576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810182905260440161052b565b5f5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610e0d576040519150601f19603f3d011682016040523d82523d5f602084013e610e12565b606091505b509150915081610e2557610e2581611eba565b50505050565b6040516001600160a01b0383811660248301526044820183905261046091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611efc565b60408401516001600160a01b03163014610ef65760408085015190517f30b078bf0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6060840151610f31576040517f58e8d18100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808501516001600160a01b03165f90815260068252604080822060608801518352909252205415610fab57602084015160608501516040517f9756f94d0000000000000000000000000000000000000000000000000000000081526001600160a01b039092166004830152602482015260440161052b565b6005546001600160a01b0316156110e4576020808501516001600160a01b03165f9081526007909152604081208054610fe390613739565b80601f016020809104026020016040519081016040528092919081815260200182805461100f90613739565b801561105a5780601f106110315761010080835404028352916020019161105a565b820191905f5260205f20905b81548152906001019060200180831161103d57829003601f168201915b505050505090505f815111156110e2576005546040517fdbd902180000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063dbd90218906110b59088908590600401613b29565b5f6040518083038186803b1580156110cb575f5ffd5b505afa1580156110dd573d5f5f3e3d5ffd5b505050505b505b5f6110ee85611f81565b9050801561118d57428560800151116111425760808501516040517f95d3dc22000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b8351421080159061118b5784516040517f2c39717b000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b505b8360400151518560c0015151146111d0576040517f9dca144800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8560c0015151811015611273575f8660c0015182815181106111f6576111f661376b565b60200260200101516020015190505f8660400151838151811061121b5761121b61376b565b6020026020010151905081811115611269576040517f8ffa61fa000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161052b565b50506001016111d2565b505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663680038c76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112d1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190613b4d565b90505f8160ff1687610120015111611310578160ff16611317565b8661012001515b90508087610140015151101561136b57610140870151516040517fcd0ce69900000000000000000000000000000000000000000000000000000000815261052b918391600401918252602082015260400190565b5f5f90505f60405180602001604052806113848b610ccb565b905290505f61139a61139583611fd0565b61200d565b90505f5b8a61014001515181101561152f575f6113d5838d610140015184815181106113c8576113c861376b565b6020026020010151612054565b9050846001600160a01b0316816001600160a01b031611611435576040517fce98854c0000000000000000000000000000000000000000000000000000000081526001600160a01b0380871660048301528216602482015260440161052b565b6040517f616556700000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015291955085915f917f000000000000000000000000000000000000000000000000000000000000000090911690636165567090602401602060405180830381865afa1580156114bb573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114df91906136fb565b1590508015611525576040517ff24706660000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505060010161139e565b505f6115486115426113958c8e336108ac565b8a612054565b6040517f163eed9e0000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301529192505f917f0000000000000000000000000000000000000000000000000000000000000000169063163eed9e90602401602060405180830381865afa1580156115ca573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ee91906136fb565b1580156115f9575088155b9050801561163e576040517f100158a80000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505050505050505050505050565b5f8260a001518060200190518101906116659190613c16565b90505f82602001518060200190518101906116809190613d93565b905061168c828261207c565b60208401516004545f916116a9916001600160a01b0316906122ed565b905046835f01510361170b575f5b836040015151811015611709575f846040015182815181106116db576116db61376b565b60200260200101519050611700815f01518860200151865f0151846020015187612371565b506001016116b7565b505b5f611715846123b1565b83516040517f7b4da5de0000000000000000000000000000000000000000000000000000000081529192506001600160a01b031690637b4da5de906117609089908990600401613efe565b5f604051808303815f87803b158015611777575f5ffd5b505af1158015611789573d5f5f3e3d5ffd5b50505050468460200151036119c8575f84606001515167ffffffffffffffff8111156117b7576117b7612de4565b6040519080825280602002602001820160405280156117e0578160200160208202803683370190505b5090505f5b856060015151811015611990575f866060015182815181106118095761180961376b565b602002602001015190505f611821825f01513061246d565b90505f8584815181106118365761183661376b565b602002602001015190508082101561188b576040517ff5ad5ab700000000000000000000000000000000000000000000000000000000815260048101859052602481018390526044810182905260640161052b565b611895818361371a565b8585815181106118a7576118a761376b565b6020026020010181815250505f886040015185815181106118ca576118ca61376b565b60200260200101519050808686815181106118e7576118e761376b565b6020026020010151101561195557848686815181106119085761190861376b565b60209081029190910101516040517ff0a43aa7000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044810182905260640161052b565b611980845f015185604001518888815181106119735761197361376b565b602002602001015161098b565b5050600190920191506117e59050565b506119bb8787836040516020016119a79190613f22565b604051602081830303815290604052612524565b6119c68787856125b7565b505b505050505050565b5f8260a001518060200190518101906119e99190613f34565b90506119f5818361263b565b60208301516004545f91611a12916001600160a01b0316906122ed565b90505f5b826020015151811015611a69575f83602001518281518110611a3a57611a3a61376b565b60200260200101519050611a60815f015187602001518360400151846020015187612371565b50600101611a16565b50604080515f815260208101909152611a859085908590612524565b610e258484836125b7565b5f8260a00151806020019051810190611aa99190613fc0565b9050611aba818385602001516126fc565b5f81602001515167ffffffffffffffff811115611ad957611ad9612de4565b604051908082528060200260200182016040528015611b0c57816020015b6060815260200190600190039081611af75790505b5090505f5b826020015151811015611b93575f83602001518281518110611b3557611b3561376b565b602090810291909101810151878201518151928201516040830151600454939550611b6d946001600160a01b0390941693919061279a565b838381518110611b7f57611b7f61376b565b602090810291909101015250600101611b11565b50611baa8484836040516020016119a7919061410a565b610e25848460016125b7565b606060ff8314611bd057611bc98361282d565b90506107cb565b818054611bdc90613739565b80601f0160208091040260200160405190810160405280929190818152602001828054611c0890613739565b8015611c535780601f10611c2a57610100808354040283529160200191611c53565b820191905f5260205f20905b815481529060010190602001808311611c3657829003601f168201915b5050505050905092915050565b5f5f825167ffffffffffffffff811115611c7c57611c7c612de4565b604051908082528060200260200182016040528015611ca5578160200160208202803683370190505b5090505f5b8351811015611d76577fe44101eb81f50b64d4005e31312ea7bafd6f81357f9dce4fe51d5f2dcb939da4848281518110611ce657611ce661376b565b60200260200101515f0151858381518110611d0357611d0361376b565b602002602001015160200151604051602001611d3b939291909283526001600160a01b03919091166020830152604082015260600190565b60405160208183030381529060405280519060200120828281518110611d6357611d6361376b565b6020908102919091010152600101611caa565b5080604051602001611d88919061390c565b60405160208183030381529060405280519060200120915050919050565b5f5f825167ffffffffffffffff811115611dc257611dc2612de4565b604051908082528060200260200182016040528015611deb578160200160208202803683370190505b5090505f5b8351811015611d76577ff7a396f96f3810db1cfde06de38d32fc29d56b63cf9d9aee2c10c91ef7273bb1848281518110611e2c57611e2c61376b565b60200260200101515f0151858381518110611e4957611e4961376b565b60200260200101516020015180519060200120604051602001611e7f939291909283526020830191909152604082015260600190565b60405160208183030381529060405280519060200120828281518110611ea757611ea761376b565b6020908102919091010152600101611df0565b805115611eca5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f60205f8451602086015f885af180611f1b576040513d5f823e3d81fd5b50505f513d91508115611f32578060011415611f3f565b6001600160a01b0384163b155b15610e25576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b80515f9060ff1615611f9557506001919050565b5f8260a00151806020019051810190611fae9190613c16565b90508060200151815f015103611fc75750600192915050565b51461492915050565b8051604080517f07094367b2db06e3dbb414cf947644f8e08067ea238c5f41cb2fa5fea5be628e6020820152908101919091525f90606001610d51565b5f6107cb61201961286a565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b5f5f5f5f6120628686612993565b92509250925061207282826129dc565b5090949350505050565b81515f904614801590612093575046836020015114155b905080156120b65760405163308a43dd60e11b815246600482015260240161052b565b826060015151826040015151146120f9576040517f1816c20200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8360600151518110156121f2575f8460600151828151811061211f5761211f61376b565b602002602001015190505f81604001519050306001600160a01b0316816001600160a01b03160361216e57604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b5f826020015190505f8660400151858151811061218d5761218d61376b565b60200260200101519050818110156121e2576040517f1fef5f3300000000000000000000000000000000000000000000000000000000815260048101869052602481018290526044810183905260640161052b565b5050600190920191506120fb9050565b5060208301518351146104605781516040517f7d7011020000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201525f917f00000000000000000000000000000000000000000000000000000000000000001690637d70110290602401602060405180830381865afa158015612280573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122a491906136fb565b1590508015610e255782516040517fdb489af40000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6040517f5e07a6e60000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301525f9190841690635e07a6e690602401602060405180830381865afa15801561234d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a891906136fb565b801561239557600454612390906001600160a01b031685878686612adf565b6123aa565b6123aa6001600160a01b038616858585612b38565b5050505050565b606081606001515167ffffffffffffffff8111156123d1576123d1612de4565b6040519080825280602002602001820160405280156123fa578160200160208202803683370190505b50905046826020015103612468575f5b82606001515181101561246657612441836060015182815181106124305761243061376b565b60200260200101515f01513061246d565b8282815181106124535761245361376b565b602090810291909101015260010161240a565b505b919050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036124a357506001600160a01b038116316107cb565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015612500573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc9919061411c565b5f5b8360e0015151811015610e25575f8460e00151828151811061254a5761254a61376b565b60200260200101519050845f015160ff16815f015186602001516001600160a01b03167fee2c98c99b683f71058b8744fea294a411482f07d55c98b392917e9286f22f1388888887602001516040516125a69493929190614133565b60405180910390a450600101612526565b6020830151335f5b8560c00151518110156119c8575f8660c0015182815181106125e3576125e361376b565b60200260200101515f01519050612604816001600160a01b03166103481490565b61263257612632818585896040015186815181106126245761262461376b565b602002602001015189612371565b506001016125bf565b8151461461265e5760405163308a43dd60e11b815246600482015260240161052b565b60208101515115612682576040516393c58e1160e01b815260040160405180910390fd5b5f5b826020015151811015610460575f836020015182815181106126a8576126a861376b565b6020026020010151604001519050306001600160a01b0316816001600160a01b0316036126f357604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b50600101612684565b8251461461271f5760405163308a43dd60e11b815246600482015260240161052b565b60208201515115612743576040516393c58e1160e01b815260040160405180910390fd5b600454612759906001600160a01b0316826122ed565b610460576040517fcaec9bac0000000000000000000000000000000000000000000000000000000081526001600160a01b038216600482015260240161052b565b60606128238663ff883fcf60e01b878787876040516024016127bf949392919061418a565b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612b71565b9695505050505050565b60605f61283983612be3565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156128c257507f000000000000000000000000000000000000000000000000000000000000000046145b156128ec57507f000000000000000000000000000000000000000000000000000000000000000090565b610bca604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f5f83516041036129ca576020840151604085015160608601515f1a6129bc88828585612c23565b9550955095505050506129d5565b505081515f91506002905b9250925092565b5f8260038111156129ef576129ef61377f565b036129f8575050565b6001826003811115612a0c57612a0c61377f565b03612a43576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826003811115612a5757612a5761377f565b03612a91576040517ffce698f70000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6003826003811115612aa557612aa561377f565b0361076a576040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6040516001600160a01b038086166024830152808516604483015283166064820152608481018290526119c89086907ff18d03cc000000000000000000000000000000000000000000000000000000009060a4016127bf565b6040516001600160a01b038481166024830152838116604483015260648201839052610e259186918216906323b872dd90608401610e58565b60605f5f846001600160a01b031684604051612b8d91906141cb565b5f60405180830381855af49150503d805f8114612bc5576040519150601f19603f3d011682016040523d82523d5f602084013e612bca565b606091505b5091509150612bda858383612ceb565b95945050505050565b5f60ff8216601f8111156107cb576040517fb3512b0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115612c5c57505f91506003905082612ce1565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015612cad573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116612cd857505f925060019150829050612ce1565b92505f91508190505b9450945094915050565b606082612d0057612cfb82611eba565b6103a8565b8151158015612d1757506001600160a01b0384163b155b15612d59576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b50806103a8565b508054612d6c90613739565b5f825580601f10612d7b575050565b601f0160209004905f5260205f209081019061039691905b80821115612da6575f8155600101612d93565b5090565b6001600160a01b0381168114610396575f5ffd5b803561246881612daa565b5f60208284031215612dd9575f5ffd5b81356103a881612daa565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b60405290565b6040805190810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051610160810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b6040516080810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612eb457612eb4612de4565b604052919050565b5f67ffffffffffffffff821115612ed557612ed5612de4565b50601f01601f191660200190565b5f82601f830112612ef2575f5ffd5b8135612f05612f0082612ebc565b612e8b565b818152846020838601011115612f19575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f67ffffffffffffffff821115612f4e57612f4e612de4565b5060051b60200190565b5f60608284031215612f68575f5ffd5b612f70612df8565b823581529050602082013567ffffffffffffffff811115612f8f575f5ffd5b612f9b84828501612ee3565b602083015250604082013567ffffffffffffffff811115612fba575f5ffd5b8201601f81018413612fca575f5ffd5b8035612fd8612f0082612f35565b8082825260208201915060208360051b850101925086831115612ff9575f5ffd5b6020840193505b8284101561301b578335825260209384019390910190613000565b60408501525091949350505050565b60ff81168114610396575f5ffd5b80356124688161302a565b5f82601f830112613052575f5ffd5b8135613060612f0082612f35565b8082825260208201915060208360061b860101925085831115613081575f5ffd5b602085015b838110156130cc576040818803121561309d575f5ffd5b6130a5612e21565b81356130b081612daa565b8152602082810135818301529084529290920191604001613086565b5095945050505050565b5f82601f8301126130e5575f5ffd5b81356130f3612f0082612f35565b8082825260208201915060208360051b860101925085831115613114575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613137575f5ffd5b86016040818903601f1901121561314c575f5ffd5b613154612e21565b60208201358152604082013567ffffffffffffffff811115613174575f5ffd5b6131838a602083860101612ee3565b6020830152508085525050602083019250602081019050613119565b5f82601f8301126131ae575f5ffd5b81356131bc612f0082612f35565b8082825260208201915060208360051b8601019250858311156131dd575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613200575f5ffd5b61320f886020838a0101612ee3565b845250602092830192016131e2565b5f610160828403121561322f575f5ffd5b613237612e44565b905061324282613038565b815261325060208301612dbe565b602082015261326160408301612dbe565b6040820152606082810135908201526080808301359082015260a082013567ffffffffffffffff811115613293575f5ffd5b61329f84828501612ee3565b60a08301525060c082013567ffffffffffffffff8111156132be575f5ffd5b6132ca84828501613043565b60c08301525060e082013567ffffffffffffffff8111156132e9575f5ffd5b6132f5848285016130d6565b60e08301525061010082013567ffffffffffffffff811115613315575f5ffd5b61332184828501612ee3565b61010083015250610120828101359082015261014082013567ffffffffffffffff81111561334d575f5ffd5b6133598482850161319f565b6101408301525092915050565b5f5f5f60608486031215613378575f5ffd5b833567ffffffffffffffff81111561338e575f5ffd5b61339a86828701612f58565b935050602084013567ffffffffffffffff8111156133b6575f5ffd5b6133c28682870161321e565b92505060408401356133d381612daa565b809150509250925092565b5f5f5f606084860312156133f0575f5ffd5b83356133fb81612daa565b9250602084013561340b81612daa565b929592945050506040919091013590565b5f6020828403121561342c575f5ffd5b813567ffffffffffffffff811115613442575f5ffd5b8201601f81018413613452575f5ffd5b8035613460612f0082612f35565b8082825260208201915060208360051b850101925086831115613481575f5ffd5b602084015b8381101561355757803567ffffffffffffffff8111156134a4575f5ffd5b85016060818a03601f190112156134b9575f5ffd5b6134c1612df8565b602082013567ffffffffffffffff8111156134da575f5ffd5b6134e98b60208386010161321e565b825250604082013567ffffffffffffffff811115613505575f5ffd5b6135148b602083860101612f58565b602083015250606082013567ffffffffffffffff811115613533575f5ffd5b6135428b602083860101612ee3565b60408301525084525060209283019201613486565b509695505050505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f8151808452602084019350602083015f5b828110156135c05781518652602095860195909101906001016135a2565b5093949350505050565b7fff000000000000000000000000000000000000000000000000000000000000008816815260e060208201525f61360460e0830189613562565b82810360408401526136168189613562565b90508660608401526001600160a01b03861660808401528460a084015282810360c08401526136458185613590565b9a9950505050505050505050565b602081525f6103a86020830184613562565b5f5f60408385031215613676575f5ffd5b823561368181612daa565b946020939093013593505050565b5f6020828403121561369f575f5ffd5b813567ffffffffffffffff8111156136b5575f5ffd5b6136c184828501612ee3565b949350505050565b5f602082840312156136d9575f5ffd5b813567ffffffffffffffff8111156136ef575f5ffd5b6136c18482850161321e565b5f6020828403121561370b575f5ffd5b815180151581146103a8575f5ffd5b818103818111156107cb57634e487b7160e01b5f52601160045260245ffd5b600181811c9082168061374d57607f821691505b60208210810361246657634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b601f82111561046057805f5260205f20601f840160051c810160208510156137b85750805b601f840160051c820191505b818110156123aa575f81556001016137c4565b815167ffffffffffffffff8111156137f1576137f1612de4565b613805816137ff8454613739565b84613793565b6020601f821160018114613837575f83156138205750848201515b5f19600385901b1c1916600184901b1784556123aa565b5f84815260208120601f198516915b828110156138665787850151825560209485019460019092019101613846565b508482101561388357868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8b815260ff8b1660208201526001600160a01b038a1660408201526001600160a01b03891660608201528760808201528660a08201528560c08201528460e0820152836101008201526101606101208201525f6138f3610160830185613562565b9050826101408301529c9b505050505050505050505050565b81515f90829060208501835b82811015613936578151845260209384019390910190600101613918565b509195945050505050565b5f8151808452602084019350602083015f5b828110156135c057815180516001600160a01b031687526020908101518188015260409096019590910190600101613953565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f1985840301885281518051845260208101519050604060208501526139d46040850182613562565b6020998a01999094509290920191506001016139a2565b50909695505050505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f19858403018852613a2f838351613562565b6020988901989093509190910190600101613a13565b805160ff1682525f6020820151613a6760208501826001600160a01b03169052565b506040820151613a8260408501826001600160a01b03169052565b50606082015160608401526080820151608084015260a082015161016060a0850152613ab2610160850182613562565b905060c083015184820360c0860152613acb8282613941565b91505060e083015184820360e0860152613ae58282613986565b915050610100830151848203610100860152613b018282613562565b915050610120830151610120850152610140830151848203610140860152612bda82826139f7565b604081525f613b3b6040830185613a45565b8281036020840152612bda8185613562565b5f60208284031215613b5d575f5ffd5b81516103a88161302a565b5f613b75612f0084612f35565b83815290506020810160608402830185811115613b90575f5ffd5b835b81811015613bee575f60608289031215613baa575f5ffd5b613bb2612df8565b90508151613bbf81612daa565b8152602082810151908201526040820151613bd981612daa565b60408201528352602090920191606001613b92565b5050509392505050565b5f82601f830112613c07575f5ffd5b6103a883835160208501613b68565b5f60208284031215613c26575f5ffd5b815167ffffffffffffffff811115613c3c575f5ffd5b820160808185031215613c4d575f5ffd5b613c55612e68565b8151815260208083015190820152604082015167ffffffffffffffff811115613c7c575f5ffd5b8201601f81018613613c8c575f5ffd5b8051613c9a612f0082612f35565b8082825260208201915060208360061b850101925088831115613cbb575f5ffd5b6020840193505b82841015613d0b576040848a031215613cd9575f5ffd5b613ce1612e21565b8451613cec81612daa565b8152602085810151818301529083526040909401939190910190613cc2565b6040850152505050606082015167ffffffffffffffff811115613d2c575f5ffd5b613d3886828501613bf8565b606083015250949350505050565b5f82601f830112613d55575f5ffd5b8151613d63612f0082612ebc565b818152846020838601011115613d77575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613da3575f5ffd5b815167ffffffffffffffff811115613db9575f5ffd5b820160608185031215613dca575f5ffd5b613dd2612df8565b8151613ddd81612daa565b8152602082015167ffffffffffffffff811115613df8575f5ffd5b613e0486828501613d46565b602083015250604082015167ffffffffffffffff811115613e23575f5ffd5b80830192505084601f830112613e37575f5ffd5b8151613e45612f0082612f35565b8082825260208201915060208360051b860101925087831115613e66575f5ffd5b6020850194505b82851015613e88578451825260209485019490910190613e6d565b6040840152509095945050505050565b805182525f602082015160606020850152613eb66060850182613562565b9050604083015184820360408601528181518084526020840191506020830193505f92505b808310156130cc5783518252602082019150602084019350600183019250613edb565b604081525f613f106040830185613a45565b8281036020840152612bda8185613e98565b602081525f6103a86020830184613590565b5f60208284031215613f44575f5ffd5b815167ffffffffffffffff811115613f5a575f5ffd5b820160408185031215613f6b575f5ffd5b613f73612e21565b81518152602082015167ffffffffffffffff811115613f90575f5ffd5b80830192505084601f830112613fa4575f5ffd5b613fb385835160208501613b68565b6020820152949350505050565b5f60208284031215613fd0575f5ffd5b815167ffffffffffffffff811115613fe6575f5ffd5b820160408185031215613ff7575f5ffd5b613fff612e21565b81518152602082015167ffffffffffffffff81111561401c575f5ffd5b80830192505084601f830112614030575f5ffd5b815161403e612f0082612f35565b8082825260208201915060208360051b86010192508783111561405f575f5ffd5b602085015b838110156140f957805167ffffffffffffffff811115614082575f5ffd5b86016060818b03601f19011215614097575f5ffd5b61409f612df8565b60208201516140ad81612daa565b8152604082015167ffffffffffffffff8111156140c8575f5ffd5b6140d78c602083860101613d46565b6020838101919091526060939093015160408301525084529283019201614064565b506020840152509095945050505050565b602081525f6103a860208301846139f7565b5f6020828403121561412c575f5ffd5b5051919050565b608081525f6141456080830187613a45565b82810360208401526141578187613e98565b9050828103604084015261416b8186613562565b9050828103606084015261417f8185613562565b979650505050505050565b6001600160a01b03851681526001600160a01b0384166020820152608060408201525f6141ba6080830185613562565b905082606083015295945050505050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220fae3247dd7a33c3cd300206de44d56570a43a7690ab25372a739d3c2704b371e64736f6c634300081c00336080604052348015600e575f5ffd5b506109a98061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80635e07a6e614610043578063f18d03cc1461006b578063ff883fcf14610080575b5f5ffd5b610056610051366004610636565b6100a0565b60405190151581526020015b60405180910390f35b61007e61007936600461064f565b6100ec565b005b61009361008e366004610703565b6101cb565b60405161006291906107d3565b5f816001600160a01b03163b5f036100b957505f919050565b6100c2826102b3565b156100cf57506001919050565b6100d882610364565b156100e557506001919050565b505f919050565b6100f5846102b3565b1561017d576040517fbeabacc80000000000000000000000000000000000000000000000000000000081526001600160a01b03848116600483015283811660248301526044820183905285169063beabacc8906064015f604051808303815f87803b158015610162575f5ffd5b505af1158015610174573d5f5f3e3d5ffd5b505050506101c5565b61018684610364565b1561019c57610197848484846103d3565b6101c5565b604051636b94637360e11b81526001600160a01b03851660048201526024015b60405180910390fd5b50505050565b60606101d6856102b3565b1561026d576040517f4ae000410000000000000000000000000000000000000000000000000000000081526001600160a01b03861690634ae0004190610224908790879087906004016107e5565b5f604051808303815f875af115801561023f573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526102669190810190610863565b90506102ab565b61027685610364565b1561028757610266858585856104a2565b604051636b94637360e11b81526001600160a01b03861660048201526024016101bc565b949350505050565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527ff44bac890000000000000000000000000000000000000000000000000000000060048201525f906001600160a01b038316906301ffc9a790602401602060405180830381865afa92505050801561034e575060408051601f3d908101601f1916820190925261034b918101906108a4565b60015b61035957505f919050565b92915050565b919050565b5f816001600160a01b031663e75235b86040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156103bf575060408051601f3d908101601f191682019092526103bc918101906108bd565b60015b6103ca57505f919050565b50600192915050565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b0384161461047f57604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261047a90859085905f6104a2565b61049b565b604080515f81526020810190915261049b9085908490846104a2565b5050505050565b60605f5f866001600160a01b0316635229073f8786885f6040518563ffffffff1660e01b81526004016104d894939291906108d4565b5f604051808303815f875af11580156104f3573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261051a9190810190610928565b9150915084515f146105365761053186838361054b565b610540565b61054082826105c3565b979650505050505050565b6060826105605761055b826105de565b6105bc565b815115801561057757506001600160a01b0384163b155b156105b9576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016101bc565b50805b9392505050565b6060826105d8576105d3826105de565b610359565b50919050565b8051156105ee5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461035f575f5ffd5b5f60208284031215610646575f5ffd5b6105bc82610620565b5f5f5f5f60808587031215610662575f5ffd5b61066b85610620565b935061067960208601610620565b925061068760408601610620565b9396929550929360600135925050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff811182821017156106d4576106d4610697565b604052919050565b5f67ffffffffffffffff8211156106f5576106f5610697565b50601f01601f191660200190565b5f5f5f5f60808587031215610716575f5ffd5b61071f85610620565b935061072d60208601610620565b9250604085013567ffffffffffffffff811115610748575f5ffd5b8501601f81018713610758575f5ffd5b803561076b610766826106dc565b6106ab565b81815288602083850101111561077f575f5ffd5b816020840160208301375f91810160200191909152949793965093946060013593505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6105bc60208301846107a5565b6001600160a01b0384168152606060208201525f61080660608301856107a5565b9050826040830152949350505050565b5f82601f830112610825575f5ffd5b8151610833610766826106dc565b818152846020838601011115610847575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215610873575f5ffd5b815167ffffffffffffffff811115610889575f5ffd5b6102ab84828501610816565b8051801515811461035f575f5ffd5b5f602082840312156108b4575f5ffd5b6105bc82610895565b5f602082840312156108cd575f5ffd5b5051919050565b6001600160a01b0385168152836020820152608060408201525f6108fb60808301856107a5565b90506002831061091957634e487b7160e01b5f52602160045260245ffd5b82606083015295945050505050565b5f5f60408385031215610939575f5ffd5b61094283610895565b9150602083015167ffffffffffffffff81111561095d575f5ffd5b61096985828601610816565b915050925092905056fea264697066735822122063c7f17d2676ae9340ccf0078bc40725749c3c4dd9198f82832bc411d2d1333764736f6c634300081c0033", + "deployedBytecode": "0x60806040526004361061010c575f3560e01c80638da5cb5b116100a1578063cbc9104511610071578063f2fde38b11610057578063f2fde38b14610314578063f77c479114610333578063fee2b7a714610366575f5ffd5b8063cbc91045146102d6578063f0801868146102f5575f5ffd5b80638da5cb5b14610239578063911929df14610255578063c422661f14610281578063c4bb6af5146102a0575f5ffd5b8063715018a6116100dc578063715018a6146101c057806377c8fb6b146101d45780637f411c79146101f357806384b0196e14610212575f5ffd5b80630fc27cf6146101175780632e5a2458146101385780634987ed2f1461016a5780636ccae054146101a1575f5ffd5b3661011357005b5f5ffd5b348015610122575f5ffd5b50610136610131366004612dc9565b610385565b005b348015610143575f5ffd5b50610157610152366004613366565b610399565b6040519081526020015b60405180910390f35b348015610175575f5ffd5b50600454610189906001600160a01b031681565b6040516001600160a01b039091168152602001610161565b3480156101ac575f5ffd5b506101366101bb3660046133de565b6103af565b3480156101cb575f5ffd5b50610136610465565b3480156101df575f5ffd5b50600554610189906001600160a01b031681565b3480156101fe575f5ffd5b5061013661020d36600461341c565b610478565b34801561021d575f5ffd5b50610226610587565b60405161016197969594939291906135ca565b348015610244575f5ffd5b505f546001600160a01b0316610189565b348015610260575f5ffd5b5061027461026f366004612dc9565b6105e5565b6040516101619190613653565b34801561028c575f5ffd5b5061013661029b366004612dc9565b61068e565b3480156102ab575f5ffd5b506101576102ba366004613665565b600660209081525f928352604080842090915290825290205481565b3480156102e1575f5ffd5b506101366102f036600461368f565b61069f565b348015610300575f5ffd5b5061013661030f36600461341c565b6106a9565b34801561031f575f5ffd5b5061013661032e366004612dc9565b61076e565b34801561033e575f5ffd5b506101897f000000000000000000000000000000000000000000000000000000000000000081565b348015610371575f5ffd5b506101576103803660046136c9565b6107c1565b61038d6107d1565b61039681610816565b50565b5f6103a58484846108ac565b90505b9392505050565b6103b76107d1565b6103bf610948565b6001600160a01b0382166103ff576040517f54328a0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61040a83838361098b565b816001600160a01b0316836001600160a01b03167fed2837b80b3489773c5f1ed30dd2884b4c90dbf7b87428ea03c49b24ef59a8058360405161044f91815260200190565b60405180910390a361046060018055565b505050565b61046d6107d1565b6104765f6109c4565b565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa1580156104e3573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050791906136fb565b6105345760405163a7ac57a960e01b81526001600160a01b03821660048201526024015b60405180910390fd5b5f5a9050610543836001610a20565b5f5a61054f908361371a565b9050806040517fa523ba4900000000000000000000000000000000000000000000000000000000815260040161052b91815260200190565b5f6060805f5f5f6060610598610b9d565b6105a0610bcf565b604080515f808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009b939a50919850469750309650945092509050565b6001600160a01b0381165f90815260076020526040902080546060919061060b90613739565b80601f016020809104026020016040519081016040528092919081815260200182805461063790613739565b80156106825780601f1061065957610100808354040283529160200191610682565b820191905f5260205f20905b81548152906001019060200180831161066557829003601f168201915b50505050509050919050565b6106966107d1565b61039681610bfc565b6103963382610c52565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa158015610714573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061073891906136fb565b6107605760405163a7ac57a960e01b81526001600160a01b038216600482015260240161052b565b61076a825f610a20565b5050565b6107766107d1565b6001600160a01b0381166107b8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f600482015260240161052b565b610396816109c4565b5f6107cb82610ccb565b92915050565b5f546001600160a01b03163314610476576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052b565b6001600160a01b038116610856576040517f42af048a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f93fa00498c991ad852fbb3361afb0e3507720aa9ff69a8386009b76134a6e654905f90a250565b5f7fabd98b1a5498f7ce6ce5fd671f1a1c2ad437b155c4c12281f925612823c72e886108d784610ccb565b83865f01518760200151805190602001206108f58960400151610d6e565b6040805160208101979097528601949094526001600160a01b039092166060850152608084015260a083015260c082015260e0016040516020818303038152906040528051906020012090509392505050565b600260015403610984576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036109b9576104608282610d80565b610460838383610e2b565b5f80546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610a28610948565b5f5b8251811015610b93575f838281518110610a4657610a4661376b565b60200260200101515f015190505f848381518110610a6657610a6661376b565b60200260200101516020015190505f858481518110610a8757610a8761376b565b6020026020010151604001519050610aa183838388610e9f565b6020838101516001600160a01b03165f9081526006825260408082206060870151835290925220439055825160ff16610ae357610ade838361164c565b610b4a565b825160ff165f1901610af957610ade83836119d0565b825160ff1660011901610b1057610ade8383611a90565b82516040517f7cdbcbf100000000000000000000000000000000000000000000000000000000815260ff909116600482015260240161052b565b610b558284336108ac565b6040518581527f120985525cfb78f6c03f96986f9687f49caf81e29350d21fa052e8c0df211ebb9060200160405180910390a2505050600101610a2a565b5061076a60018055565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006002611bb6565b905090565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006003611bb6565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f493e783f4ed464086f434b9d42b1b8fb97375d1fdc91692c73551281526d22be905f90a250565b6001600160a01b0382165f908152600760205260408120610c7291612d60565b6001600160a01b0382165f908152600760205260409020610c9382826137d7565b506040516001600160a01b038316907f5ac98b81e10e07689d7de8e9ec09900564d65647de8d039328d4cbdf575b28f4905f90a25050565b5f7fbd51147cb801db5aa8dc80920aabcbe45ea95c1767e31bc3339f62d8f0d046f9825f015183602001518460400151856060015186608001518760a0015180519060200120610d1e8960c00151611c60565b610d2b8a60e00151611da6565b8a61010001518b6101200151604051602001610d519b9a99989796959493929190613892565b604051602081830303815290604052805190602001209050919050565b5f81604051602001610d51919061390c565b80471015610dc3576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810182905260440161052b565b5f5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610e0d576040519150601f19603f3d011682016040523d82523d5f602084013e610e12565b606091505b509150915081610e2557610e2581611eba565b50505050565b6040516001600160a01b0383811660248301526044820183905261046091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611efc565b60408401516001600160a01b03163014610ef65760408085015190517f30b078bf0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6060840151610f31576040517f58e8d18100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808501516001600160a01b03165f90815260068252604080822060608801518352909252205415610fab57602084015160608501516040517f9756f94d0000000000000000000000000000000000000000000000000000000081526001600160a01b039092166004830152602482015260440161052b565b6005546001600160a01b0316156110e4576020808501516001600160a01b03165f9081526007909152604081208054610fe390613739565b80601f016020809104026020016040519081016040528092919081815260200182805461100f90613739565b801561105a5780601f106110315761010080835404028352916020019161105a565b820191905f5260205f20905b81548152906001019060200180831161103d57829003601f168201915b505050505090505f815111156110e2576005546040517fdbd902180000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063dbd90218906110b59088908590600401613b29565b5f6040518083038186803b1580156110cb575f5ffd5b505afa1580156110dd573d5f5f3e3d5ffd5b505050505b505b5f6110ee85611f81565b9050801561118d57428560800151116111425760808501516040517f95d3dc22000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b8351421080159061118b5784516040517f2c39717b000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b505b8360400151518560c0015151146111d0576040517f9dca144800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8560c0015151811015611273575f8660c0015182815181106111f6576111f661376b565b60200260200101516020015190505f8660400151838151811061121b5761121b61376b565b6020026020010151905081811115611269576040517f8ffa61fa000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161052b565b50506001016111d2565b505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663680038c76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112d1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190613b4d565b90505f8160ff1687610120015111611310578160ff16611317565b8661012001515b90508087610140015151101561136b57610140870151516040517fcd0ce69900000000000000000000000000000000000000000000000000000000815261052b918391600401918252602082015260400190565b5f5f90505f60405180602001604052806113848b610ccb565b905290505f61139a61139583611fd0565b61200d565b90505f5b8a61014001515181101561152f575f6113d5838d610140015184815181106113c8576113c861376b565b6020026020010151612054565b9050846001600160a01b0316816001600160a01b031611611435576040517fce98854c0000000000000000000000000000000000000000000000000000000081526001600160a01b0380871660048301528216602482015260440161052b565b6040517f616556700000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015291955085915f917f000000000000000000000000000000000000000000000000000000000000000090911690636165567090602401602060405180830381865afa1580156114bb573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114df91906136fb565b1590508015611525576040517ff24706660000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505060010161139e565b505f6115486115426113958c8e336108ac565b8a612054565b6040517f163eed9e0000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301529192505f917f0000000000000000000000000000000000000000000000000000000000000000169063163eed9e90602401602060405180830381865afa1580156115ca573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ee91906136fb565b1580156115f9575088155b9050801561163e576040517f100158a80000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505050505050505050505050565b5f8260a001518060200190518101906116659190613c16565b90505f82602001518060200190518101906116809190613d93565b905061168c828261207c565b60208401516004545f916116a9916001600160a01b0316906122ed565b905046835f01510361170b575f5b836040015151811015611709575f846040015182815181106116db576116db61376b565b60200260200101519050611700815f01518860200151865f0151846020015187612371565b506001016116b7565b505b5f611715846123b1565b83516040517f7b4da5de0000000000000000000000000000000000000000000000000000000081529192506001600160a01b031690637b4da5de906117609089908990600401613efe565b5f604051808303815f87803b158015611777575f5ffd5b505af1158015611789573d5f5f3e3d5ffd5b50505050468460200151036119c8575f84606001515167ffffffffffffffff8111156117b7576117b7612de4565b6040519080825280602002602001820160405280156117e0578160200160208202803683370190505b5090505f5b856060015151811015611990575f866060015182815181106118095761180961376b565b602002602001015190505f611821825f01513061246d565b90505f8584815181106118365761183661376b565b602002602001015190508082101561188b576040517ff5ad5ab700000000000000000000000000000000000000000000000000000000815260048101859052602481018390526044810182905260640161052b565b611895818361371a565b8585815181106118a7576118a761376b565b6020026020010181815250505f886040015185815181106118ca576118ca61376b565b60200260200101519050808686815181106118e7576118e761376b565b6020026020010151101561195557848686815181106119085761190861376b565b60209081029190910101516040517ff0a43aa7000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044810182905260640161052b565b611980845f015185604001518888815181106119735761197361376b565b602002602001015161098b565b5050600190920191506117e59050565b506119bb8787836040516020016119a79190613f22565b604051602081830303815290604052612524565b6119c68787856125b7565b505b505050505050565b5f8260a001518060200190518101906119e99190613f34565b90506119f5818361263b565b60208301516004545f91611a12916001600160a01b0316906122ed565b90505f5b826020015151811015611a69575f83602001518281518110611a3a57611a3a61376b565b60200260200101519050611a60815f015187602001518360400151846020015187612371565b50600101611a16565b50604080515f815260208101909152611a859085908590612524565b610e258484836125b7565b5f8260a00151806020019051810190611aa99190613fc0565b9050611aba818385602001516126fc565b5f81602001515167ffffffffffffffff811115611ad957611ad9612de4565b604051908082528060200260200182016040528015611b0c57816020015b6060815260200190600190039081611af75790505b5090505f5b826020015151811015611b93575f83602001518281518110611b3557611b3561376b565b602090810291909101810151878201518151928201516040830151600454939550611b6d946001600160a01b0390941693919061279a565b838381518110611b7f57611b7f61376b565b602090810291909101015250600101611b11565b50611baa8484836040516020016119a7919061410a565b610e25848460016125b7565b606060ff8314611bd057611bc98361282d565b90506107cb565b818054611bdc90613739565b80601f0160208091040260200160405190810160405280929190818152602001828054611c0890613739565b8015611c535780601f10611c2a57610100808354040283529160200191611c53565b820191905f5260205f20905b815481529060010190602001808311611c3657829003601f168201915b5050505050905092915050565b5f5f825167ffffffffffffffff811115611c7c57611c7c612de4565b604051908082528060200260200182016040528015611ca5578160200160208202803683370190505b5090505f5b8351811015611d76577fe44101eb81f50b64d4005e31312ea7bafd6f81357f9dce4fe51d5f2dcb939da4848281518110611ce657611ce661376b565b60200260200101515f0151858381518110611d0357611d0361376b565b602002602001015160200151604051602001611d3b939291909283526001600160a01b03919091166020830152604082015260600190565b60405160208183030381529060405280519060200120828281518110611d6357611d6361376b565b6020908102919091010152600101611caa565b5080604051602001611d88919061390c565b60405160208183030381529060405280519060200120915050919050565b5f5f825167ffffffffffffffff811115611dc257611dc2612de4565b604051908082528060200260200182016040528015611deb578160200160208202803683370190505b5090505f5b8351811015611d76577ff7a396f96f3810db1cfde06de38d32fc29d56b63cf9d9aee2c10c91ef7273bb1848281518110611e2c57611e2c61376b565b60200260200101515f0151858381518110611e4957611e4961376b565b60200260200101516020015180519060200120604051602001611e7f939291909283526020830191909152604082015260600190565b60405160208183030381529060405280519060200120828281518110611ea757611ea761376b565b6020908102919091010152600101611df0565b805115611eca5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f60205f8451602086015f885af180611f1b576040513d5f823e3d81fd5b50505f513d91508115611f32578060011415611f3f565b6001600160a01b0384163b155b15610e25576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b80515f9060ff1615611f9557506001919050565b5f8260a00151806020019051810190611fae9190613c16565b90508060200151815f015103611fc75750600192915050565b51461492915050565b8051604080517f07094367b2db06e3dbb414cf947644f8e08067ea238c5f41cb2fa5fea5be628e6020820152908101919091525f90606001610d51565b5f6107cb61201961286a565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b5f5f5f5f6120628686612993565b92509250925061207282826129dc565b5090949350505050565b81515f904614801590612093575046836020015114155b905080156120b65760405163308a43dd60e11b815246600482015260240161052b565b826060015151826040015151146120f9576040517f1816c20200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8360600151518110156121f2575f8460600151828151811061211f5761211f61376b565b602002602001015190505f81604001519050306001600160a01b0316816001600160a01b03160361216e57604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b5f826020015190505f8660400151858151811061218d5761218d61376b565b60200260200101519050818110156121e2576040517f1fef5f3300000000000000000000000000000000000000000000000000000000815260048101869052602481018290526044810183905260640161052b565b5050600190920191506120fb9050565b5060208301518351146104605781516040517f7d7011020000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201525f917f00000000000000000000000000000000000000000000000000000000000000001690637d70110290602401602060405180830381865afa158015612280573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122a491906136fb565b1590508015610e255782516040517fdb489af40000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6040517f5e07a6e60000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301525f9190841690635e07a6e690602401602060405180830381865afa15801561234d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a891906136fb565b801561239557600454612390906001600160a01b031685878686612adf565b6123aa565b6123aa6001600160a01b038616858585612b38565b5050505050565b606081606001515167ffffffffffffffff8111156123d1576123d1612de4565b6040519080825280602002602001820160405280156123fa578160200160208202803683370190505b50905046826020015103612468575f5b82606001515181101561246657612441836060015182815181106124305761243061376b565b60200260200101515f01513061246d565b8282815181106124535761245361376b565b602090810291909101015260010161240a565b505b919050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036124a357506001600160a01b038116316107cb565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015612500573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc9919061411c565b5f5b8360e0015151811015610e25575f8460e00151828151811061254a5761254a61376b565b60200260200101519050845f015160ff16815f015186602001516001600160a01b03167fee2c98c99b683f71058b8744fea294a411482f07d55c98b392917e9286f22f1388888887602001516040516125a69493929190614133565b60405180910390a450600101612526565b6020830151335f5b8560c00151518110156119c8575f8660c0015182815181106125e3576125e361376b565b60200260200101515f01519050612604816001600160a01b03166103481490565b61263257612632818585896040015186815181106126245761262461376b565b602002602001015189612371565b506001016125bf565b8151461461265e5760405163308a43dd60e11b815246600482015260240161052b565b60208101515115612682576040516393c58e1160e01b815260040160405180910390fd5b5f5b826020015151811015610460575f836020015182815181106126a8576126a861376b565b6020026020010151604001519050306001600160a01b0316816001600160a01b0316036126f357604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b50600101612684565b8251461461271f5760405163308a43dd60e11b815246600482015260240161052b565b60208201515115612743576040516393c58e1160e01b815260040160405180910390fd5b600454612759906001600160a01b0316826122ed565b610460576040517fcaec9bac0000000000000000000000000000000000000000000000000000000081526001600160a01b038216600482015260240161052b565b60606128238663ff883fcf60e01b878787876040516024016127bf949392919061418a565b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612b71565b9695505050505050565b60605f61283983612be3565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156128c257507f000000000000000000000000000000000000000000000000000000000000000046145b156128ec57507f000000000000000000000000000000000000000000000000000000000000000090565b610bca604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f5f83516041036129ca576020840151604085015160608601515f1a6129bc88828585612c23565b9550955095505050506129d5565b505081515f91506002905b9250925092565b5f8260038111156129ef576129ef61377f565b036129f8575050565b6001826003811115612a0c57612a0c61377f565b03612a43576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826003811115612a5757612a5761377f565b03612a91576040517ffce698f70000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6003826003811115612aa557612aa561377f565b0361076a576040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6040516001600160a01b038086166024830152808516604483015283166064820152608481018290526119c89086907ff18d03cc000000000000000000000000000000000000000000000000000000009060a4016127bf565b6040516001600160a01b038481166024830152838116604483015260648201839052610e259186918216906323b872dd90608401610e58565b60605f5f846001600160a01b031684604051612b8d91906141cb565b5f60405180830381855af49150503d805f8114612bc5576040519150601f19603f3d011682016040523d82523d5f602084013e612bca565b606091505b5091509150612bda858383612ceb565b95945050505050565b5f60ff8216601f8111156107cb576040517fb3512b0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115612c5c57505f91506003905082612ce1565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015612cad573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116612cd857505f925060019150829050612ce1565b92505f91508190505b9450945094915050565b606082612d0057612cfb82611eba565b6103a8565b8151158015612d1757506001600160a01b0384163b155b15612d59576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b50806103a8565b508054612d6c90613739565b5f825580601f10612d7b575050565b601f0160209004905f5260205f209081019061039691905b80821115612da6575f8155600101612d93565b5090565b6001600160a01b0381168114610396575f5ffd5b803561246881612daa565b5f60208284031215612dd9575f5ffd5b81356103a881612daa565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b60405290565b6040805190810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051610160810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b6040516080810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612eb457612eb4612de4565b604052919050565b5f67ffffffffffffffff821115612ed557612ed5612de4565b50601f01601f191660200190565b5f82601f830112612ef2575f5ffd5b8135612f05612f0082612ebc565b612e8b565b818152846020838601011115612f19575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f67ffffffffffffffff821115612f4e57612f4e612de4565b5060051b60200190565b5f60608284031215612f68575f5ffd5b612f70612df8565b823581529050602082013567ffffffffffffffff811115612f8f575f5ffd5b612f9b84828501612ee3565b602083015250604082013567ffffffffffffffff811115612fba575f5ffd5b8201601f81018413612fca575f5ffd5b8035612fd8612f0082612f35565b8082825260208201915060208360051b850101925086831115612ff9575f5ffd5b6020840193505b8284101561301b578335825260209384019390910190613000565b60408501525091949350505050565b60ff81168114610396575f5ffd5b80356124688161302a565b5f82601f830112613052575f5ffd5b8135613060612f0082612f35565b8082825260208201915060208360061b860101925085831115613081575f5ffd5b602085015b838110156130cc576040818803121561309d575f5ffd5b6130a5612e21565b81356130b081612daa565b8152602082810135818301529084529290920191604001613086565b5095945050505050565b5f82601f8301126130e5575f5ffd5b81356130f3612f0082612f35565b8082825260208201915060208360051b860101925085831115613114575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613137575f5ffd5b86016040818903601f1901121561314c575f5ffd5b613154612e21565b60208201358152604082013567ffffffffffffffff811115613174575f5ffd5b6131838a602083860101612ee3565b6020830152508085525050602083019250602081019050613119565b5f82601f8301126131ae575f5ffd5b81356131bc612f0082612f35565b8082825260208201915060208360051b8601019250858311156131dd575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613200575f5ffd5b61320f886020838a0101612ee3565b845250602092830192016131e2565b5f610160828403121561322f575f5ffd5b613237612e44565b905061324282613038565b815261325060208301612dbe565b602082015261326160408301612dbe565b6040820152606082810135908201526080808301359082015260a082013567ffffffffffffffff811115613293575f5ffd5b61329f84828501612ee3565b60a08301525060c082013567ffffffffffffffff8111156132be575f5ffd5b6132ca84828501613043565b60c08301525060e082013567ffffffffffffffff8111156132e9575f5ffd5b6132f5848285016130d6565b60e08301525061010082013567ffffffffffffffff811115613315575f5ffd5b61332184828501612ee3565b61010083015250610120828101359082015261014082013567ffffffffffffffff81111561334d575f5ffd5b6133598482850161319f565b6101408301525092915050565b5f5f5f60608486031215613378575f5ffd5b833567ffffffffffffffff81111561338e575f5ffd5b61339a86828701612f58565b935050602084013567ffffffffffffffff8111156133b6575f5ffd5b6133c28682870161321e565b92505060408401356133d381612daa565b809150509250925092565b5f5f5f606084860312156133f0575f5ffd5b83356133fb81612daa565b9250602084013561340b81612daa565b929592945050506040919091013590565b5f6020828403121561342c575f5ffd5b813567ffffffffffffffff811115613442575f5ffd5b8201601f81018413613452575f5ffd5b8035613460612f0082612f35565b8082825260208201915060208360051b850101925086831115613481575f5ffd5b602084015b8381101561355757803567ffffffffffffffff8111156134a4575f5ffd5b85016060818a03601f190112156134b9575f5ffd5b6134c1612df8565b602082013567ffffffffffffffff8111156134da575f5ffd5b6134e98b60208386010161321e565b825250604082013567ffffffffffffffff811115613505575f5ffd5b6135148b602083860101612f58565b602083015250606082013567ffffffffffffffff811115613533575f5ffd5b6135428b602083860101612ee3565b60408301525084525060209283019201613486565b509695505050505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f8151808452602084019350602083015f5b828110156135c05781518652602095860195909101906001016135a2565b5093949350505050565b7fff000000000000000000000000000000000000000000000000000000000000008816815260e060208201525f61360460e0830189613562565b82810360408401526136168189613562565b90508660608401526001600160a01b03861660808401528460a084015282810360c08401526136458185613590565b9a9950505050505050505050565b602081525f6103a86020830184613562565b5f5f60408385031215613676575f5ffd5b823561368181612daa565b946020939093013593505050565b5f6020828403121561369f575f5ffd5b813567ffffffffffffffff8111156136b5575f5ffd5b6136c184828501612ee3565b949350505050565b5f602082840312156136d9575f5ffd5b813567ffffffffffffffff8111156136ef575f5ffd5b6136c18482850161321e565b5f6020828403121561370b575f5ffd5b815180151581146103a8575f5ffd5b818103818111156107cb57634e487b7160e01b5f52601160045260245ffd5b600181811c9082168061374d57607f821691505b60208210810361246657634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b601f82111561046057805f5260205f20601f840160051c810160208510156137b85750805b601f840160051c820191505b818110156123aa575f81556001016137c4565b815167ffffffffffffffff8111156137f1576137f1612de4565b613805816137ff8454613739565b84613793565b6020601f821160018114613837575f83156138205750848201515b5f19600385901b1c1916600184901b1784556123aa565b5f84815260208120601f198516915b828110156138665787850151825560209485019460019092019101613846565b508482101561388357868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8b815260ff8b1660208201526001600160a01b038a1660408201526001600160a01b03891660608201528760808201528660a08201528560c08201528460e0820152836101008201526101606101208201525f6138f3610160830185613562565b9050826101408301529c9b505050505050505050505050565b81515f90829060208501835b82811015613936578151845260209384019390910190600101613918565b509195945050505050565b5f8151808452602084019350602083015f5b828110156135c057815180516001600160a01b031687526020908101518188015260409096019590910190600101613953565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f1985840301885281518051845260208101519050604060208501526139d46040850182613562565b6020998a01999094509290920191506001016139a2565b50909695505050505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f19858403018852613a2f838351613562565b6020988901989093509190910190600101613a13565b805160ff1682525f6020820151613a6760208501826001600160a01b03169052565b506040820151613a8260408501826001600160a01b03169052565b50606082015160608401526080820151608084015260a082015161016060a0850152613ab2610160850182613562565b905060c083015184820360c0860152613acb8282613941565b91505060e083015184820360e0860152613ae58282613986565b915050610100830151848203610100860152613b018282613562565b915050610120830151610120850152610140830151848203610140860152612bda82826139f7565b604081525f613b3b6040830185613a45565b8281036020840152612bda8185613562565b5f60208284031215613b5d575f5ffd5b81516103a88161302a565b5f613b75612f0084612f35565b83815290506020810160608402830185811115613b90575f5ffd5b835b81811015613bee575f60608289031215613baa575f5ffd5b613bb2612df8565b90508151613bbf81612daa565b8152602082810151908201526040820151613bd981612daa565b60408201528352602090920191606001613b92565b5050509392505050565b5f82601f830112613c07575f5ffd5b6103a883835160208501613b68565b5f60208284031215613c26575f5ffd5b815167ffffffffffffffff811115613c3c575f5ffd5b820160808185031215613c4d575f5ffd5b613c55612e68565b8151815260208083015190820152604082015167ffffffffffffffff811115613c7c575f5ffd5b8201601f81018613613c8c575f5ffd5b8051613c9a612f0082612f35565b8082825260208201915060208360061b850101925088831115613cbb575f5ffd5b6020840193505b82841015613d0b576040848a031215613cd9575f5ffd5b613ce1612e21565b8451613cec81612daa565b8152602085810151818301529083526040909401939190910190613cc2565b6040850152505050606082015167ffffffffffffffff811115613d2c575f5ffd5b613d3886828501613bf8565b606083015250949350505050565b5f82601f830112613d55575f5ffd5b8151613d63612f0082612ebc565b818152846020838601011115613d77575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613da3575f5ffd5b815167ffffffffffffffff811115613db9575f5ffd5b820160608185031215613dca575f5ffd5b613dd2612df8565b8151613ddd81612daa565b8152602082015167ffffffffffffffff811115613df8575f5ffd5b613e0486828501613d46565b602083015250604082015167ffffffffffffffff811115613e23575f5ffd5b80830192505084601f830112613e37575f5ffd5b8151613e45612f0082612f35565b8082825260208201915060208360051b860101925087831115613e66575f5ffd5b6020850194505b82851015613e88578451825260209485019490910190613e6d565b6040840152509095945050505050565b805182525f602082015160606020850152613eb66060850182613562565b9050604083015184820360408601528181518084526020840191506020830193505f92505b808310156130cc5783518252602082019150602084019350600183019250613edb565b604081525f613f106040830185613a45565b8281036020840152612bda8185613e98565b602081525f6103a86020830184613590565b5f60208284031215613f44575f5ffd5b815167ffffffffffffffff811115613f5a575f5ffd5b820160408185031215613f6b575f5ffd5b613f73612e21565b81518152602082015167ffffffffffffffff811115613f90575f5ffd5b80830192505084601f830112613fa4575f5ffd5b613fb385835160208501613b68565b6020820152949350505050565b5f60208284031215613fd0575f5ffd5b815167ffffffffffffffff811115613fe6575f5ffd5b820160408185031215613ff7575f5ffd5b613fff612e21565b81518152602082015167ffffffffffffffff81111561401c575f5ffd5b80830192505084601f830112614030575f5ffd5b815161403e612f0082612f35565b8082825260208201915060208360051b86010192508783111561405f575f5ffd5b602085015b838110156140f957805167ffffffffffffffff811115614082575f5ffd5b86016060818b03601f19011215614097575f5ffd5b61409f612df8565b60208201516140ad81612daa565b8152604082015167ffffffffffffffff8111156140c8575f5ffd5b6140d78c602083860101613d46565b6020838101919091526060939093015160408301525084529283019201614064565b506020840152509095945050505050565b602081525f6103a860208301846139f7565b5f6020828403121561412c575f5ffd5b5051919050565b608081525f6141456080830187613a45565b82810360208401526141578187613e98565b9050828103604084015261416b8186613562565b9050828103606084015261417f8185613562565b979650505050505050565b6001600160a01b03851681526001600160a01b0384166020820152608060408201525f6141ba6080830185613562565b905082606083015295945050505050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220fae3247dd7a33c3cd300206de44d56570a43a7690ab25372a739d3c2704b371e64736f6c634300081c0033", + "linkReferences": {}, + "deployedLinkReferences": {}, + "immutableReferences": { + "4041": [ + { + "length": 32, + "start": 10442 + } + ], + "4043": [ + { + "length": 32, + "start": 10400 + } + ], + "4045": [ + { + "length": 32, + "start": 10358 + } + ], + "4047": [ + { + "length": 32, + "start": 10523 + } + ], + "4049": [ + { + "length": 32, + "start": 10563 + } + ], + "4052": [ + { + "length": 32, + "start": 2980 + } + ], + "4055": [ + { + "length": 32, + "start": 3030 + } + ], + "8966": [ + { + "length": 32, + "start": 836 + }, + { + "length": 32, + "start": 1180 + }, + { + "length": 32, + "start": 1741 + }, + { + "length": 32, + "start": 4727 + }, + { + "length": 32, + "start": 5236 + }, + { + "length": 32, + "start": 5509 + }, + { + "length": 32, + "start": 8763 + } + ] + }, + "inputSourceName": "project/contracts/Settler.sol", + "buildInfoId": "27822028aefba6db9743fbac66c2677f7feca3ad" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json b/packages/evm/ignition/deployments/chain-1/build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json new file mode 100644 index 0000000..df0f28b --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json @@ -0,0 +1,266 @@ +{ + "_format": "hh3-sol-build-info-1", + "id": "27822028aefba6db9743fbac66c2677f7feca3ad", + "solcVersion": "0.8.28", + "solcLongVersion": "0.8.28+commit.7893614a", + "userSourceNameMap": { + "contracts/Controller.sol": "project/contracts/Controller.sol", + "contracts/Intents.sol": "project/contracts/Intents.sol", + "contracts/Settler.sol": "project/contracts/Settler.sol", + "contracts/interfaces/IController.sol": "project/contracts/interfaces/IController.sol", + "contracts/interfaces/ICreateX.sol": "project/contracts/interfaces/ICreateX.sol", + "contracts/interfaces/IExecutor.sol": "project/contracts/interfaces/IExecutor.sol", + "contracts/interfaces/IIntentsValidator.sol": "project/contracts/interfaces/IIntentsValidator.sol", + "contracts/interfaces/ISafe.sol": "project/contracts/interfaces/ISafe.sol", + "contracts/interfaces/ISettler.sol": "project/contracts/interfaces/ISettler.sol", + "contracts/interfaces/ISmartAccount.sol": "project/contracts/interfaces/ISmartAccount.sol", + "contracts/interfaces/ISmartAccountsHandler.sol": "project/contracts/interfaces/ISmartAccountsHandler.sol", + "contracts/safeguards/BaseIntentsValidator.sol": "project/contracts/safeguards/BaseIntentsValidator.sol", + "contracts/safeguards/CallIntentsValidator.sol": "project/contracts/safeguards/CallIntentsValidator.sol", + "contracts/safeguards/IntentsValidator.sol": "project/contracts/safeguards/IntentsValidator.sol", + "contracts/safeguards/Safeguards.sol": "project/contracts/safeguards/Safeguards.sol", + "contracts/safeguards/SwapIntentsValidator.sol": "project/contracts/safeguards/SwapIntentsValidator.sol", + "contracts/safeguards/TransferIntentsValidator.sol": "project/contracts/safeguards/TransferIntentsValidator.sol", + "contracts/smart-accounts/SmartAccount.sol": "project/contracts/smart-accounts/SmartAccount.sol", + "contracts/smart-accounts/SmartAccountsHandler.sol": "project/contracts/smart-accounts/SmartAccountsHandler.sol", + "contracts/smart-accounts/SmartAccountsHandlerHelpers.sol": "project/contracts/smart-accounts/SmartAccountsHandlerHelpers.sol", + "contracts/test/CallMock.sol": "project/contracts/test/CallMock.sol", + "contracts/test/TokenMock.sol": "project/contracts/test/TokenMock.sol", + "contracts/test/executors/EmptyExecutorMock.sol": "project/contracts/test/executors/EmptyExecutorMock.sol", + "contracts/test/executors/MintExecutorMock.sol": "project/contracts/test/executors/MintExecutorMock.sol", + "contracts/test/executors/ReentrantExecutorMock.sol": "project/contracts/test/executors/ReentrantExecutorMock.sol", + "contracts/test/executors/TransferExecutorMock.sol": "project/contracts/test/executors/TransferExecutorMock.sol", + "contracts/test/smart-accounts/SafeMock.sol": "project/contracts/test/smart-accounts/SafeMock.sol", + "contracts/test/utils/DenominationsMock.sol": "project/contracts/test/utils/DenominationsMock.sol", + "contracts/test/utils/ERC20HelpersMock.sol": "project/contracts/test/utils/ERC20HelpersMock.sol", + "contracts/utils/Denominations.sol": "project/contracts/utils/Denominations.sol", + "contracts/utils/ERC20Helpers.sol": "project/contracts/utils/ERC20Helpers.sol" + }, + "input": { + "language": "Solidity", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000 + }, + "evmVersion": "cancun", + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "remappings": [ + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", + "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/" + ] + }, + "sources": { + "npm/@openzeppelin/contracts@5.3.0/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/interfaces/draft-IERC6093.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol)\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard ERC-20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\n */\ninterface IERC20Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC20InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC20InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC20InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC-721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\n */\ninterface IERC721Errors {\n /**\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.\n * Used in balance queries.\n * @param owner Address of the current owner of a token.\n */\n error ERC721InvalidOwner(address owner);\n\n /**\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\n * @param tokenId Identifier number of a token.\n */\n error ERC721NonexistentToken(uint256 tokenId);\n\n /**\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param tokenId Identifier number of a token.\n * @param owner Address of the current owner of a token.\n */\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC721InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC721InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param tokenId Identifier number of a token.\n */\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC721InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC-1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\n */\ninterface IERC1155Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n * @param tokenId Identifier number of a token.\n */\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC1155InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC1155InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param owner Address of the current owner of a token.\n */\n error ERC1155MissingApprovalForAll(address operator, address owner);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC1155InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC1155InvalidOperator(address operator);\n\n /**\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n * Used in batch transfers.\n * @param idsLength Length of the array of token identifiers\n * @param valuesLength Length of the array of token amounts\n */\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC1271.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC1271.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-1271 standard signature validation method for\n * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].\n */\ninterface IERC1271 {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param hash Hash of the data to be signed\n * @param signature Signature byte array associated with `hash`\n */\n function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC1363.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @title IERC1363\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\n *\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\n */\ninterface IERC1363 is IERC20, IERC165 {\n /*\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\n * 0xb0202a11 ===\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\n */\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @param data Additional data with no specified format, sent in call to `spender`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n" + }, + "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../token/ERC20/IERC20.sol\";\n" + }, + "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC5267.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)\n\npragma solidity ^0.8.20;\n\ninterface IERC5267 {\n /**\n * @dev MAY be emitted to signal that the domain could have changed.\n */\n event EIP712DomainChanged();\n\n /**\n * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712\n * signature.\n */\n function eip712Domain()\n external\n view\n returns (\n bytes1 fields,\n string memory name,\n string memory version,\n uint256 chainId,\n address verifyingContract,\n bytes32 salt,\n uint256[] memory extensions\n );\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n mapping(address account => uint256) private _balances;\n\n mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * Both values are immutable: they can only be set once during construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Skips emitting an {Approval} event indicating an allowance update. This is not\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n _totalSupply += value;\n } else {\n uint256 fromBalance = _balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n _balances[from] = fromBalance - value;\n }\n }\n\n if (to == address(0)) {\n unchecked {\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n _totalSupply -= value;\n }\n } else {\n unchecked {\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n _balances[to] += value;\n }\n }\n\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n * Relies on the `_update` mechanism\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _mint(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(address(0), account, value);\n }\n\n /**\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n * Relies on the `_update` mechanism.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead\n */\n function _burn(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n _update(account, address(0), value);\n }\n\n /**\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n *\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n _approve(owner, spender, value, true);\n }\n\n /**\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n *\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any\n * `Approval` event during `transferFrom` operations.\n *\n * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to\n * true using the following override:\n *\n * ```solidity\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n * super._approve(owner, spender, value, true);\n * }\n * ```\n *\n * Requirements are the same as {_approve}.\n */\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n if (owner == address(0)) {\n revert ERC20InvalidApprover(address(0));\n }\n if (spender == address(0)) {\n revert ERC20InvalidSpender(address(0));\n }\n _allowances[owner][spender] = value;\n if (emitEvent) {\n emit Approval(owner, spender, value);\n }\n }\n\n /**\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\n *\n * Does not update the allowance value in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Does not emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance < type(uint256).max) {\n if (currentAllowance < value) {\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n }\n unchecked {\n _approve(owner, spender, currentAllowance - value, false);\n }\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC1363} from \"../../../interfaces/IERC1363.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n /**\n * @dev An operation with an ERC-20 token failed.\n */\n error SafeERC20FailedOperation(address token);\n\n /**\n * @dev Indicates a failed `decreaseAllowance` request.\n */\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\n\n /**\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));\n }\n\n /**\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n */\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n }\n\n /**\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\n return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));\n }\n\n /**\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\n return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n }\n\n /**\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n uint256 oldAllowance = token.allowance(address(this), spender);\n forceApprove(token, spender, oldAllowance + value);\n }\n\n /**\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\n * value, non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\n unchecked {\n uint256 currentAllowance = token.allowance(address(this), spender);\n if (currentAllowance < requestedDecrease) {\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\n }\n forceApprove(token, spender, currentAllowance - requestedDecrease);\n }\n }\n\n /**\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n * to be set to zero before setting it to a non-zero value, such as USDT.\n *\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\n * only sets the \"standard\" allowance. Any temporary allowance will remain active, in addition to the value being\n * set here.\n */\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\n bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));\n\n if (!_callOptionalReturnBool(token, approvalCall)) {\n _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));\n _callOptionalReturn(token, approvalCall);\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n safeTransfer(token, to, value);\n } else if (!token.transferAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\n * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferFromAndCallRelaxed(\n IERC1363 token,\n address from,\n address to,\n uint256 value,\n bytes memory data\n ) internal {\n if (to.code.length == 0) {\n safeTransferFrom(token, from, to, value);\n } else if (!token.transferFromAndCall(from, to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\n * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\n * once without retrying, and relies on the returned value to be true.\n *\n * Reverts if the returned value is other than `true`.\n */\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n forceApprove(token, to, value);\n } else if (!token.approveAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n // bubble errors\n if iszero(success) {\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize())\n revert(ptr, returndatasize())\n }\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n\n if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.\n */\n function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {\n bool success;\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)\n\npragma solidity ^0.8.20;\n\nimport {Errors} from \"./Errors.sol\";\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev There's no code at `target` (it is not a contract).\n */\n error AddressEmptyCode(address target);\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n if (address(this).balance < amount) {\n revert Errors.InsufficientBalance(address(this).balance, amount);\n }\n\n (bool success, bytes memory returndata) = recipient.call{value: amount}(\"\");\n if (!success) {\n _revert(returndata);\n }\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason or custom error, it is bubbled\n * up by this function (like regular Solidity function calls). However, if\n * the call reverted with no returned reason, this function reverts with a\n * {Errors.FailedCall} error.\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n if (address(this).balance < value) {\n revert Errors.InsufficientBalance(address(this).balance, value);\n }\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target\n * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case\n * of an unsuccessful call.\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata\n ) internal view returns (bytes memory) {\n if (!success) {\n _revert(returndata);\n } else {\n // only check if target is a contract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n if (returndata.length == 0 && target.code.length == 0) {\n revert AddressEmptyCode(target);\n }\n return returndata;\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the\n * revert reason or with a default {Errors.FailedCall} error.\n */\n function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {\n if (!success) {\n _revert(returndata);\n } else {\n return returndata;\n }\n }\n\n /**\n * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.\n */\n function _revert(bytes memory returndata) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n assembly (\"memory-safe\") {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert Errors.FailedCall();\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/cryptography/ECDSA.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS\n }\n\n /**\n * @dev The signature derives the `address(0)`.\n */\n error ECDSAInvalidSignature();\n\n /**\n * @dev The signature has an invalid length.\n */\n error ECDSAInvalidSignatureLength(uint256 length);\n\n /**\n * @dev The signature has an S value that is in the upper half order.\n */\n error ECDSAInvalidSignatureS(bytes32 s);\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not\n * return address(0) without also returning an error description. Errors are documented using an enum (error type)\n * and a bytes32 providing additional information about the error.\n *\n * If no error is returned, then the address can be used for verification purposes.\n *\n * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n */\n function tryRecover(\n bytes32 hash,\n bytes memory signature\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly (\"memory-safe\") {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n unchecked {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n // We do not check for an overflow here since the shift operation results in 0 or 1.\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n */\n function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS, s);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature, bytes32(0));\n }\n\n return (signer, RecoverError.NoError, bytes32(0));\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.\n */\n function _throwError(RecoverError error, bytes32 errorArg) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert ECDSAInvalidSignature();\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert ECDSAInvalidSignatureLength(uint256(errorArg));\n } else if (error == RecoverError.InvalidSignatureS) {\n revert ECDSAInvalidSignatureS(errorArg);\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/cryptography/EIP712.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/EIP712.sol)\n\npragma solidity ^0.8.20;\n\nimport {MessageHashUtils} from \"./MessageHashUtils.sol\";\nimport {ShortStrings, ShortString} from \"../ShortStrings.sol\";\nimport {IERC5267} from \"../../interfaces/IERC5267.sol\";\n\n/**\n * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.\n *\n * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose\n * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract\n * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to\n * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.\n *\n * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding\n * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA\n * ({_hashTypedDataV4}).\n *\n * The implementation of the domain separator was designed to be as efficient as possible while still properly updating\n * the chain id to protect against replay attacks on an eventual fork of the chain.\n *\n * NOTE: This contract implements the version of the encoding known as \"v4\", as implemented by the JSON RPC method\n * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].\n *\n * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain\n * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the\n * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.\n *\n * @custom:oz-upgrades-unsafe-allow state-variable-immutable\n */\nabstract contract EIP712 is IERC5267 {\n using ShortStrings for *;\n\n bytes32 private constant TYPE_HASH =\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\");\n\n // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to\n // invalidate the cached domain separator if the chain id changes.\n bytes32 private immutable _cachedDomainSeparator;\n uint256 private immutable _cachedChainId;\n address private immutable _cachedThis;\n\n bytes32 private immutable _hashedName;\n bytes32 private immutable _hashedVersion;\n\n ShortString private immutable _name;\n ShortString private immutable _version;\n // slither-disable-next-line constable-states\n string private _nameFallback;\n // slither-disable-next-line constable-states\n string private _versionFallback;\n\n /**\n * @dev Initializes the domain separator and parameter caches.\n *\n * The meaning of `name` and `version` is specified in\n * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:\n *\n * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.\n * - `version`: the current major version of the signing domain.\n *\n * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart\n * contract upgrade].\n */\n constructor(string memory name, string memory version) {\n _name = name.toShortStringWithFallback(_nameFallback);\n _version = version.toShortStringWithFallback(_versionFallback);\n _hashedName = keccak256(bytes(name));\n _hashedVersion = keccak256(bytes(version));\n\n _cachedChainId = block.chainid;\n _cachedDomainSeparator = _buildDomainSeparator();\n _cachedThis = address(this);\n }\n\n /**\n * @dev Returns the domain separator for the current chain.\n */\n function _domainSeparatorV4() internal view returns (bytes32) {\n if (address(this) == _cachedThis && block.chainid == _cachedChainId) {\n return _cachedDomainSeparator;\n } else {\n return _buildDomainSeparator();\n }\n }\n\n function _buildDomainSeparator() private view returns (bytes32) {\n return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));\n }\n\n /**\n * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this\n * function returns the hash of the fully encoded EIP712 message for this domain.\n *\n * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:\n *\n * ```solidity\n * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(\n * keccak256(\"Mail(address to,string contents)\"),\n * mailTo,\n * keccak256(bytes(mailContents))\n * )));\n * address signer = ECDSA.recover(digest, signature);\n * ```\n */\n function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {\n return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);\n }\n\n /**\n * @inheritdoc IERC5267\n */\n function eip712Domain()\n public\n view\n virtual\n returns (\n bytes1 fields,\n string memory name,\n string memory version,\n uint256 chainId,\n address verifyingContract,\n bytes32 salt,\n uint256[] memory extensions\n )\n {\n return (\n hex\"0f\", // 01111\n _EIP712Name(),\n _EIP712Version(),\n block.chainid,\n address(this),\n bytes32(0),\n new uint256[](0)\n );\n }\n\n /**\n * @dev The name parameter for the EIP712 domain.\n *\n * NOTE: By default this function reads _name which is an immutable value.\n * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).\n */\n // solhint-disable-next-line func-name-mixedcase\n function _EIP712Name() internal view returns (string memory) {\n return _name.toStringWithFallback(_nameFallback);\n }\n\n /**\n * @dev The version parameter for the EIP712 domain.\n *\n * NOTE: By default this function reads _version which is an immutable value.\n * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).\n */\n // solhint-disable-next-line func-name-mixedcase\n function _EIP712Version() internal view returns (string memory) {\n return _version.toStringWithFallback(_versionFallback);\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/cryptography/MessageHashUtils.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)\n\npragma solidity ^0.8.20;\n\nimport {Strings} from \"../Strings.sol\";\n\n/**\n * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.\n *\n * The library provides methods for generating a hash of a message that conforms to the\n * https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]\n * specifications.\n */\nlibrary MessageHashUtils {\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x45` (`personal_sign` messages).\n *\n * The digest is calculated by prefixing a bytes32 `messageHash` with\n * `\"\\x19Ethereum Signed Message:\\n32\"` and hashing the result. It corresponds with the\n * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.\n *\n * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with\n * keccak256, although any bytes32 value can be safely used because the final digest will\n * be re-hashed.\n *\n * See {ECDSA-recover}.\n */\n function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n mstore(0x00, \"\\x19Ethereum Signed Message:\\n32\") // 32 is the bytes-length of messageHash\n mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix\n digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)\n }\n }\n\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x45` (`personal_sign` messages).\n *\n * The digest is calculated by prefixing an arbitrary `message` with\n * `\"\\x19Ethereum Signed Message:\\n\" + len(message)` and hashing the result. It corresponds with the\n * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.\n *\n * See {ECDSA-recover}.\n */\n function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {\n return\n keccak256(bytes.concat(\"\\x19Ethereum Signed Message:\\n\", bytes(Strings.toString(message.length)), message));\n }\n\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x00` (data with intended validator).\n *\n * The digest is calculated by prefixing an arbitrary `data` with `\"\\x19\\x00\"` and the intended\n * `validator` address. Then hashing the result.\n *\n * See {ECDSA-recover}.\n */\n function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(hex\"19_00\", validator, data));\n }\n\n /**\n * @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.\n */\n function toDataWithIntendedValidatorHash(\n address validator,\n bytes32 messageHash\n ) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n mstore(0x00, hex\"19_00\")\n mstore(0x02, shl(96, validator))\n mstore(0x16, messageHash)\n digest := keccak256(0x00, 0x36)\n }\n }\n\n /**\n * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).\n *\n * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with\n * `\\x19\\x01` and hashing the result. It corresponds to the hash signed by the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.\n *\n * See {ECDSA-recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n mstore(ptr, hex\"19_01\")\n mstore(add(ptr, 0x02), domainSeparator)\n mstore(add(ptr, 0x22), structHash)\n digest := keccak256(ptr, 0x42)\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/Errors.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Collection of common custom errors used in multiple contracts\n *\n * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.\n * It is recommended to avoid relying on the error API for critical functionality.\n *\n * _Available since v5.1._\n */\nlibrary Errors {\n /**\n * @dev The ETH balance of the account is not enough to perform the operation.\n */\n error InsufficientBalance(uint256 balance, uint256 needed);\n\n /**\n * @dev A call to an address target failed. The target may have reverted.\n */\n error FailedCall();\n\n /**\n * @dev The deployment failed.\n */\n error FailedDeployment();\n\n /**\n * @dev A necessary precompile is missing.\n */\n error MissingPrecompile(address);\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/introspection/ERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/introspection/ERC165Checker.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165Checker.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @dev Library used to query support of an interface declared via {IERC165}.\n *\n * Note that these functions return the actual result of the query: they do not\n * `revert` if an interface is not supported. It is up to the caller to decide\n * what to do in these cases.\n */\nlibrary ERC165Checker {\n // As per the ERC-165 spec, no interface should ever match 0xffffffff\n bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff;\n\n /**\n * @dev Returns true if `account` supports the {IERC165} interface.\n */\n function supportsERC165(address account) internal view returns (bool) {\n // Any contract that implements ERC-165 must explicitly indicate support of\n // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid\n return\n supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&\n !supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);\n }\n\n /**\n * @dev Returns true if `account` supports the interface defined by\n * `interfaceId`. Support for {IERC165} itself is queried automatically.\n *\n * See {IERC165-supportsInterface}.\n */\n function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {\n // query support of both ERC-165 as per the spec and support of _interfaceId\n return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);\n }\n\n /**\n * @dev Returns a boolean array where each value corresponds to the\n * interfaces passed in and whether they're supported or not. This allows\n * you to batch check interfaces for a contract where your expectation\n * is that some interfaces may not be supported.\n *\n * See {IERC165-supportsInterface}.\n */\n function getSupportedInterfaces(\n address account,\n bytes4[] memory interfaceIds\n ) internal view returns (bool[] memory) {\n // an array of booleans corresponding to interfaceIds and whether they're supported or not\n bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);\n\n // query support of ERC-165 itself\n if (supportsERC165(account)) {\n // query support of each interface in interfaceIds\n for (uint256 i = 0; i < interfaceIds.length; i++) {\n interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);\n }\n }\n\n return interfaceIdsSupported;\n }\n\n /**\n * @dev Returns true if `account` supports all the interfaces defined in\n * `interfaceIds`. Support for {IERC165} itself is queried automatically.\n *\n * Batch-querying can lead to gas savings by skipping repeated checks for\n * {IERC165} support.\n *\n * See {IERC165-supportsInterface}.\n */\n function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {\n // query support of ERC-165 itself\n if (!supportsERC165(account)) {\n return false;\n }\n\n // query support of each interface in interfaceIds\n for (uint256 i = 0; i < interfaceIds.length; i++) {\n if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {\n return false;\n }\n }\n\n // all interfaces supported\n return true;\n }\n\n /**\n * @notice Query if a contract implements an interface, does not check ERC-165 support\n * @param account The address of the contract to query for support of an interface\n * @param interfaceId The interface identifier, as specified in ERC-165\n * @return true if the contract at account indicates support of the interface with\n * identifier interfaceId, false otherwise\n * @dev Assumes that account contains a contract that supports ERC-165, otherwise\n * the behavior of this method is undefined. This precondition can be checked\n * with {supportsERC165}.\n *\n * Some precompiled contracts will falsely indicate support for a given interface, so caution\n * should be exercised when using this function.\n *\n * Interface identification is specified in ERC-165.\n */\n function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {\n // prepare call\n bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));\n\n // perform static call\n bool success;\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)\n returnSize := returndatasize()\n returnValue := mload(0x00)\n }\n\n return success && returnSize >= 0x20 && returnValue > 0;\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.20;\n\nimport {Panic} from \"../Panic.sol\";\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Floor, // Toward negative infinity\n Ceil, // Toward positive infinity\n Trunc, // Toward zero\n Expand // Away from zero\n }\n\n /**\n * @dev Return the 512-bit addition of two uint256.\n *\n * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.\n */\n function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n assembly (\"memory-safe\") {\n low := add(a, b)\n high := lt(low, a)\n }\n }\n\n /**\n * @dev Return the 512-bit multiplication of two uint256.\n *\n * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.\n */\n function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use\n // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = high * 2²⁵⁶ + low.\n assembly (\"memory-safe\") {\n let mm := mulmod(a, b, not(0))\n low := mul(a, b)\n high := sub(sub(mm, low), lt(mm, low))\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a + b;\n success = c >= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a - b;\n success = c <= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a * b;\n assembly (\"memory-safe\") {\n // Only true when the multiplication doesn't overflow\n // (c / a == b) || (a == 0)\n success := or(eq(div(c, a), b), iszero(a))\n }\n // equivalent to: success ? c : 0\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `DIV` opcode returns zero when the denominator is 0.\n result := div(a, b)\n }\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `MOD` opcode returns zero when the denominator is 0.\n result := mod(a, b)\n }\n }\n }\n\n /**\n * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryAdd(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.\n */\n function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {\n (, uint256 result) = trySub(a, b);\n return result;\n }\n\n /**\n * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryMul(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * SafeCast.toUint(condition));\n }\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds towards infinity instead\n * of rounding towards zero.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n if (b == 0) {\n // Guarantee the same behavior as in a regular Solidity division.\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n\n // The following calculation ensures accurate ceiling division without overflow.\n // Since a is non-zero, (a - 1) / b will not overflow.\n // The largest possible result occurs when (a - 1) / b is type(uint256).max,\n // but the largest value we can obtain is type(uint256).max - 1, which happens\n // when a = type(uint256).max and b = 1.\n unchecked {\n return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);\n }\n }\n\n /**\n * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or\n * denominator == 0.\n *\n * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by\n * Uniswap Labs also under MIT license.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n\n // Handle non-overflow cases, 256 by 256 division.\n if (high == 0) {\n // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n // The surrounding unchecked block does not change this fact.\n // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n return low / denominator;\n }\n\n // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.\n if (denominator <= high) {\n Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));\n }\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [high low].\n uint256 remainder;\n assembly (\"memory-safe\") {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n high := sub(high, gt(remainder, low))\n low := sub(low, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator.\n // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.\n\n uint256 twos = denominator & (0 - denominator);\n assembly (\"memory-safe\") {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [high low] by twos.\n low := div(low, twos)\n\n // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from high into low.\n low |= high * twos;\n\n // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such\n // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv ≡ 1 mod 2⁴.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also\n // works in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶\n inverse *= 2 - denominator * inverse; // inverse mod 2³²\n inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴\n inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is\n // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high\n // is no longer required.\n result = low * inverse;\n return result;\n }\n }\n\n /**\n * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);\n }\n\n /**\n * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.\n */\n function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n if (high >= 1 << n) {\n Panic.panic(Panic.UNDER_OVERFLOW);\n }\n return (high << (256 - n)) | (low >> n);\n }\n }\n\n /**\n * @dev Calculates x * y >> n with full precision, following the selected rounding direction.\n */\n function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {\n return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);\n }\n\n /**\n * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.\n *\n * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.\n * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.\n *\n * If the input value is not inversible, 0 is returned.\n *\n * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the\n * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.\n */\n function invMod(uint256 a, uint256 n) internal pure returns (uint256) {\n unchecked {\n if (n == 0) return 0;\n\n // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)\n // Used to compute integers x and y such that: ax + ny = gcd(a, n).\n // When the gcd is 1, then the inverse of a modulo n exists and it's x.\n // ax + ny = 1\n // ax = 1 + (-y)n\n // ax ≡ 1 (mod n) # x is the inverse of a modulo n\n\n // If the remainder is 0 the gcd is n right away.\n uint256 remainder = a % n;\n uint256 gcd = n;\n\n // Therefore the initial coefficients are:\n // ax + ny = gcd(a, n) = n\n // 0a + 1n = n\n int256 x = 0;\n int256 y = 1;\n\n while (remainder != 0) {\n uint256 quotient = gcd / remainder;\n\n (gcd, remainder) = (\n // The old remainder is the next gcd to try.\n remainder,\n // Compute the next remainder.\n // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd\n // where gcd is at most n (capped to type(uint256).max)\n gcd - remainder * quotient\n );\n\n (x, y) = (\n // Increment the coefficient of a.\n y,\n // Decrement the coefficient of n.\n // Can overflow, but the result is casted to uint256 so that the\n // next value of y is \"wrapped around\" to a value between 0 and n - 1.\n x - y * int256(quotient)\n );\n }\n\n if (gcd != 1) return 0; // No inverse exists.\n return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.\n }\n }\n\n /**\n * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.\n *\n * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is\n * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that\n * `a**(p-2)` is the modular multiplicative inverse of a in Fp.\n *\n * NOTE: this function does NOT check that `p` is a prime greater than `2`.\n */\n function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {\n unchecked {\n return Math.modExp(a, p - 2, p);\n }\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)\n *\n * Requirements:\n * - modulus can't be zero\n * - underlying staticcall to precompile must succeed\n *\n * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make\n * sure the chain you're using it on supports the precompiled contract for modular exponentiation\n * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,\n * the underlying function will succeed given the lack of a revert, but the result may be incorrectly\n * interpreted as 0.\n */\n function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {\n (bool success, uint256 result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).\n * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying\n * to operate modulo 0 or if the underlying precompile reverted.\n *\n * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain\n * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in\n * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack\n * of a revert, but the result may be incorrectly interpreted as 0.\n */\n function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {\n if (m == 0) return (false, 0);\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n // | Offset | Content | Content (Hex) |\n // |-----------|------------|--------------------------------------------------------------------|\n // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x60:0x7f | value of b | 0x<.............................................................b> |\n // | 0x80:0x9f | value of e | 0x<.............................................................e> |\n // | 0xa0:0xbf | value of m | 0x<.............................................................m> |\n mstore(ptr, 0x20)\n mstore(add(ptr, 0x20), 0x20)\n mstore(add(ptr, 0x40), 0x20)\n mstore(add(ptr, 0x60), b)\n mstore(add(ptr, 0x80), e)\n mstore(add(ptr, 0xa0), m)\n\n // Given the result < m, it's guaranteed to fit in 32 bytes,\n // so we can use the memory scratch space located at offset 0.\n success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)\n result := mload(0x00)\n }\n }\n\n /**\n * @dev Variant of {modExp} that supports inputs of arbitrary length.\n */\n function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {\n (bool success, bytes memory result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Variant of {tryModExp} that supports inputs of arbitrary length.\n */\n function tryModExp(\n bytes memory b,\n bytes memory e,\n bytes memory m\n ) internal view returns (bool success, bytes memory result) {\n if (_zeroBytes(m)) return (false, new bytes(0));\n\n uint256 mLen = m.length;\n\n // Encode call args in result and move the free memory pointer\n result = abi.encodePacked(b.length, e.length, mLen, b, e, m);\n\n assembly (\"memory-safe\") {\n let dataPtr := add(result, 0x20)\n // Write result on top of args to avoid allocating extra memory.\n success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)\n // Overwrite the length.\n // result.length > returndatasize() is guaranteed because returndatasize() == m.length\n mstore(result, mLen)\n // Set the memory pointer after the returned data.\n mstore(0x40, add(dataPtr, mLen))\n }\n }\n\n /**\n * @dev Returns whether the provided byte array is zero.\n */\n function _zeroBytes(bytes memory byteArray) private pure returns (bool) {\n for (uint256 i = 0; i < byteArray.length; ++i) {\n if (byteArray[i] != 0) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded\n * towards zero.\n *\n * This method is based on Newton's method for computing square roots; the algorithm is restricted to only\n * using integer operations.\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n unchecked {\n // Take care of easy edge cases when a == 0 or a == 1\n if (a <= 1) {\n return a;\n }\n\n // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a\n // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between\n // the current value as `ε_n = | x_n - sqrt(a) |`.\n //\n // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root\n // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is\n // bigger than any uint256.\n //\n // By noticing that\n // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`\n // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar\n // to the msb function.\n uint256 aa = a;\n uint256 xn = 1;\n\n if (aa >= (1 << 128)) {\n aa >>= 128;\n xn <<= 64;\n }\n if (aa >= (1 << 64)) {\n aa >>= 64;\n xn <<= 32;\n }\n if (aa >= (1 << 32)) {\n aa >>= 32;\n xn <<= 16;\n }\n if (aa >= (1 << 16)) {\n aa >>= 16;\n xn <<= 8;\n }\n if (aa >= (1 << 8)) {\n aa >>= 8;\n xn <<= 4;\n }\n if (aa >= (1 << 4)) {\n aa >>= 4;\n xn <<= 2;\n }\n if (aa >= (1 << 2)) {\n xn <<= 1;\n }\n\n // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).\n //\n // We can refine our estimation by noticing that the middle of that interval minimizes the error.\n // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).\n // This is going to be our x_0 (and ε_0)\n xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)\n\n // From here, Newton's method give us:\n // x_{n+1} = (x_n + a / x_n) / 2\n //\n // One should note that:\n // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a\n // = ((x_n² + a) / (2 * x_n))² - a\n // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a\n // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)\n // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)\n // = (x_n² - a)² / (2 * x_n)²\n // = ((x_n² - a) / (2 * x_n))²\n // ≥ 0\n // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n\n //\n // This gives us the proof of quadratic convergence of the sequence:\n // ε_{n+1} = | x_{n+1} - sqrt(a) |\n // = | (x_n + a / x_n) / 2 - sqrt(a) |\n // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |\n // = | (x_n - sqrt(a))² / (2 * x_n) |\n // = | ε_n² / (2 * x_n) |\n // = ε_n² / | (2 * x_n) |\n //\n // For the first iteration, we have a special case where x_0 is known:\n // ε_1 = ε_0² / | (2 * x_0) |\n // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))\n // ≤ 2**(2*e-4) / (3 * 2**(e-1))\n // ≤ 2**(e-3) / 3\n // ≤ 2**(e-3-log2(3))\n // ≤ 2**(e-4.5)\n //\n // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:\n // ε_{n+1} = ε_n² / | (2 * x_n) |\n // ≤ (2**(e-k))² / (2 * 2**(e-1))\n // ≤ 2**(2*e-2*k) / 2**e\n // ≤ 2**(e-2*k)\n xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above\n xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5\n xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9\n xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18\n xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36\n xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72\n\n // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision\n // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either\n // sqrt(a) or sqrt(a) + 1.\n return xn - SafeCast.toUint(xn > a / xn);\n }\n }\n\n /**\n * @dev Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);\n }\n }\n\n /**\n * @dev Return the log in base 2 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log2(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // If upper 8 bits of 16-bit half set, add 8 to result\n r |= SafeCast.toUint((x >> r) > 0xff) << 3;\n // If upper 4 bits of 8-bit half set, add 4 to result\n r |= SafeCast.toUint((x >> r) > 0xf) << 2;\n\n // Shifts value right by the current result and use it as an index into this lookup table:\n //\n // | x (4 bits) | index | table[index] = MSB position |\n // |------------|---------|-----------------------------|\n // | 0000 | 0 | table[0] = 0 |\n // | 0001 | 1 | table[1] = 0 |\n // | 0010 | 2 | table[2] = 1 |\n // | 0011 | 3 | table[3] = 1 |\n // | 0100 | 4 | table[4] = 2 |\n // | 0101 | 5 | table[5] = 2 |\n // | 0110 | 6 | table[6] = 2 |\n // | 0111 | 7 | table[7] = 2 |\n // | 1000 | 8 | table[8] = 3 |\n // | 1001 | 9 | table[9] = 3 |\n // | 1010 | 10 | table[10] = 3 |\n // | 1011 | 11 | table[11] = 3 |\n // | 1100 | 12 | table[12] = 3 |\n // | 1101 | 13 | table[13] = 3 |\n // | 1110 | 14 | table[14] = 3 |\n // | 1111 | 15 | table[15] = 3 |\n //\n // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.\n assembly (\"memory-safe\") {\n r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))\n }\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);\n }\n }\n\n /**\n * @dev Return the log in base 10 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10 ** 64) {\n value /= 10 ** 64;\n result += 64;\n }\n if (value >= 10 ** 32) {\n value /= 10 ** 32;\n result += 32;\n }\n if (value >= 10 ** 16) {\n value /= 10 ** 16;\n result += 16;\n }\n if (value >= 10 ** 8) {\n value /= 10 ** 8;\n result += 8;\n }\n if (value >= 10 ** 4) {\n value /= 10 ** 4;\n result += 4;\n }\n if (value >= 10 ** 2) {\n value /= 10 ** 2;\n result += 2;\n }\n if (value >= 10 ** 1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);\n }\n }\n\n /**\n * @dev Return the log in base 256 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8\n return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);\n }\n\n /**\n * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);\n }\n }\n\n /**\n * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.\n */\n function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {\n return uint8(rounding) % 2 == 1;\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)\n// This file was procedurally generated from scripts/generate/templates/SafeCast.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeCast {\n /**\n * @dev Value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);\n\n /**\n * @dev An int value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedIntToUint(int256 value);\n\n /**\n * @dev Value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);\n\n /**\n * @dev An uint value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedUintToInt(uint256 value);\n\n /**\n * @dev Returns the downcasted uint248 from uint256, reverting on\n * overflow (when the input is greater than largest uint248).\n *\n * Counterpart to Solidity's `uint248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toUint248(uint256 value) internal pure returns (uint248) {\n if (value > type(uint248).max) {\n revert SafeCastOverflowedUintDowncast(248, value);\n }\n return uint248(value);\n }\n\n /**\n * @dev Returns the downcasted uint240 from uint256, reverting on\n * overflow (when the input is greater than largest uint240).\n *\n * Counterpart to Solidity's `uint240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toUint240(uint256 value) internal pure returns (uint240) {\n if (value > type(uint240).max) {\n revert SafeCastOverflowedUintDowncast(240, value);\n }\n return uint240(value);\n }\n\n /**\n * @dev Returns the downcasted uint232 from uint256, reverting on\n * overflow (when the input is greater than largest uint232).\n *\n * Counterpart to Solidity's `uint232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toUint232(uint256 value) internal pure returns (uint232) {\n if (value > type(uint232).max) {\n revert SafeCastOverflowedUintDowncast(232, value);\n }\n return uint232(value);\n }\n\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n if (value > type(uint224).max) {\n revert SafeCastOverflowedUintDowncast(224, value);\n }\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint216 from uint256, reverting on\n * overflow (when the input is greater than largest uint216).\n *\n * Counterpart to Solidity's `uint216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toUint216(uint256 value) internal pure returns (uint216) {\n if (value > type(uint216).max) {\n revert SafeCastOverflowedUintDowncast(216, value);\n }\n return uint216(value);\n }\n\n /**\n * @dev Returns the downcasted uint208 from uint256, reverting on\n * overflow (when the input is greater than largest uint208).\n *\n * Counterpart to Solidity's `uint208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toUint208(uint256 value) internal pure returns (uint208) {\n if (value > type(uint208).max) {\n revert SafeCastOverflowedUintDowncast(208, value);\n }\n return uint208(value);\n }\n\n /**\n * @dev Returns the downcasted uint200 from uint256, reverting on\n * overflow (when the input is greater than largest uint200).\n *\n * Counterpart to Solidity's `uint200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toUint200(uint256 value) internal pure returns (uint200) {\n if (value > type(uint200).max) {\n revert SafeCastOverflowedUintDowncast(200, value);\n }\n return uint200(value);\n }\n\n /**\n * @dev Returns the downcasted uint192 from uint256, reverting on\n * overflow (when the input is greater than largest uint192).\n *\n * Counterpart to Solidity's `uint192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toUint192(uint256 value) internal pure returns (uint192) {\n if (value > type(uint192).max) {\n revert SafeCastOverflowedUintDowncast(192, value);\n }\n return uint192(value);\n }\n\n /**\n * @dev Returns the downcasted uint184 from uint256, reverting on\n * overflow (when the input is greater than largest uint184).\n *\n * Counterpart to Solidity's `uint184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toUint184(uint256 value) internal pure returns (uint184) {\n if (value > type(uint184).max) {\n revert SafeCastOverflowedUintDowncast(184, value);\n }\n return uint184(value);\n }\n\n /**\n * @dev Returns the downcasted uint176 from uint256, reverting on\n * overflow (when the input is greater than largest uint176).\n *\n * Counterpart to Solidity's `uint176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toUint176(uint256 value) internal pure returns (uint176) {\n if (value > type(uint176).max) {\n revert SafeCastOverflowedUintDowncast(176, value);\n }\n return uint176(value);\n }\n\n /**\n * @dev Returns the downcasted uint168 from uint256, reverting on\n * overflow (when the input is greater than largest uint168).\n *\n * Counterpart to Solidity's `uint168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toUint168(uint256 value) internal pure returns (uint168) {\n if (value > type(uint168).max) {\n revert SafeCastOverflowedUintDowncast(168, value);\n }\n return uint168(value);\n }\n\n /**\n * @dev Returns the downcasted uint160 from uint256, reverting on\n * overflow (when the input is greater than largest uint160).\n *\n * Counterpart to Solidity's `uint160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toUint160(uint256 value) internal pure returns (uint160) {\n if (value > type(uint160).max) {\n revert SafeCastOverflowedUintDowncast(160, value);\n }\n return uint160(value);\n }\n\n /**\n * @dev Returns the downcasted uint152 from uint256, reverting on\n * overflow (when the input is greater than largest uint152).\n *\n * Counterpart to Solidity's `uint152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toUint152(uint256 value) internal pure returns (uint152) {\n if (value > type(uint152).max) {\n revert SafeCastOverflowedUintDowncast(152, value);\n }\n return uint152(value);\n }\n\n /**\n * @dev Returns the downcasted uint144 from uint256, reverting on\n * overflow (when the input is greater than largest uint144).\n *\n * Counterpart to Solidity's `uint144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toUint144(uint256 value) internal pure returns (uint144) {\n if (value > type(uint144).max) {\n revert SafeCastOverflowedUintDowncast(144, value);\n }\n return uint144(value);\n }\n\n /**\n * @dev Returns the downcasted uint136 from uint256, reverting on\n * overflow (when the input is greater than largest uint136).\n *\n * Counterpart to Solidity's `uint136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toUint136(uint256 value) internal pure returns (uint136) {\n if (value > type(uint136).max) {\n revert SafeCastOverflowedUintDowncast(136, value);\n }\n return uint136(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) {\n revert SafeCastOverflowedUintDowncast(128, value);\n }\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint120 from uint256, reverting on\n * overflow (when the input is greater than largest uint120).\n *\n * Counterpart to Solidity's `uint120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toUint120(uint256 value) internal pure returns (uint120) {\n if (value > type(uint120).max) {\n revert SafeCastOverflowedUintDowncast(120, value);\n }\n return uint120(value);\n }\n\n /**\n * @dev Returns the downcasted uint112 from uint256, reverting on\n * overflow (when the input is greater than largest uint112).\n *\n * Counterpart to Solidity's `uint112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toUint112(uint256 value) internal pure returns (uint112) {\n if (value > type(uint112).max) {\n revert SafeCastOverflowedUintDowncast(112, value);\n }\n return uint112(value);\n }\n\n /**\n * @dev Returns the downcasted uint104 from uint256, reverting on\n * overflow (when the input is greater than largest uint104).\n *\n * Counterpart to Solidity's `uint104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toUint104(uint256 value) internal pure returns (uint104) {\n if (value > type(uint104).max) {\n revert SafeCastOverflowedUintDowncast(104, value);\n }\n return uint104(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n if (value > type(uint96).max) {\n revert SafeCastOverflowedUintDowncast(96, value);\n }\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint88 from uint256, reverting on\n * overflow (when the input is greater than largest uint88).\n *\n * Counterpart to Solidity's `uint88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toUint88(uint256 value) internal pure returns (uint88) {\n if (value > type(uint88).max) {\n revert SafeCastOverflowedUintDowncast(88, value);\n }\n return uint88(value);\n }\n\n /**\n * @dev Returns the downcasted uint80 from uint256, reverting on\n * overflow (when the input is greater than largest uint80).\n *\n * Counterpart to Solidity's `uint80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toUint80(uint256 value) internal pure returns (uint80) {\n if (value > type(uint80).max) {\n revert SafeCastOverflowedUintDowncast(80, value);\n }\n return uint80(value);\n }\n\n /**\n * @dev Returns the downcasted uint72 from uint256, reverting on\n * overflow (when the input is greater than largest uint72).\n *\n * Counterpart to Solidity's `uint72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toUint72(uint256 value) internal pure returns (uint72) {\n if (value > type(uint72).max) {\n revert SafeCastOverflowedUintDowncast(72, value);\n }\n return uint72(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n if (value > type(uint64).max) {\n revert SafeCastOverflowedUintDowncast(64, value);\n }\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint56 from uint256, reverting on\n * overflow (when the input is greater than largest uint56).\n *\n * Counterpart to Solidity's `uint56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toUint56(uint256 value) internal pure returns (uint56) {\n if (value > type(uint56).max) {\n revert SafeCastOverflowedUintDowncast(56, value);\n }\n return uint56(value);\n }\n\n /**\n * @dev Returns the downcasted uint48 from uint256, reverting on\n * overflow (when the input is greater than largest uint48).\n *\n * Counterpart to Solidity's `uint48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toUint48(uint256 value) internal pure returns (uint48) {\n if (value > type(uint48).max) {\n revert SafeCastOverflowedUintDowncast(48, value);\n }\n return uint48(value);\n }\n\n /**\n * @dev Returns the downcasted uint40 from uint256, reverting on\n * overflow (when the input is greater than largest uint40).\n *\n * Counterpart to Solidity's `uint40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toUint40(uint256 value) internal pure returns (uint40) {\n if (value > type(uint40).max) {\n revert SafeCastOverflowedUintDowncast(40, value);\n }\n return uint40(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n if (value > type(uint32).max) {\n revert SafeCastOverflowedUintDowncast(32, value);\n }\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint24 from uint256, reverting on\n * overflow (when the input is greater than largest uint24).\n *\n * Counterpart to Solidity's `uint24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toUint24(uint256 value) internal pure returns (uint24) {\n if (value > type(uint24).max) {\n revert SafeCastOverflowedUintDowncast(24, value);\n }\n return uint24(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n if (value > type(uint16).max) {\n revert SafeCastOverflowedUintDowncast(16, value);\n }\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n if (value > type(uint8).max) {\n revert SafeCastOverflowedUintDowncast(8, value);\n }\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n if (value < 0) {\n revert SafeCastOverflowedIntToUint(value);\n }\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int248 from int256, reverting on\n * overflow (when the input is less than smallest int248 or\n * greater than largest int248).\n *\n * Counterpart to Solidity's `int248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toInt248(int256 value) internal pure returns (int248 downcasted) {\n downcasted = int248(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(248, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int240 from int256, reverting on\n * overflow (when the input is less than smallest int240 or\n * greater than largest int240).\n *\n * Counterpart to Solidity's `int240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toInt240(int256 value) internal pure returns (int240 downcasted) {\n downcasted = int240(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(240, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int232 from int256, reverting on\n * overflow (when the input is less than smallest int232 or\n * greater than largest int232).\n *\n * Counterpart to Solidity's `int232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toInt232(int256 value) internal pure returns (int232 downcasted) {\n downcasted = int232(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(232, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int224 from int256, reverting on\n * overflow (when the input is less than smallest int224 or\n * greater than largest int224).\n *\n * Counterpart to Solidity's `int224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toInt224(int256 value) internal pure returns (int224 downcasted) {\n downcasted = int224(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(224, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int216 from int256, reverting on\n * overflow (when the input is less than smallest int216 or\n * greater than largest int216).\n *\n * Counterpart to Solidity's `int216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toInt216(int256 value) internal pure returns (int216 downcasted) {\n downcasted = int216(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(216, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int208 from int256, reverting on\n * overflow (when the input is less than smallest int208 or\n * greater than largest int208).\n *\n * Counterpart to Solidity's `int208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toInt208(int256 value) internal pure returns (int208 downcasted) {\n downcasted = int208(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(208, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int200 from int256, reverting on\n * overflow (when the input is less than smallest int200 or\n * greater than largest int200).\n *\n * Counterpart to Solidity's `int200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toInt200(int256 value) internal pure returns (int200 downcasted) {\n downcasted = int200(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(200, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int192 from int256, reverting on\n * overflow (when the input is less than smallest int192 or\n * greater than largest int192).\n *\n * Counterpart to Solidity's `int192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toInt192(int256 value) internal pure returns (int192 downcasted) {\n downcasted = int192(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(192, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int184 from int256, reverting on\n * overflow (when the input is less than smallest int184 or\n * greater than largest int184).\n *\n * Counterpart to Solidity's `int184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toInt184(int256 value) internal pure returns (int184 downcasted) {\n downcasted = int184(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(184, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int176 from int256, reverting on\n * overflow (when the input is less than smallest int176 or\n * greater than largest int176).\n *\n * Counterpart to Solidity's `int176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toInt176(int256 value) internal pure returns (int176 downcasted) {\n downcasted = int176(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(176, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int168 from int256, reverting on\n * overflow (when the input is less than smallest int168 or\n * greater than largest int168).\n *\n * Counterpart to Solidity's `int168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toInt168(int256 value) internal pure returns (int168 downcasted) {\n downcasted = int168(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(168, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int160 from int256, reverting on\n * overflow (when the input is less than smallest int160 or\n * greater than largest int160).\n *\n * Counterpart to Solidity's `int160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toInt160(int256 value) internal pure returns (int160 downcasted) {\n downcasted = int160(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(160, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int152 from int256, reverting on\n * overflow (when the input is less than smallest int152 or\n * greater than largest int152).\n *\n * Counterpart to Solidity's `int152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toInt152(int256 value) internal pure returns (int152 downcasted) {\n downcasted = int152(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(152, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int144 from int256, reverting on\n * overflow (when the input is less than smallest int144 or\n * greater than largest int144).\n *\n * Counterpart to Solidity's `int144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toInt144(int256 value) internal pure returns (int144 downcasted) {\n downcasted = int144(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(144, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int136 from int256, reverting on\n * overflow (when the input is less than smallest int136 or\n * greater than largest int136).\n *\n * Counterpart to Solidity's `int136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toInt136(int256 value) internal pure returns (int136 downcasted) {\n downcasted = int136(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(136, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toInt128(int256 value) internal pure returns (int128 downcasted) {\n downcasted = int128(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(128, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int120 from int256, reverting on\n * overflow (when the input is less than smallest int120 or\n * greater than largest int120).\n *\n * Counterpart to Solidity's `int120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toInt120(int256 value) internal pure returns (int120 downcasted) {\n downcasted = int120(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(120, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int112 from int256, reverting on\n * overflow (when the input is less than smallest int112 or\n * greater than largest int112).\n *\n * Counterpart to Solidity's `int112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toInt112(int256 value) internal pure returns (int112 downcasted) {\n downcasted = int112(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(112, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int104 from int256, reverting on\n * overflow (when the input is less than smallest int104 or\n * greater than largest int104).\n *\n * Counterpart to Solidity's `int104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toInt104(int256 value) internal pure returns (int104 downcasted) {\n downcasted = int104(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(104, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int96 from int256, reverting on\n * overflow (when the input is less than smallest int96 or\n * greater than largest int96).\n *\n * Counterpart to Solidity's `int96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toInt96(int256 value) internal pure returns (int96 downcasted) {\n downcasted = int96(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(96, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int88 from int256, reverting on\n * overflow (when the input is less than smallest int88 or\n * greater than largest int88).\n *\n * Counterpart to Solidity's `int88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toInt88(int256 value) internal pure returns (int88 downcasted) {\n downcasted = int88(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(88, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int80 from int256, reverting on\n * overflow (when the input is less than smallest int80 or\n * greater than largest int80).\n *\n * Counterpart to Solidity's `int80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toInt80(int256 value) internal pure returns (int80 downcasted) {\n downcasted = int80(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(80, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int72 from int256, reverting on\n * overflow (when the input is less than smallest int72 or\n * greater than largest int72).\n *\n * Counterpart to Solidity's `int72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toInt72(int256 value) internal pure returns (int72 downcasted) {\n downcasted = int72(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(72, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toInt64(int256 value) internal pure returns (int64 downcasted) {\n downcasted = int64(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(64, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int56 from int256, reverting on\n * overflow (when the input is less than smallest int56 or\n * greater than largest int56).\n *\n * Counterpart to Solidity's `int56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toInt56(int256 value) internal pure returns (int56 downcasted) {\n downcasted = int56(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(56, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int48 from int256, reverting on\n * overflow (when the input is less than smallest int48 or\n * greater than largest int48).\n *\n * Counterpart to Solidity's `int48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toInt48(int256 value) internal pure returns (int48 downcasted) {\n downcasted = int48(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(48, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int40 from int256, reverting on\n * overflow (when the input is less than smallest int40 or\n * greater than largest int40).\n *\n * Counterpart to Solidity's `int40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toInt40(int256 value) internal pure returns (int40 downcasted) {\n downcasted = int40(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(40, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toInt32(int256 value) internal pure returns (int32 downcasted) {\n downcasted = int32(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(32, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int24 from int256, reverting on\n * overflow (when the input is less than smallest int24 or\n * greater than largest int24).\n *\n * Counterpart to Solidity's `int24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toInt24(int256 value) internal pure returns (int24 downcasted) {\n downcasted = int24(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(24, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toInt16(int256 value) internal pure returns (int16 downcasted) {\n downcasted = int16(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(16, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toInt8(int256 value) internal pure returns (int8 downcasted) {\n downcasted = int8(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(8, value);\n }\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n if (value > uint256(type(int256).max)) {\n revert SafeCastOverflowedUintToInt(value);\n }\n return int256(value);\n }\n\n /**\n * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.\n */\n function toUint(bool b) internal pure returns (uint256 u) {\n assembly (\"memory-safe\") {\n u := iszero(iszero(b))\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/math/SignedMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)\n\npragma solidity ^0.8.20;\n\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard signed math utilities missing in the Solidity language.\n */\nlibrary SignedMath {\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));\n }\n }\n\n /**\n * @dev Returns the largest of two signed numbers.\n */\n function max(int256 a, int256 b) internal pure returns (int256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two signed numbers.\n */\n function min(int256 a, int256 b) internal pure returns (int256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two signed numbers without overflow.\n * The result is rounded towards zero.\n */\n function average(int256 a, int256 b) internal pure returns (int256) {\n // Formula from the book \"Hacker's Delight\"\n int256 x = (a & b) + ((a ^ b) >> 1);\n return x + (int256(uint256(x) >> 255) & (a ^ b));\n }\n\n /**\n * @dev Returns the absolute unsigned value of a signed value.\n */\n function abs(int256 n) internal pure returns (uint256) {\n unchecked {\n // Formula from the \"Bit Twiddling Hacks\" by Sean Eron Anderson.\n // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,\n // taking advantage of the most significant (or \"sign\" bit) in two's complement representation.\n // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,\n // the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).\n int256 mask = n >> 255;\n\n // A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.\n return uint256((n + mask) ^ mask);\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/Panic.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Helper library for emitting standardized panic codes.\n *\n * ```solidity\n * contract Example {\n * using Panic for uint256;\n *\n * // Use any of the declared internal constants\n * function foo() { Panic.GENERIC.panic(); }\n *\n * // Alternatively\n * function foo() { Panic.panic(Panic.GENERIC); }\n * }\n * ```\n *\n * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].\n *\n * _Available since v5.1._\n */\n// slither-disable-next-line unused-state\nlibrary Panic {\n /// @dev generic / unspecified error\n uint256 internal constant GENERIC = 0x00;\n /// @dev used by the assert() builtin\n uint256 internal constant ASSERT = 0x01;\n /// @dev arithmetic underflow or overflow\n uint256 internal constant UNDER_OVERFLOW = 0x11;\n /// @dev division or modulo by zero\n uint256 internal constant DIVISION_BY_ZERO = 0x12;\n /// @dev enum conversion error\n uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;\n /// @dev invalid encoding in storage\n uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;\n /// @dev empty array pop\n uint256 internal constant EMPTY_ARRAY_POP = 0x31;\n /// @dev array out of bounds access\n uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;\n /// @dev resource error (too large allocation or too large array)\n uint256 internal constant RESOURCE_ERROR = 0x41;\n /// @dev calling invalid internal function\n uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;\n\n /// @dev Reverts with a panic code. Recommended to use with\n /// the internal constants with predefined codes.\n function panic(uint256 code) internal pure {\n assembly (\"memory-safe\") {\n mstore(0x00, 0x4e487b71)\n mstore(0x20, code)\n revert(0x1c, 0x24)\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/ReentrancyGuard.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,\n * consider using {ReentrancyGuardTransient} instead.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant NOT_ENTERED = 1;\n uint256 private constant ENTERED = 2;\n\n uint256 private _status;\n\n /**\n * @dev Unauthorized reentrant call.\n */\n error ReentrancyGuardReentrantCall();\n\n constructor() {\n _status = NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be NOT_ENTERED\n if (_status == ENTERED) {\n revert ReentrancyGuardReentrantCall();\n }\n\n // Any calls to nonReentrant after this point will fail\n _status = ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = NOT_ENTERED;\n }\n\n /**\n * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n * `nonReentrant` function in the call stack.\n */\n function _reentrancyGuardEntered() internal view returns (bool) {\n return _status == ENTERED;\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/ShortStrings.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/ShortStrings.sol)\n\npragma solidity ^0.8.20;\n\nimport {StorageSlot} from \"./StorageSlot.sol\";\n\n// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |\n// | length | 0x BB |\ntype ShortString is bytes32;\n\n/**\n * @dev This library provides functions to convert short memory strings\n * into a `ShortString` type that can be used as an immutable variable.\n *\n * Strings of arbitrary length can be optimized using this library if\n * they are short enough (up to 31 bytes) by packing them with their\n * length (1 byte) in a single EVM word (32 bytes). Additionally, a\n * fallback mechanism can be used for every other case.\n *\n * Usage example:\n *\n * ```solidity\n * contract Named {\n * using ShortStrings for *;\n *\n * ShortString private immutable _name;\n * string private _nameFallback;\n *\n * constructor(string memory contractName) {\n * _name = contractName.toShortStringWithFallback(_nameFallback);\n * }\n *\n * function name() external view returns (string memory) {\n * return _name.toStringWithFallback(_nameFallback);\n * }\n * }\n * ```\n */\nlibrary ShortStrings {\n // Used as an identifier for strings longer than 31 bytes.\n bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;\n\n error StringTooLong(string str);\n error InvalidShortString();\n\n /**\n * @dev Encode a string of at most 31 chars into a `ShortString`.\n *\n * This will trigger a `StringTooLong` error is the input string is too long.\n */\n function toShortString(string memory str) internal pure returns (ShortString) {\n bytes memory bstr = bytes(str);\n if (bstr.length > 31) {\n revert StringTooLong(str);\n }\n return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));\n }\n\n /**\n * @dev Decode a `ShortString` back to a \"normal\" string.\n */\n function toString(ShortString sstr) internal pure returns (string memory) {\n uint256 len = byteLength(sstr);\n // using `new string(len)` would work locally but is not memory safe.\n string memory str = new string(32);\n assembly (\"memory-safe\") {\n mstore(str, len)\n mstore(add(str, 0x20), sstr)\n }\n return str;\n }\n\n /**\n * @dev Return the length of a `ShortString`.\n */\n function byteLength(ShortString sstr) internal pure returns (uint256) {\n uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;\n if (result > 31) {\n revert InvalidShortString();\n }\n return result;\n }\n\n /**\n * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.\n */\n function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {\n if (bytes(value).length < 32) {\n return toShortString(value);\n } else {\n StorageSlot.getStringSlot(store).value = value;\n return ShortString.wrap(FALLBACK_SENTINEL);\n }\n }\n\n /**\n * @dev Decode a string that was encoded to `ShortString` or written to storage using {toShortStringWithFallback}.\n */\n function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {\n if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {\n return toString(value);\n } else {\n return store;\n }\n }\n\n /**\n * @dev Return the length of a string that was encoded to `ShortString` or written to storage using\n * {toShortStringWithFallback}.\n *\n * WARNING: This will return the \"byte length\" of the string. This may not reflect the actual length in terms of\n * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.\n */\n function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {\n if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {\n return byteLength(value);\n } else {\n return bytes(store).length;\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/StorageSlot.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)\n// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC-1967 implementation slot:\n * ```solidity\n * contract ERC1967 {\n * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(newImplementation.code.length > 0);\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * TIP: Consider using this library along with {SlotDerivation}.\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n struct Int256Slot {\n int256 value;\n }\n\n struct StringSlot {\n string value;\n }\n\n struct BytesSlot {\n bytes value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Int256Slot` with member `value` located at `slot`.\n */\n function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `StringSlot` with member `value` located at `slot`.\n */\n function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `StringSlot` representation of the string storage pointer `store`.\n */\n function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := store.slot\n }\n }\n\n /**\n * @dev Returns a `BytesSlot` with member `value` located at `slot`.\n */\n function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.\n */\n function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := store.slot\n }\n }\n}\n" + }, + "npm/@openzeppelin/contracts@5.3.0/utils/Strings.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol)\n\npragma solidity ^0.8.20;\n\nimport {Math} from \"./math/Math.sol\";\nimport {SafeCast} from \"./math/SafeCast.sol\";\nimport {SignedMath} from \"./math/SignedMath.sol\";\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n using SafeCast for *;\n\n bytes16 private constant HEX_DIGITS = \"0123456789abcdef\";\n uint8 private constant ADDRESS_LENGTH = 20;\n uint256 private constant SPECIAL_CHARS_LOOKUP =\n (1 << 0x08) | // backspace\n (1 << 0x09) | // tab\n (1 << 0x0a) | // newline\n (1 << 0x0c) | // form feed\n (1 << 0x0d) | // carriage return\n (1 << 0x22) | // double quote\n (1 << 0x5c); // backslash\n\n /**\n * @dev The `value` string doesn't fit in the specified `length`.\n */\n error StringsInsufficientHexLength(uint256 value, uint256 length);\n\n /**\n * @dev The string being parsed contains characters that are not in scope of the given base.\n */\n error StringsInvalidChar();\n\n /**\n * @dev The string being parsed is not a properly formatted address.\n */\n error StringsInvalidAddressFormat();\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = Math.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n assembly (\"memory-safe\") {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n assembly (\"memory-safe\") {\n mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `int256` to its ASCII `string` decimal representation.\n */\n function toStringSigned(int256 value) internal pure returns (string memory) {\n return string.concat(value < 0 ? \"-\" : \"\", toString(SignedMath.abs(value)));\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, Math.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n uint256 localValue = value;\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = HEX_DIGITS[localValue & 0xf];\n localValue >>= 4;\n }\n if (localValue != 0) {\n revert StringsInsufficientHexLength(value, length);\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal\n * representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal\n * representation, according to EIP-55.\n */\n function toChecksumHexString(address addr) internal pure returns (string memory) {\n bytes memory buffer = bytes(toHexString(addr));\n\n // hash the hex part of buffer (skip length + 2 bytes, length 40)\n uint256 hashValue;\n assembly (\"memory-safe\") {\n hashValue := shr(96, keccak256(add(buffer, 0x22), 40))\n }\n\n for (uint256 i = 41; i > 1; --i) {\n // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)\n if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {\n // case shift by xoring with 0x20\n buffer[i] ^= 0x20;\n }\n hashValue >>= 4;\n }\n return string(buffer);\n }\n\n /**\n * @dev Returns true if the two strings are equal.\n */\n function equal(string memory a, string memory b) internal pure returns (bool) {\n return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));\n }\n\n /**\n * @dev Parse a decimal string and returns the value as a `uint256`.\n *\n * Requirements:\n * - The string must be formatted as `[0-9]*`\n * - The result must fit into an `uint256` type\n */\n function parseUint(string memory input) internal pure returns (uint256) {\n return parseUint(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `[0-9]*`\n * - The result must fit into an `uint256` type\n */\n function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {\n (bool success, uint256 value) = tryParseUint(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {\n return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid\n * character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseUint(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, uint256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseUintUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseUintUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, uint256 value) {\n bytes memory buffer = bytes(input);\n\n uint256 result = 0;\n for (uint256 i = begin; i < end; ++i) {\n uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));\n if (chr > 9) return (false, 0);\n result *= 10;\n result += chr;\n }\n return (true, result);\n }\n\n /**\n * @dev Parse a decimal string and returns the value as a `int256`.\n *\n * Requirements:\n * - The string must be formatted as `[-+]?[0-9]*`\n * - The result must fit in an `int256` type.\n */\n function parseInt(string memory input) internal pure returns (int256) {\n return parseInt(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `[-+]?[0-9]*`\n * - The result must fit in an `int256` type.\n */\n function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {\n (bool success, int256 value) = tryParseInt(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if\n * the result does not fit in a `int256`.\n *\n * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.\n */\n function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {\n return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);\n }\n\n uint256 private constant ABS_MIN_INT256 = 2 ** 255;\n\n /**\n * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid\n * character or if the result does not fit in a `int256`.\n *\n * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.\n */\n function tryParseInt(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, int256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseIntUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseIntUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, int256 value) {\n bytes memory buffer = bytes(input);\n\n // Check presence of a negative sign.\n bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n bool positiveSign = sign == bytes1(\"+\");\n bool negativeSign = sign == bytes1(\"-\");\n uint256 offset = (positiveSign || negativeSign).toUint();\n\n (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);\n\n if (absSuccess && absValue < ABS_MIN_INT256) {\n return (true, negativeSign ? -int256(absValue) : int256(absValue));\n } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {\n return (true, type(int256).min);\n } else return (false, 0);\n }\n\n /**\n * @dev Parse a hexadecimal string (with or without \"0x\" prefix), and returns the value as a `uint256`.\n *\n * Requirements:\n * - The string must be formatted as `(0x)?[0-9a-fA-F]*`\n * - The result must fit in an `uint256` type.\n */\n function parseHexUint(string memory input) internal pure returns (uint256) {\n return parseHexUint(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `(0x)?[0-9a-fA-F]*`\n * - The result must fit in an `uint256` type.\n */\n function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {\n (bool success, uint256 value) = tryParseHexUint(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {\n return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an\n * invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseHexUint(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, uint256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseHexUintUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseHexUintUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, uint256 value) {\n bytes memory buffer = bytes(input);\n\n // skip 0x prefix if present\n bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2(\"0x\"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n uint256 offset = hasPrefix.toUint() * 2;\n\n uint256 result = 0;\n for (uint256 i = begin + offset; i < end; ++i) {\n uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));\n if (chr > 15) return (false, 0);\n result *= 16;\n unchecked {\n // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).\n // This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.\n result += chr;\n }\n }\n return (true, result);\n }\n\n /**\n * @dev Parse a hexadecimal string (with or without \"0x\" prefix), and returns the value as an `address`.\n *\n * Requirements:\n * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`\n */\n function parseAddress(string memory input) internal pure returns (address) {\n return parseAddress(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`\n */\n function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {\n (bool success, address value) = tryParseAddress(input, begin, end);\n if (!success) revert StringsInvalidAddressFormat();\n return value;\n }\n\n /**\n * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly\n * formatted address. See {parseAddress-string} requirements.\n */\n function tryParseAddress(string memory input) internal pure returns (bool success, address value) {\n return tryParseAddress(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly\n * formatted address. See {parseAddress-string-uint256-uint256} requirements.\n */\n function tryParseAddress(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, address value) {\n if (end > bytes(input).length || begin > end) return (false, address(0));\n\n bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2(\"0x\"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n uint256 expectedLength = 40 + hasPrefix.toUint() * 2;\n\n // check that input is the correct length\n if (end - begin == expectedLength) {\n // length guarantees that this does not overflow, and value is at most type(uint160).max\n (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);\n return (s, address(uint160(v)));\n } else {\n return (false, address(0));\n }\n }\n\n function _tryParseChr(bytes1 chr) private pure returns (uint8) {\n uint8 value = uint8(chr);\n\n // Try to parse `chr`:\n // - Case 1: [0-9]\n // - Case 2: [a-f]\n // - Case 3: [A-F]\n // - otherwise not supported\n unchecked {\n if (value > 47 && value < 58) value -= 48;\n else if (value > 96 && value < 103) value -= 87;\n else if (value > 64 && value < 71) value -= 55;\n else return type(uint8).max;\n }\n\n return value;\n }\n\n /**\n * @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.\n *\n * WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.\n *\n * NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of\n * RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode\n * characters that are not in this range, but other tooling may provide different results.\n */\n function escapeJSON(string memory input) internal pure returns (string memory) {\n bytes memory buffer = bytes(input);\n bytes memory output = new bytes(2 * buffer.length); // worst case scenario\n uint256 outputLength = 0;\n\n for (uint256 i; i < buffer.length; ++i) {\n bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));\n if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {\n output[outputLength++] = \"\\\\\";\n if (char == 0x08) output[outputLength++] = \"b\";\n else if (char == 0x09) output[outputLength++] = \"t\";\n else if (char == 0x0a) output[outputLength++] = \"n\";\n else if (char == 0x0c) output[outputLength++] = \"f\";\n else if (char == 0x0d) output[outputLength++] = \"r\";\n else if (char == 0x5c) output[outputLength++] = \"\\\\\";\n else if (char == 0x22) {\n // solhint-disable-next-line quotes\n output[outputLength++] = '\"';\n }\n } else {\n output[outputLength++] = char;\n }\n }\n // write the actual length and deallocate unused memory\n assembly (\"memory-safe\") {\n mstore(output, outputLength)\n mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))\n }\n\n return string(output);\n }\n\n /**\n * @dev Reads a bytes32 from a bytes array without bounds checking.\n *\n * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the\n * assembly block as such would prevent some optimizations.\n */\n function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {\n // This is not memory safe in the general case, but all calls to this private function are within bounds.\n assembly (\"memory-safe\") {\n value := mload(add(buffer, add(0x20, offset)))\n }\n }\n}\n" + }, + "project/contracts/Controller.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/access/Ownable.sol';\n\nimport './interfaces/IController.sol';\n\n/**\n * @title Controller\n * @dev Manages allow lists for solvers, executors, proposal signers and validators\n */\ncontract Controller is IController, Ownable {\n // List of allowed solvers\n mapping (address => bool) public override isSolverAllowed;\n\n // List of allowed executors\n mapping (address => bool) public override isExecutorAllowed;\n\n // List of allowed proposal signers\n mapping (address => bool) public override isProposalSignerAllowed;\n\n // List of allowed validators\n mapping (address => bool) public override isValidatorAllowed;\n\n // Minimum number of validations allowed\n uint8 public override minValidations;\n\n /**\n * @dev Creates a new Controller contract\n * @param owner Address that will own the contract\n * @param solvers List of allowed solvers\n * @param executors List of allowed executors\n * @param proposalSigners List of allowed proposal signers\n * @param validators List of allowed validators\n * @param _minValidations Minimum number of validations allowed\n */\n constructor(\n address owner,\n address[] memory solvers,\n address[] memory executors,\n address[] memory proposalSigners,\n address[] memory validators,\n uint8 _minValidations\n ) Ownable(owner) {\n for (uint256 i = 0; i < solvers.length; i++) _setAllowedSolver(solvers[i], true);\n for (uint256 i = 0; i < executors.length; i++) _setAllowedExecutor(executors[i], true);\n for (uint256 i = 0; i < proposalSigners.length; i++) _setAllowedProposalSigner(proposalSigners[i], true);\n for (uint256 i = 0; i < validators.length; i++) _setAllowedValidator(validators[i], true);\n _setMinValidations(_minValidations);\n }\n\n /**\n * @dev Sets permissions for multiple solvers\n * @param solvers List of solver addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedSolvers(address[] memory solvers, bool[] memory alloweds) external override onlyOwner {\n if (solvers.length != alloweds.length) revert ControllerInputInvalidLength();\n for (uint256 i = 0; i < solvers.length; i++) _setAllowedSolver(solvers[i], alloweds[i]);\n }\n\n /**\n * @dev Sets permissions for multiple executors\n * @param executors List of executor addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedExecutors(address[] memory executors, bool[] memory alloweds) external override onlyOwner {\n if (executors.length != alloweds.length) revert ControllerInputInvalidLength();\n for (uint256 i = 0; i < executors.length; i++) _setAllowedExecutor(executors[i], alloweds[i]);\n }\n\n /**\n * @dev Sets permissions for multiple proposal signers\n * @param signers List of proposal signer addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedProposalSigners(address[] memory signers, bool[] memory alloweds) external override onlyOwner {\n if (signers.length != alloweds.length) revert ControllerInputInvalidLength();\n for (uint256 i = 0; i < signers.length; i++) _setAllowedProposalSigner(signers[i], alloweds[i]);\n }\n\n /**\n * @dev Sets permissions for multiple validators\n * @param validators List of validator addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedValidators(address[] memory validators, bool[] memory alloweds) external override onlyOwner {\n if (validators.length != alloweds.length) revert ControllerInputInvalidLength();\n for (uint256 i = 0; i < validators.length; i++) _setAllowedValidator(validators[i], alloweds[i]);\n }\n\n /**\n * @dev Sets the minimum number of validations allowed\n * @param newMinValidations minimum number of validations allowed\n */\n function setMinValidations(uint8 newMinValidations) external override onlyOwner {\n _setMinValidations(newMinValidations);\n }\n\n /**\n * @dev Sets a solver permission\n */\n function _setAllowedSolver(address solver, bool allowed) internal {\n isSolverAllowed[solver] = allowed;\n emit SolverAllowedSet(solver, allowed);\n }\n\n /**\n * @dev Sets an executor permission\n */\n function _setAllowedExecutor(address executor, bool allowed) internal {\n isExecutorAllowed[executor] = allowed;\n emit ExecutorAllowedSet(executor, allowed);\n }\n\n /**\n * @dev Sets a proposal signer permission\n */\n function _setAllowedProposalSigner(address signer, bool allowed) internal {\n isProposalSignerAllowed[signer] = allowed;\n emit ProposalSignerAllowedSet(signer, allowed);\n }\n\n /**\n * @dev Sets a validator permission\n */\n function _setAllowedValidator(address validator, bool allowed) internal {\n isValidatorAllowed[validator] = allowed;\n emit ValidatorAllowedSet(validator, allowed);\n }\n\n /**\n * @dev Sets the minimum number of validations allowed\n */\n function _setMinValidations(uint8 newMinValidations) internal {\n minValidations = newMinValidations;\n emit MinValidationSet(newMinValidations);\n }\n}\n" + }, + "project/contracts/Intents.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Enum representing the type of intent operation.\n * - Swap: Swap tokens between chains or tokens.\n * - Transfer: Transfer tokens to one or more recipients.\n * - Call: Execute arbitrary contract calls.\n */\nenum OpType {\n Swap,\n Transfer,\n Call\n}\n\n/**\n * @dev Execution structure.\n * @param intent Intent to be fulfilled.\n * @param proposal Proposal to be executed.\n * @param signature Proposal signature.\n */\nstruct Execution {\n Intent intent;\n Proposal proposal;\n bytes signature;\n}\n\n/**\n * @dev EIP-712 typed data struct representing a validator's approval of an intent.\n * @param intent The hash of the intent being validated.\n */\nstruct Validation {\n bytes32 intent;\n}\n\n/**\n * @dev General intent structure used to abstract over different intent types.\n * @param op The type of operation this intent represents.\n * @param user The originator of the intent.\n * @param settler The address responsible for executing the intent on-chain.\n * @param nonce A unique value used to prevent replay attacks and distinguish intents.\n * @param deadline The timestamp by which the intent must be executed.\n * @param data ABI-encoded data representing a specific intent type (e.g. SwapIntent, TransferIntent, CallIntent).\n * @param maxFees List of max fees the user is willing to pay for the intent.\n * @param events List of custom intent events to be emitted.\n * @param configSig The signature of the configuration that this intent belongs to\n * @param minValidations The minimum number of validator approvals required for this intent to be considered valid.\n * @param validations The list validator signatures attesting to this intent.\n */\nstruct Intent {\n uint8 op;\n address user;\n address settler;\n bytes32 nonce;\n uint256 deadline;\n bytes data;\n MaxFee[] maxFees;\n IntentEvent[] events;\n bytes configSig;\n uint256 minValidations;\n bytes[] validations;\n}\n\n/**\n * @dev Max fee representation\n * @param token Token used to pay for the execution fee.\n * @param amount Max amount of fee token to be paid for settling this intent.\n */\nstruct MaxFee {\n address token;\n uint256 amount;\n}\n\n/**\n * @dev Intent event representation.\n * @param topic Event topic to be emitted.\n * @param data Event data to be emitted.\n */\nstruct IntentEvent {\n bytes32 topic;\n bytes data;\n}\n\n/**\n * @dev Represents a swap intent between two chains.\n * @param sourceChain Chain ID where tokens will be sent from.\n * @param destinationChain Chain ID where tokens will be received.\n * @param tokensIn List of input tokens and amounts to swap.\n * @param tokensOut List of expected output tokens, minimum amounts, and recipients.\n */\nstruct SwapIntent {\n uint256 sourceChain;\n uint256 destinationChain;\n TokenIn[] tokensIn;\n TokenOut[] tokensOut;\n}\n\n/**\n * @dev Token in representation.\n * @param token Address of a token to be sent.\n * @param amount Amount of tokens to be sent.\n */\nstruct TokenIn {\n address token;\n uint256 amount;\n}\n\n/**\n * @dev Token out representation.\n * @param token Address of a token to be received.\n * @param minAmount Minimum amount of tokens to be received.\n * @param recipient Recipient address that will receive the token out.\n */\nstruct TokenOut {\n address token;\n uint256 minAmount;\n address recipient;\n}\n\n/**\n * @dev Represents a transfer intent containing multiple token transfers.\n * @param chainId Chain ID where the transfers should be executed.\n * @param transfers List of token transfers to be performed.\n */\nstruct TransferIntent {\n uint256 chainId;\n TransferData[] transfers;\n}\n\n/**\n * @dev Transfer data for a single token transfer.\n * @param token Address of the token to transfer.\n * @param amount Amount of the token to transfer.\n * @param recipient Recipient of the token transfer.\n */\nstruct TransferData {\n address token;\n uint256 amount;\n address recipient;\n}\n\n/**\n * @dev Represents a generic call intent consisting of one or more contract calls.\n * @param chainId Chain ID where the calls should be executed.\n * @param calls List of low-level contract calls to be executed.\n */\nstruct CallIntent {\n uint256 chainId;\n CallData[] calls;\n}\n\n/**\n * @dev Low-level call data for a target contract interaction.\n * @param target Target contract address.\n * @param data Calldata to be sent to the target.\n * @param value ETH value to send along with the call.\n */\nstruct CallData {\n address target;\n bytes data;\n uint256 value;\n}\n\n/**\n * @dev Generic proposal structure representing a solver’s response to an intent.\n * @param deadline Timestamp until when the proposal is valid.\n * @param data ABI-encoded proposal-specific data (e.g. SwapProposal).\n * @param fees List of fee amounts the solver requires for execution.\n */\nstruct Proposal {\n uint256 deadline;\n bytes data;\n uint256[] fees;\n}\n\n/**\n * @dev Swap proposal representation for a swap intent.\n * @param executor Address of the executor contract that should be called during intent execution.\n * @param data Arbitrary data used to call the executor contract.\n * @param amountsOut List of amounts of tokens out proposed by the solver.\n */\nstruct SwapProposal {\n address executor;\n bytes data;\n uint256[] amountsOut;\n}\n\nlibrary IntentsHelpers {\n bytes32 internal constant INTENT_TYPE_HASH =\n keccak256(\n 'Intent(uint8 op,address user,address settler,bytes32 nonce,uint256 deadline,bytes data,MaxFee[] maxFees,IntentEvent[] events,bytes configSig,uint256 minValidations)IntentEvent(bytes32 topic,bytes data)MaxFee(address token,uint256 amount)'\n );\n\n bytes32 internal constant PROPOSAL_TYPE_HASH =\n keccak256('Proposal(bytes32 intent,address solver,uint256 deadline,bytes data,uint256[] fees)');\n\n bytes32 internal constant VALIDATION_TYPE_HASH = keccak256('Validation(bytes32 intent)');\n\n bytes32 internal constant MAX_FEE_TYPE_HASH = keccak256('MaxFee(address token,uint256 amount)');\n\n bytes32 internal constant INTENT_EVENT_TYPE_HASH = keccak256('IntentEvent(bytes32 topic,bytes data)');\n\n function hash(Intent memory intent) internal pure returns (bytes32) {\n return\n keccak256(\n abi.encode(\n INTENT_TYPE_HASH,\n intent.op,\n intent.user,\n intent.settler,\n intent.nonce,\n intent.deadline,\n keccak256(intent.data),\n hash(intent.maxFees),\n hash(intent.events),\n intent.configSig,\n intent.minValidations\n )\n );\n }\n\n function hash(Proposal memory proposal, Intent memory intent, address solver) internal pure returns (bytes32) {\n return\n keccak256(\n abi.encode(\n PROPOSAL_TYPE_HASH,\n hash(intent),\n solver,\n proposal.deadline,\n keccak256(proposal.data),\n hash(proposal.fees)\n )\n );\n }\n\n function hash(MaxFee[] memory fees) internal pure returns (bytes32) {\n bytes32[] memory hashes = new bytes32[](fees.length);\n for (uint256 i = 0; i < fees.length; i++) {\n hashes[i] = keccak256(abi.encode(MAX_FEE_TYPE_HASH, fees[i].token, fees[i].amount));\n }\n return keccak256(abi.encodePacked(hashes));\n }\n\n function hash(IntentEvent[] memory events) internal pure returns (bytes32) {\n bytes32[] memory hashes = new bytes32[](events.length);\n for (uint256 i = 0; i < events.length; i++) {\n hashes[i] = keccak256(abi.encode(INTENT_EVENT_TYPE_HASH, events[i].topic, keccak256(events[i].data)));\n }\n return keccak256(abi.encodePacked(hashes));\n }\n\n function hash(uint256[] memory fees) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(fees));\n }\n\n function hash(Validation memory validation) internal pure returns (bytes32) {\n return keccak256(abi.encode(VALIDATION_TYPE_HASH, validation.intent));\n }\n}\n" + }, + "project/contracts/interfaces/IController.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @title Controller interface\n */\ninterface IController {\n /**\n * @dev The input arrays are not of equal length\n */\n error ControllerInputInvalidLength();\n\n /**\n * @dev Emitted every time a solver permission is set\n */\n event SolverAllowedSet(address indexed solver, bool allowed);\n\n /**\n * @dev Emitted every time an executor permission is set\n */\n event ExecutorAllowedSet(address indexed executor, bool allowed);\n\n /**\n * @dev Emitted every time a proposal signer permission is set\n */\n event ProposalSignerAllowedSet(address indexed proposalSigner, bool allowed);\n\n /**\n * @dev Emitted every time a validator permission is set\n */\n event ValidatorAllowedSet(address indexed validator, bool allowed);\n\n /**\n * @dev Emitted when the minimum validations changes\n */\n event MinValidationSet(uint8 indexed newMinValidation);\n\n /**\n * @dev Tells whether a solver is allowed\n * @param solver Address of the solver being queried\n */\n function isSolverAllowed(address solver) external view returns (bool);\n\n /**\n * @dev Tells whether an executor is allowed\n * @param executor Address of the executor being queried\n */\n function isExecutorAllowed(address executor) external view returns (bool);\n\n /**\n * @dev Tells whether a proposal signer is allowed\n * @param signer Address of the proposal signer being queried\n */\n function isProposalSignerAllowed(address signer) external view returns (bool);\n\n /**\n * @dev Tells whether a validator is allowed\n * @param validator Address of the validator being queried\n */\n function isValidatorAllowed(address validator) external view returns (bool);\n\n /**\n * @dev Tells the minimum number of validations allowed\n */\n function minValidations() external view returns (uint8);\n\n /**\n * @dev Sets permissions for multiple solvers\n * @param solvers List of solver addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedSolvers(address[] memory solvers, bool[] memory alloweds) external;\n\n /**\n * @dev Sets permissions for multiple executors\n * @param executors List of executor addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedExecutors(address[] memory executors, bool[] memory alloweds) external;\n\n /**\n * @dev Sets permissions for multiple proposal signers\n * @param signers List of proposal signer addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedProposalSigners(address[] memory signers, bool[] memory alloweds) external;\n\n /**\n * @dev Sets permissions for multiple validators\n * @param validators List of validator addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedValidators(address[] memory validators, bool[] memory alloweds) external;\n\n /**\n * @dev Sets the minimum number of validations allowed\n * @param newMinValidations minimum number of validations allowed\n */\n function setMinValidations(uint8 newMinValidations) external;\n}\n" + }, + "project/contracts/interfaces/ICreateX.sol": { + "content": "// SPDX-License-Identifier: AGPL-3.0-only\n\npragma solidity ^0.8.4;\n\n/**\n * @title CreateX Factory Interface Definition\n * @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/)\n * @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/)\n */\ninterface ICreateX {\n event ContractCreation(address indexed newContract);\n\n function deployCreate3(bytes32 salt, bytes memory initCode) external payable returns (address newContract);\n}\n" + }, + "project/contracts/interfaces/IExecutor.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../Intents.sol';\n\n/**\n * @title Executor interface\n */\ninterface IExecutor {\n /**\n * @dev Executes an intent proposal\n * @param intent Intent to be executed\n * @param proposal Proposal to be executed to fulfill the intent\n */\n function execute(Intent memory intent, Proposal memory proposal) external;\n}\n" + }, + "project/contracts/interfaces/IIntentsValidator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../Intents.sol';\nimport '../safeguards/Safeguards.sol';\n\n/**\n * @title IntentsValidator\n * @dev Performs intents validations based on safeguards\n */\ninterface IIntentsValidator {\n /**\n * @dev Validates an intent for a safeguard\n * @param intent Intent to be validated\n * @param config Safeguard config to validate the intent with\n */\n function validate(Intent memory intent, bytes memory config) external pure;\n}\n" + }, + "project/contracts/interfaces/ISafe.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nenum SafeOperation {\n Call,\n DelegateCall\n}\n\ninterface ISafe {\n function getThreshold() external view returns (uint256);\n\n function execTransactionFromModuleReturnData(address to, uint256 value, bytes memory data, SafeOperation operation)\n external\n returns (bool success, bytes memory returnData);\n}\n" + }, + "project/contracts/interfaces/ISettler.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../Intents.sol';\nimport '../safeguards/Safeguards.sol';\n\n/**\n * @title Settler interface\n */\ninterface ISettler {\n /**\n * @dev The requested intent type is unknown\n */\n error SettlerUnknownIntentType(uint8 op);\n\n /**\n * @dev The simulation has been successful\n */\n error SettlerSimulationSuccess(uint256 gasUsed);\n\n /**\n * @dev The solver is not allowed\n */\n error SettlerSolverNotAllowed(address solver);\n\n /**\n * @dev The executor is not allowed\n */\n error SettlerExecutorNotAllowed(address executor);\n\n /**\n * @dev The proposal signer is not allowed\n */\n error SettlerProposalSignerNotAllowed(address signer);\n\n /**\n * @dev The validator is not allowed\n */\n error SettlerValidatorNotAllowed(address validator);\n\n /**\n * @dev The validator is duplicated\n */\n error SettlerValidatorDuplicatedOrUnsorted(address previous, address current);\n\n /**\n * @dev The settler is not the current contract\n */\n error SettlerInvalidSettler(address settler);\n\n /**\n * @dev The nonce is zero\n */\n error SettlerNonceZero();\n\n /**\n * @dev The nonce has already been used for the user\n */\n error SettlerNonceAlreadyUsed(address user, bytes32 nonce);\n\n /**\n * @dev The intent deadline is in the past\n */\n error SettlerIntentPastDeadline(uint256 deadline, uint256 timestamp);\n\n /**\n * @dev The current chain is not valid\n */\n error SettlerInvalidChain(uint256 chainId);\n\n /**\n * @dev The recipient is the settler contract\n */\n error SettlerInvalidRecipient(address to);\n\n /**\n * @dev The user is not a smart account\n */\n error SettlerUserNotSmartAccount(address user);\n\n /**\n * @dev The amount out is lower than the proposed amount\n */\n error SettlerAmountOutLtProposed(uint256 index, uint256 amountOut, uint256 proposed);\n\n /**\n * @dev The proposed amount is lower than the minimum amount\n */\n error SettlerProposedAmountLtMinAmount(uint256 index, uint256 proposed, uint256 minAmount);\n\n /**\n * @dev The proposed amounts array and the tokens out array are not of equal length\n */\n error SettlerInvalidProposedAmounts();\n\n /**\n * @dev The balance after the proposal execution is lower than the balance before\n */\n error SettlerPostBalanceOutLtPre(uint256 index, uint256 post, uint256 pre);\n\n /**\n * @dev The solver fees length does not match the requested by the user\n */\n error SettlerSolverFeeInvalidLength();\n\n /**\n * @dev The solver fee is too high\n */\n error SettlerSolverFeeTooHigh(uint256 fee, uint256 max);\n\n /**\n * @dev The intent validations are not enough\n */\n error SettlerIntentValidationsNotEnough(uint256 min, uint256 current);\n\n /**\n * @dev The proposal deadline is in the past\n */\n error SettlerProposalPastDeadline(uint256 deadline, uint256 timestamp);\n\n /**\n * @dev The proposal data is not empty\n */\n error SettlerProposalDataNotEmpty();\n\n /**\n * @dev The rescue funds recipient is zero\n */\n error SettlerRescueFundsRecipientZero();\n\n /**\n * @dev The list of safeguards exceed the maximum allowed\n */\n error SettlerTooManySafeguards(uint256 lengthRequested);\n\n /**\n * @dev The new smart accounts handler is zero\n */\n error SmartAccountsHandlerZero();\n\n /**\n * @dev Custom events emitted for each intent\n */\n event IntentExecuted(\n address indexed user,\n bytes32 indexed topic,\n uint8 indexed op,\n Intent intent,\n Proposal proposal,\n bytes output,\n bytes data\n );\n\n /**\n * @dev Emitted every time an intent is fulfilled\n */\n event ProposalExecuted(bytes32 indexed proposal, uint256 index);\n\n /**\n * @dev Emitted every time tokens are withdrawn from the contract balance\n */\n event FundsRescued(address indexed token, address indexed recipient, uint256 amount);\n\n /**\n * @dev Emitted every time the smart accounts handler is set\n */\n event SmartAccountsHandlerSet(address indexed smartAccountsHandler);\n\n /**\n * @dev Emitted every time the intents validator is set\n */\n event IntentsValidatorSet(address indexed intentsValidator);\n\n /**\n * @dev Emitted every time a safeguard is set\n */\n event SafeguardSet(address indexed user);\n\n /**\n * @dev Tells the reference to the Mimic controller\n */\n function controller() external view returns (address);\n\n /**\n * @dev Tells the reference to the smart accounts handler\n */\n function smartAccountsHandler() external view returns (address);\n\n /**\n * @dev Tells the reference to the intents validator\n */\n function intentsValidator() external view returns (address);\n\n /**\n * @dev Tells the block at which a user nonce was used. Returns 0 if unused.\n * @param user Address of the user being queried\n * @param nonce Nonce being queried\n */\n function getNonceBlock(address user, bytes32 nonce) external view returns (uint256);\n\n /**\n * @dev Tells the safeguard set for a user\n * @param user Address of the user being queried\n */\n function getUserSafeguard(address user) external view returns (bytes memory);\n\n /**\n * @dev Tells the hash of an intent\n * @param intent Intent to get the hash of\n */\n function getIntentHash(Intent memory intent) external pure returns (bytes32);\n\n /**\n * @dev Tells the hash of a proposal\n * @param proposal Proposal to be hashed\n * @param intent Intent being fulfilled by the requested proposal\n * @param solver Address of the solver that made the proposal\n */\n function getProposalHash(Proposal memory proposal, Intent memory intent, address solver)\n external\n pure\n returns (bytes32);\n\n /**\n * @dev Withdraws ERC20 or native tokens from the contract\n * @param token Address of the token to be withdrawn\n * @param recipient Address of the account receiving the tokens\n * @param amount Amount of tokens to be withdrawn\n */\n function rescueFunds(address token, address recipient, uint256 amount) external;\n\n /**\n * @dev Sets a new smart accounts handler\n * @param newSmartAccountsHandler New smart accounts handler to be set\n */\n function setSmartAccountsHandler(address newSmartAccountsHandler) external;\n\n /**\n * @dev Sets a new intents validator address\n * @param newIntentsValidator New intents validator to be set\n */\n function setIntentsValidator(address newIntentsValidator) external;\n\n /**\n * @dev Sets a safeguard for a user\n * @param safeguard Safeguard to be set\n */\n function setSafeguard(bytes memory safeguard) external;\n\n /**\n * @dev Executes a proposal to fulfill an intent\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n */\n function execute(Execution[] memory executions) external;\n\n /**\n * @dev Simulates an execution. It will always revert. Successful executions are returned as\n * `SettlerSimulationSuccess` errors. Any other error should be treated as failure.\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n */\n function simulate(Execution[] memory executions) external;\n}\n" + }, + "project/contracts/interfaces/ISmartAccount.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/interfaces/IERC1271.sol';\nimport '@openzeppelin/contracts/utils/introspection/IERC165.sol';\n\n/**\n * @title SmartAccount interface\n */\ninterface ISmartAccount is IERC165, IERC1271 {\n /**\n * @dev Emitted every time tokens are transferred\n */\n event Transferred(address indexed token, address indexed recipient, uint256 amount);\n\n /**\n * @dev Emitted every time `call` is called\n */\n event Called(address indexed target, bytes data, uint256 value, bytes result);\n\n /**\n * @dev Transfers ERC20 or native tokens to the recipient. Sender must be the owner or the settler.\n * @param token Address of the token to be withdrawn\n * @param recipient Address of the account receiving the tokens\n * @param amount Amount of tokens to be withdrawn\n */\n function transfer(address token, address recipient, uint256 amount) external;\n\n /**\n * @dev Executes an arbitrary call from the contract. Sender must be the owner or the settler.\n * @param target Address where the call will be sent\n * @param data Calldata to be sent to the target\n * @param value Native token value to send along with the call\n */\n function call(address target, bytes memory data, uint256 value) external returns (bytes memory result);\n}\n" + }, + "project/contracts/interfaces/ISmartAccountsHandler.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\ninterface ISmartAccountsHandler {\n /**\n * @dev The smart account given is not supported\n */\n error SmartAccountsHandlerUnsupportedAccount(address account);\n\n /**\n * @dev Tells whether an account is a supported smart account\n * @param account Address of the account being queried\n */\n function isSmartAccount(address account) external view returns (bool);\n\n /**\n * @dev Performs a transfer from a smart account\n */\n function transfer(address account, address token, address to, uint256 amount) external;\n\n /**\n * @dev Performs a call from a smart account\n */\n function call(address account, address target, bytes memory data, uint256 value) external returns (bytes memory);\n}\n" + }, + "project/contracts/safeguards/BaseIntentsValidator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './Safeguards.sol';\nimport '../Intents.sol';\n\n/**\n * @title BaseIntentsValidator\n */\ncontract BaseIntentsValidator {\n /**\n * @dev No intents allowed\n */\n error IntentsValidatorNoneAllowed();\n\n /**\n * @dev Intent type unknown\n */\n error IntentsValidatorUnknownIntentType(uint8 opType);\n\n /**\n * @dev Invalid safeguard mode\n */\n error IntentsValidatorInvalidSafeguardMode(uint8 mode);\n\n /**\n * @dev Tells whether a chain is allowed\n */\n function _isChainAllowed(uint256 chainId, bytes memory config) internal pure returns (bool) {\n (bool isDenyList, uint256[] memory values) = abi.decode(config, (bool, uint256[]));\n if (isDenyList) {\n for (uint256 i = 0; i < values.length; i++) {\n if (chainId == values[i]) return false;\n }\n return true;\n } else {\n for (uint256 i = 0; i < values.length; i++) {\n if (chainId == values[i]) return true;\n }\n return false;\n }\n }\n\n /**\n * @dev Tells whether an account is allowed\n */\n function _isAccountAllowed(address account, bytes memory config) internal pure returns (bool) {\n (bool isDenyList, address[] memory values) = abi.decode(config, (bool, address[]));\n if (isDenyList) {\n for (uint256 i = 0; i < values.length; i++) {\n if (account == values[i]) return false;\n }\n return true;\n } else {\n for (uint256 i = 0; i < values.length; i++) {\n if (account == values[i]) return true;\n }\n return false;\n }\n }\n\n /**\n * @dev Tells whether a selector is allowed\n */\n function _isSelectorAllowed(bytes4 selector, bytes memory config) internal pure returns (bool) {\n (bool isDenyList, bytes4[] memory values) = abi.decode(config, (bool, bytes4[]));\n if (isDenyList) {\n for (uint256 i = 0; i < values.length; i++) {\n if (selector == values[i]) return false;\n }\n return true;\n } else {\n for (uint256 i = 0; i < values.length; i++) {\n if (selector == values[i]) return true;\n }\n return false;\n }\n }\n}\n" + }, + "project/contracts/safeguards/CallIntentsValidator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './Safeguards.sol';\nimport './BaseIntentsValidator.sol';\nimport '../Intents.sol';\n\n/**\n * @dev Call safeguard modes to validate call intents\n * @param None To ensure no calls are allowed\n * @param Chain To validate that the chain where calls execute is allowed\n * @param Target To validate that the call targets (contract addresses) are allowed\n * @param Selector To validate that the function selectors being called are allowed\n */\nenum CallSafeguardMode {\n None,\n Chain,\n Target,\n Selector\n}\n\n/**\n * @title CallIntentsValidator\n * @dev Performs call intents validations based on safeguards\n */\ncontract CallIntentsValidator is BaseIntentsValidator {\n /**\n * @dev Tells whether a call intent is valid for a safeguard\n * @param intent Call intent to be validated\n * @param safeguard Safeguard to validate the intent with\n */\n function _isCallIntentValid(Intent memory intent, Safeguard memory safeguard) internal pure returns (bool) {\n CallIntent memory callIntent = abi.decode(intent.data, (CallIntent));\n if (safeguard.mode == uint8(CallSafeguardMode.Chain))\n return _isChainAllowed(callIntent.chainId, safeguard.config);\n if (safeguard.mode == uint8(CallSafeguardMode.Target))\n return _areCallTargetsValid(callIntent.calls, safeguard.config);\n if (safeguard.mode == uint8(CallSafeguardMode.Selector))\n return _areCallSelectorsValid(callIntent.calls, safeguard.config);\n revert IntentsValidatorInvalidSafeguardMode(safeguard.mode);\n }\n\n /**\n * @dev Tells whether the call targets are allowed\n */\n function _areCallTargetsValid(CallData[] memory calls, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < calls.length; i++) {\n if (!_isAccountAllowed(calls[i].target, config)) return false;\n }\n return true;\n }\n\n /**\n * @dev Tells whether the call selectors are allowed\n */\n function _areCallSelectorsValid(CallData[] memory calls, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < calls.length; i++) {\n if (!_isSelectorAllowed(bytes4(calls[i].data), config)) return false;\n }\n return true;\n }\n}\n" + }, + "project/contracts/safeguards/IntentsValidator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './CallIntentsValidator.sol';\nimport './TransferIntentsValidator.sol';\nimport './Safeguards.sol';\nimport './SwapIntentsValidator.sol';\nimport '../Intents.sol';\nimport '../interfaces/IIntentsValidator.sol';\n\n/**\n * @title IntentsValidator\n * @dev Performs intents validations based on safeguards\n */\ncontract IntentsValidator is IIntentsValidator, SwapIntentsValidator, TransferIntentsValidator, CallIntentsValidator {\n /**\n * @dev Safeguard validation failed\n */\n error IntentsValidatorSafeguardFailed();\n\n /**\n * @dev Invalid safeguard config mode\n */\n error IntentsValidatorInvalidSafeguardConfigMode(uint8 mode);\n\n /**\n * @dev Invalid safeguard group logic mode\n */\n error IntentsValidatorInvalidSafeguardGroupLogicMode(uint8 mode);\n\n /**\n * @dev Tells whether an intent is valid for a safeguard\n * @param intent Intent to be validated\n * @param config Safeguard config to validate the intent with\n */\n function validate(Intent memory intent, bytes memory config) external pure override {\n (uint8 mode, bytes memory safeguard) = abi.decode(config, (uint8, bytes));\n if (mode == uint8(SafeguardConfigMode.List)) _validate(intent, abi.decode(safeguard, (Safeguard[])));\n else if (mode == uint8(SafeguardConfigMode.Tree)) _validate(intent, _decodeSafeguardTree(safeguard));\n else revert IntentsValidatorInvalidSafeguardConfigMode(mode);\n }\n\n /**\n * @dev Tells whether an intent is valid for a safeguards list\n * @param intent Intent to be validated\n * @param safeguards Safeguard list to validate the intent with\n */\n function _validate(Intent memory intent, Safeguard[] memory safeguards) internal pure {\n if (safeguards.length == 0) revert IntentsValidatorSafeguardFailed();\n for (uint256 i = 0; i < safeguards.length; i++) {\n if (!_isSafeguardValid(intent, safeguards[i])) revert IntentsValidatorSafeguardFailed();\n }\n }\n\n /**\n * @dev Tells whether an intent is valid for a safeguard tree\n * @param intent Intent to be validated\n * @param tree Safeguard tree to validate the intent with\n */\n function _validate(Intent memory intent, SafeguardTree memory tree) internal pure {\n if (tree.nodes.length == 0) revert IntentsValidatorSafeguardFailed();\n if (!_isSafeguardGroupValid(intent, tree, 0)) revert IntentsValidatorSafeguardFailed();\n }\n\n /**\n * @dev Tells whether an intent is valid for a safeguard tree at a certain level\n * @param intent Intent to be validated\n * @param tree Safeguard tree to validate the intent with\n * @param index Index of the group node to evaluate\n */\n function _isSafeguardGroupValid(Intent memory intent, SafeguardTree memory tree, uint16 index)\n internal\n pure\n returns (bool)\n {\n SafeguardGroup memory group = tree.nodes[index];\n\n if (group.logic == uint8(SafeguardGroupLogic.NOT)) {\n for (uint256 i = 0; i < group.leaves.length; i++) {\n if (_isSafeguardValid(intent, tree.leaves[group.leaves[i]])) return false;\n }\n for (uint256 i = 0; i < group.children.length; i++) {\n if (_isSafeguardGroupValid(intent, tree, group.children[i])) return false;\n }\n return true;\n }\n\n if (group.logic == uint8(SafeguardGroupLogic.AND)) {\n for (uint256 i = 0; i < group.leaves.length; i++) {\n if (!_isSafeguardValid(intent, tree.leaves[group.leaves[i]])) return false;\n }\n for (uint256 i = 0; i < group.children.length; i++) {\n if (!_isSafeguardGroupValid(intent, tree, group.children[i])) return false;\n }\n return true;\n }\n\n if (group.logic == uint8(SafeguardGroupLogic.OR)) {\n for (uint256 i = 0; i < group.leaves.length; i++) {\n if (_isSafeguardValid(intent, tree.leaves[group.leaves[i]])) return true;\n }\n for (uint256 i = 0; i < group.children.length; i++) {\n if (_isSafeguardGroupValid(intent, tree, group.children[i])) return true;\n }\n return false;\n }\n\n if (group.logic == uint8(SafeguardGroupLogic.XOR)) {\n uint256 hits = 0;\n for (uint256 i = 0; i < group.leaves.length; i++) {\n if (_isSafeguardValid(intent, tree.leaves[group.leaves[i]]))\n if (++hits > 1) return false;\n }\n for (uint256 i = 0; i < group.children.length; i++) {\n if (_isSafeguardGroupValid(intent, tree, group.children[i]))\n if (++hits > 1) return false;\n }\n return hits == 1;\n }\n\n revert IntentsValidatorInvalidSafeguardGroupLogicMode(group.logic);\n }\n\n /**\n * @dev Tells whether an intent is valid for a safeguard\n * @param intent Intent to be validated\n * @param safeguard Safeguard to validate the intent with\n */\n function _isSafeguardValid(Intent memory intent, Safeguard memory safeguard) internal pure returns (bool) {\n if (safeguard.mode == uint8(0)) revert IntentsValidatorNoneAllowed();\n if (intent.op == uint8(OpType.Swap)) return _isSwapIntentValid(intent, safeguard);\n if (intent.op == uint8(OpType.Transfer)) return _isTransferIntentValid(intent, safeguard);\n if (intent.op == uint8(OpType.Call)) return _isCallIntentValid(intent, safeguard);\n revert IntentsValidatorUnknownIntentType(uint8(intent.op));\n }\n\n /**\n * @dev Safely decodes a safeguard tree avoiding compiler issues with dynamic arrays\n * @param data Safeguard tree data to be decoded\n */\n function _decodeSafeguardTree(bytes memory data) private pure returns (SafeguardTree memory) {\n (SafeguardGroup[] memory nodes, Safeguard[] memory leaves) = abi.decode(data, (SafeguardGroup[], Safeguard[]));\n return SafeguardTree(nodes, leaves);\n }\n}\n" + }, + "project/contracts/safeguards/Safeguards.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Safeguard config modes\n * - List: Safeguard lists\n * - Tree: Safeguard groups\n */\nenum SafeguardConfigMode {\n List,\n Tree\n}\n\n/**\n * @dev Logical operators for safeguard groups\n * - AND: every child must pass\n * - OR: at least one child must pass\n * - XOR: exactly one child must pass\n * - NOT: every child must fail\n */\nenum SafeguardGroupLogic {\n AND,\n OR,\n XOR,\n NOT\n}\n\n/**\n * @dev Flat node in the safeguard tree\n * @param logic Group operator (AND/OR/XOR/NOT)\n * @param leaves Indices into `SafeguardTree.leaves`\n * @param children Indices into `SafeguardTree.nodes`\n */\nstruct SafeguardGroup {\n uint8 logic;\n uint16[] leaves;\n uint16[] children;\n}\n\n/**\n * @dev Safeguard tree representation\n * @param nodes List of all the nodes in the tree\n * @param leaves List of all the leaves in the tree\n */\nstruct SafeguardTree {\n SafeguardGroup[] nodes;\n Safeguard[] leaves;\n}\n\n/**\n * @dev Safeguard representation\n * @param mode Safeguard mode\n * @param config Safeguard configuration settings or parameters\n */\nstruct Safeguard {\n uint8 mode;\n bytes config;\n}\n" + }, + "project/contracts/safeguards/SwapIntentsValidator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './BaseIntentsValidator.sol';\nimport './Safeguards.sol';\nimport '../Intents.sol';\n\n/**\n * @dev Swap safeguard modes to validate swap intents\n * @param None To ensure no swaps are allowed\n * @param SourceChain To validate that the source chain is allowed\n * @param DestinationChain To validate that the destination chain is allowed\n * @param TokenIn To validate that the tokens to be sent are allowed\n * @param TokenOut To validate that the tokens to be received are allowed\n * @param Recipient To validate that the recipients that will receive the tokens are allowed\n */\nenum SwapSafeguardMode {\n None,\n SourceChain,\n DestinationChain,\n TokenIn,\n TokenOut,\n Recipient\n}\n\n/**\n * @title SwapIntentsValidator\n * @dev Performs swap intents validations based on safeguards\n */\ncontract SwapIntentsValidator is BaseIntentsValidator {\n /**\n * @dev Tells whether a swap intent is valid for a safeguard\n * @param intent Swap intent to be validated\n * @param safeguard Safeguard to validate the intent with\n */\n function _isSwapIntentValid(Intent memory intent, Safeguard memory safeguard) internal pure returns (bool) {\n SwapIntent memory swapIntent = abi.decode(intent.data, (SwapIntent));\n if (safeguard.mode == uint8(SwapSafeguardMode.SourceChain))\n return _isChainAllowed(swapIntent.sourceChain, safeguard.config);\n if (safeguard.mode == uint8(SwapSafeguardMode.DestinationChain))\n return _isChainAllowed(swapIntent.destinationChain, safeguard.config);\n if (safeguard.mode == uint8(SwapSafeguardMode.TokenIn))\n return _areSwapTokensInValid(swapIntent.tokensIn, safeguard.config);\n if (safeguard.mode == uint8(SwapSafeguardMode.TokenOut))\n return _areSwapTokensOutValid(swapIntent.tokensOut, safeguard.config);\n if (safeguard.mode == uint8(SwapSafeguardMode.Recipient))\n return _areSwapRecipientsValid(swapIntent.tokensOut, safeguard.config);\n revert IntentsValidatorInvalidSafeguardMode(safeguard.mode);\n }\n\n /**\n * @dev Tells whether the tokens to be sent are allowed\n */\n function _areSwapTokensInValid(TokenIn[] memory tokensIn, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < tokensIn.length; i++) {\n if (!_isAccountAllowed(tokensIn[i].token, config)) return false;\n }\n return true;\n }\n\n /**\n * @dev Tells whether the tokens to be received are allowed\n */\n function _areSwapTokensOutValid(TokenOut[] memory tokensOut, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < tokensOut.length; i++) {\n if (!_isAccountAllowed(tokensOut[i].token, config)) return false;\n }\n return true;\n }\n\n /**\n * @dev Tells whether the recipients to be received are allowed\n */\n function _areSwapRecipientsValid(TokenOut[] memory tokensOut, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < tokensOut.length; i++) {\n if (!_isAccountAllowed(tokensOut[i].recipient, config)) return false;\n }\n return true;\n }\n}\n" + }, + "project/contracts/safeguards/TransferIntentsValidator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './BaseIntentsValidator.sol';\nimport './Safeguards.sol';\nimport '../Intents.sol';\n\n/**\n * @dev Transfer safeguard modes to validate transfer intents\n * @param None To ensure no transfers are allowed\n * @param Chain To validate that the chain where transfers execute is allowed\n * @param Token To validate that the tokens being transferred are allowed\n * @param Recipient To validate that the recipients of the transfers are allowed\n */\nenum TransferSafeguardMode {\n None,\n Chain,\n Token,\n Recipient\n}\n\n/**\n * @title TransferIntentsValidator\n * @dev Performs transfer intents validations based on safeguards\n */\ncontract TransferIntentsValidator is BaseIntentsValidator {\n /**\n * @dev Tells whether a transfer intent is valid for a safeguard\n * @param intent Transfer intent to be validated\n * @param safeguard Safeguard to validate the intent with\n */\n function _isTransferIntentValid(Intent memory intent, Safeguard memory safeguard) internal pure returns (bool) {\n TransferIntent memory transferIntent = abi.decode(intent.data, (TransferIntent));\n if (safeguard.mode == uint8(TransferSafeguardMode.Chain))\n return _isChainAllowed(transferIntent.chainId, safeguard.config);\n if (safeguard.mode == uint8(TransferSafeguardMode.Token))\n return _areTransferTokensValid(transferIntent.transfers, safeguard.config);\n if (safeguard.mode == uint8(TransferSafeguardMode.Recipient))\n return _areTransferRecipientsValid(transferIntent.transfers, safeguard.config);\n revert IntentsValidatorInvalidSafeguardMode(safeguard.mode);\n }\n\n /**\n * @dev Tells whether the tokens being transferred are allowed\n */\n function _areTransferTokensValid(TransferData[] memory transfers, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < transfers.length; i++) {\n if (!_isAccountAllowed(transfers[i].token, config)) return false;\n }\n return true;\n }\n\n /**\n * @dev Tells whether the recipients of the transfers are allowed\n */\n function _areTransferRecipientsValid(TransferData[] memory transfers, bytes memory config)\n private\n pure\n returns (bool)\n {\n for (uint256 i = 0; i < transfers.length; i++) {\n if (!_isAccountAllowed(transfers[i].recipient, config)) return false;\n }\n return true;\n }\n}\n" + }, + "project/contracts/Settler.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/access/Ownable.sol';\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport '@openzeppelin/contracts/utils/ReentrancyGuard.sol';\nimport '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';\nimport '@openzeppelin/contracts/utils/cryptography/EIP712.sol';\nimport '@openzeppelin/contracts/utils/introspection/ERC165Checker.sol';\n\nimport './Intents.sol';\nimport './interfaces/IController.sol';\nimport './interfaces/IIntentsValidator.sol';\nimport './interfaces/IExecutor.sol';\nimport './interfaces/ISettler.sol';\nimport './utils/Denominations.sol';\nimport './utils/ERC20Helpers.sol';\nimport './smart-accounts/SmartAccountsHandler.sol';\nimport './smart-accounts/SmartAccountsHandlerHelpers.sol';\n\n/**\n * @title Settler\n * @dev Contract that provides the appropriate context for solvers to execute proposals that fulfill user intents\n */\ncontract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {\n using SafeERC20 for IERC20;\n using IntentsHelpers for Intent;\n using IntentsHelpers for Proposal;\n using IntentsHelpers for Validation;\n using SmartAccountsHandlerHelpers for address;\n\n // Mimic controller reference\n // solhint-disable-next-line immutable-vars-naming\n address public immutable override controller;\n\n // Smart accounts handler reference\n address public override smartAccountsHandler;\n\n // Intents validator reference\n address public override intentsValidator;\n\n // List of block numbers at which a user nonce was used\n mapping (address => mapping (bytes32 => uint256)) public override getNonceBlock;\n\n // Safeguard config per user\n mapping (address => bytes) internal _userSafeguard;\n\n /**\n * @dev Modifier to tag settler functions in order to check if the sender is an allowed solver\n */\n modifier onlySolver() {\n address sender = _msgSender();\n if (!IController(controller).isSolverAllowed(sender)) revert SettlerSolverNotAllowed(sender);\n _;\n }\n\n /**\n * @dev Creates a new Settler contract\n * @param _controller Address of the Settler controller\n * @param _owner Address that will own the contract\n */\n constructor(address _controller, address _owner) Ownable(_owner) EIP712('Mimic Protocol Settler', '1') {\n controller = _controller;\n smartAccountsHandler = address(new SmartAccountsHandler());\n }\n\n /**\n * @dev Tells the hash of an intent\n * @param intent Intent to get the hash of\n */\n function getIntentHash(Intent memory intent) external pure override returns (bytes32) {\n return intent.hash();\n }\n\n /**\n * @dev Tells the hash of a proposal\n * @param proposal Proposal to be hashed\n * @param intent Intent being fulfilled by the requested proposal\n * @param solver Address of the solver that made the proposal\n */\n function getProposalHash(Proposal memory proposal, Intent memory intent, address solver)\n external\n pure\n override\n returns (bytes32)\n {\n return proposal.hash(intent, solver);\n }\n\n /**\n * @dev Tells the safeguard set for a user\n * @param user Address of the user being queried\n */\n function getUserSafeguard(address user) external view override returns (bytes memory) {\n return _userSafeguard[user];\n }\n\n /**\n * @dev It allows receiving native token transfers\n * Note: This method mainly allows supporting native tokens for swaps\n */\n receive() external payable {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n /**\n * @dev Withdraws ERC20 or native tokens from the contract\n * @param token Address of the token to be withdrawn\n * @param recipient Address of the account receiving the tokens\n * @param amount Amount of tokens to be withdrawn\n */\n function rescueFunds(address token, address recipient, uint256 amount) external override onlyOwner nonReentrant {\n if (recipient == address(0)) revert SettlerRescueFundsRecipientZero();\n ERC20Helpers.transfer(token, recipient, amount);\n emit FundsRescued(token, recipient, amount);\n }\n\n /**\n * @dev Sets a new smart accounts handler\n * @param newSmartAccountsHandler New smart accounts handler to be set\n */\n function setSmartAccountsHandler(address newSmartAccountsHandler) external override onlyOwner {\n _setSmartAccountsHandler(newSmartAccountsHandler);\n }\n\n /**\n * @dev Sets a new intents validator address\n * @param newIntentsValidator New intents validator to be set\n */\n function setIntentsValidator(address newIntentsValidator) external override onlyOwner {\n _setIntentsValidator(newIntentsValidator);\n }\n\n /**\n * @dev Sets a safeguard for a user\n * @param safeguard Safeguard to be set\n */\n function setSafeguard(bytes memory safeguard) external override {\n _setSafeguard(msg.sender, safeguard);\n }\n\n /**\n * @dev Executes a proposal to fulfill an intent\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n */\n function execute(Execution[] memory executions) external override onlySolver {\n _execute(executions, false);\n }\n\n /**\n * @dev Simulates an execution. It will always revert. Successful executions are returned as\n * `SettlerSimulationSuccess` errors. Any other error should be treated as failure.\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n */\n function simulate(Execution[] memory executions) external override onlySolver {\n uint256 initialGas = gasleft();\n _execute(executions, true);\n uint256 gasUsed = initialGas - gasleft();\n revert SettlerSimulationSuccess(gasUsed);\n }\n\n /**\n * @dev Validates and executes a proposal to fulfill an intent\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n * @param simulated Whether the execution is a simulation\n */\n function _execute(Execution[] memory executions, bool simulated) internal nonReentrant {\n for (uint256 i = 0; i < executions.length; i++) {\n Intent memory intent = executions[i].intent;\n Proposal memory proposal = executions[i].proposal;\n bytes memory signature = executions[i].signature;\n\n _validateIntent(intent, proposal, signature, simulated);\n getNonceBlock[intent.user][intent.nonce] = block.number;\n\n if (intent.op == uint8(OpType.Swap)) _executeSwap(intent, proposal);\n else if (intent.op == uint8(OpType.Transfer)) _executeTransfer(intent, proposal);\n else if (intent.op == uint8(OpType.Call)) _executeCall(intent, proposal);\n else revert SettlerUnknownIntentType(uint8(intent.op));\n\n emit ProposalExecuted(proposal.hash(intent, _msgSender()), i);\n }\n }\n\n /**\n * @dev Validates and executes a proposal to fulfill a swap intent\n * @param intent Swap intent to be fulfilled\n * @param proposal Swap proposal to be executed\n */\n function _executeSwap(Intent memory intent, Proposal memory proposal) internal {\n SwapIntent memory swapIntent = abi.decode(intent.data, (SwapIntent));\n SwapProposal memory swapProposal = abi.decode(proposal.data, (SwapProposal));\n _validateSwapIntent(swapIntent, swapProposal);\n\n bool isSmartAccount = smartAccountsHandler.isSmartAccount(intent.user);\n if (swapIntent.sourceChain == block.chainid) {\n for (uint256 i = 0; i < swapIntent.tokensIn.length; i++) {\n TokenIn memory tokenIn = swapIntent.tokensIn[i];\n _transferFrom(tokenIn.token, intent.user, swapProposal.executor, tokenIn.amount, isSmartAccount);\n }\n }\n\n uint256[] memory preBalancesOut = _getTokensOutBalance(swapIntent);\n IExecutor(swapProposal.executor).execute(intent, proposal);\n\n if (swapIntent.destinationChain == block.chainid) {\n uint256[] memory outputs = new uint256[](swapIntent.tokensOut.length);\n for (uint256 i = 0; i < swapIntent.tokensOut.length; i++) {\n TokenOut memory tokenOut = swapIntent.tokensOut[i];\n uint256 postBalanceOut = ERC20Helpers.balanceOf(tokenOut.token, address(this));\n uint256 preBalanceOut = preBalancesOut[i];\n if (postBalanceOut < preBalanceOut) revert SettlerPostBalanceOutLtPre(i, postBalanceOut, preBalanceOut);\n\n outputs[i] = postBalanceOut - preBalanceOut;\n uint256 proposedAmount = swapProposal.amountsOut[i];\n if (outputs[i] < proposedAmount) revert SettlerAmountOutLtProposed(i, outputs[i], proposedAmount);\n\n ERC20Helpers.transfer(tokenOut.token, tokenOut.recipient, outputs[i]);\n }\n\n _emitIntentEvents(intent, proposal, abi.encode(outputs));\n _payFees(intent, proposal, isSmartAccount);\n }\n }\n\n /**\n * @dev Validates and executes a proposal to fulfill a transfer intent\n * @param intent Transfer intent to be fulfilled\n * @param proposal Transfer proposal to be executed\n */\n function _executeTransfer(Intent memory intent, Proposal memory proposal) internal {\n TransferIntent memory transferIntent = abi.decode(intent.data, (TransferIntent));\n _validateTransferIntent(transferIntent, proposal);\n\n bool isSmartAccount = smartAccountsHandler.isSmartAccount(intent.user);\n for (uint256 i = 0; i < transferIntent.transfers.length; i++) {\n TransferData memory transfer = transferIntent.transfers[i];\n _transferFrom(transfer.token, intent.user, transfer.recipient, transfer.amount, isSmartAccount);\n }\n\n _emitIntentEvents(intent, proposal, new bytes(0));\n _payFees(intent, proposal, isSmartAccount);\n }\n\n /**\n * @dev Validates and executes a proposal to fulfill a call intent\n * @param intent Call intent to be fulfilled\n * @param proposal Call proposal to be executed\n */\n function _executeCall(Intent memory intent, Proposal memory proposal) internal {\n CallIntent memory callIntent = abi.decode(intent.data, (CallIntent));\n _validateCallIntent(callIntent, proposal, intent.user);\n\n bytes[] memory outputs = new bytes[](callIntent.calls.length);\n for (uint256 i = 0; i < callIntent.calls.length; i++) {\n CallData memory call = callIntent.calls[i];\n // solhint-disable-next-line avoid-low-level-calls\n outputs[i] = smartAccountsHandler.call(intent.user, call.target, call.data, call.value);\n }\n\n _emitIntentEvents(intent, proposal, abi.encode(outputs));\n _payFees(intent, proposal, true);\n }\n\n /**\n * @dev Validates an intent and its corresponding proposal\n * @param intent Intent to be fulfilled\n * @param proposal Proposal to be executed\n * @param signature Proposal signature\n * @param simulated Whether the execution is a simulation\n */\n function _validateIntent(Intent memory intent, Proposal memory proposal, bytes memory signature, bool simulated)\n internal\n view\n {\n if (intent.settler != address(this)) revert SettlerInvalidSettler(intent.settler);\n if (intent.nonce == bytes32(0)) revert SettlerNonceZero();\n if (getNonceBlock[intent.user][intent.nonce] != 0) revert SettlerNonceAlreadyUsed(intent.user, intent.nonce);\n\n if (intentsValidator != address(0)) {\n bytes memory safeguard = _userSafeguard[intent.user];\n if (safeguard.length > 0) IIntentsValidator(intentsValidator).validate(intent, safeguard);\n }\n\n bool shouldValidateDeadlines = _shouldValidateDeadlines(intent);\n if (shouldValidateDeadlines) {\n if (intent.deadline <= block.timestamp) revert SettlerIntentPastDeadline(intent.deadline, block.timestamp);\n bool isProposalPastDeadline = proposal.deadline <= block.timestamp;\n if (isProposalPastDeadline) revert SettlerProposalPastDeadline(proposal.deadline, block.timestamp);\n }\n\n if (intent.maxFees.length != proposal.fees.length) revert SettlerSolverFeeInvalidLength();\n for (uint256 i = 0; i < intent.maxFees.length; i++) {\n uint256 maxFee = intent.maxFees[i].amount;\n uint256 proposalFee = proposal.fees[i];\n if (proposalFee > maxFee) revert SettlerSolverFeeTooHigh(proposalFee, maxFee);\n }\n\n uint8 minValidations = IController(controller).minValidations();\n uint256 requiredValidations = intent.minValidations > minValidations ? intent.minValidations : minValidations;\n\n if (intent.validations.length < requiredValidations) {\n revert SettlerIntentValidationsNotEnough(requiredValidations, intent.validations.length);\n }\n\n address lastValidator = address(0);\n Validation memory validation = Validation(intent.hash());\n bytes32 typedDataHash = _hashTypedDataV4(validation.hash());\n for (uint256 i = 0; i < intent.validations.length; i++) {\n address validator = ECDSA.recover(typedDataHash, intent.validations[i]);\n if (validator <= lastValidator) {\n revert SettlerValidatorDuplicatedOrUnsorted(lastValidator, validator);\n }\n lastValidator = validator;\n bool isValidatorNotAllowed = !IController(controller).isValidatorAllowed(validator);\n if (isValidatorNotAllowed) revert SettlerValidatorNotAllowed(validator);\n }\n\n address signer = ECDSA.recover(_hashTypedDataV4(proposal.hash(intent, _msgSender())), signature);\n bool isProposalSignerNotAllowed = !IController(controller).isProposalSignerAllowed(signer) && !simulated;\n if (isProposalSignerNotAllowed) revert SettlerProposalSignerNotAllowed(signer);\n }\n\n /**\n * @dev Validates a swap intent and its corresponding proposal\n * @param intent Swap intent to be fulfilled\n * @param proposal Proposal to be executed\n */\n function _validateSwapIntent(SwapIntent memory intent, SwapProposal memory proposal) internal view {\n bool isChainInvalid = intent.sourceChain != block.chainid && intent.destinationChain != block.chainid;\n if (isChainInvalid) revert SettlerInvalidChain(block.chainid);\n\n if (proposal.amountsOut.length != intent.tokensOut.length) revert SettlerInvalidProposedAmounts();\n\n for (uint256 i = 0; i < intent.tokensOut.length; i++) {\n TokenOut memory tokenOut = intent.tokensOut[i];\n address recipient = tokenOut.recipient;\n if (recipient == address(this)) revert SettlerInvalidRecipient(recipient);\n\n uint256 minAmount = tokenOut.minAmount;\n uint256 proposedAmount = proposal.amountsOut[i];\n if (proposedAmount < minAmount) revert SettlerProposedAmountLtMinAmount(i, proposedAmount, minAmount);\n }\n\n if (intent.sourceChain != intent.destinationChain) {\n bool isExecutorInvalid = !IController(controller).isExecutorAllowed(proposal.executor);\n if (isExecutorInvalid) revert SettlerExecutorNotAllowed(proposal.executor);\n }\n }\n\n /**\n * @dev Validates a transfer intent and its corresponding proposal\n * @param intent Transfer intent to be fulfilled\n * @param proposal Proposal to be executed\n */\n function _validateTransferIntent(TransferIntent memory intent, Proposal memory proposal) internal view {\n if (intent.chainId != block.chainid) revert SettlerInvalidChain(block.chainid);\n if (proposal.data.length > 0) revert SettlerProposalDataNotEmpty();\n for (uint256 i = 0; i < intent.transfers.length; i++) {\n address recipient = intent.transfers[i].recipient;\n if (recipient == address(this)) revert SettlerInvalidRecipient(recipient);\n }\n }\n\n /**\n * @dev Validates a call intent and its corresponding proposal\n * @param intent Call intent to be fulfilled\n * @param proposal Proposal to be executed\n * @param user The originator of the intent\n */\n function _validateCallIntent(CallIntent memory intent, Proposal memory proposal, address user) internal view {\n if (intent.chainId != block.chainid) revert SettlerInvalidChain(block.chainid);\n if (proposal.data.length > 0) revert SettlerProposalDataNotEmpty();\n if (!smartAccountsHandler.isSmartAccount(user)) revert SettlerUserNotSmartAccount(user);\n }\n\n /**\n * @dev Tells the contract balance for each token out of a swap intent\n * @param intent Swap intent containing the list of tokens out\n */\n function _getTokensOutBalance(SwapIntent memory intent) internal view returns (uint256[] memory balances) {\n balances = new uint256[](intent.tokensOut.length);\n if (intent.destinationChain == block.chainid) {\n for (uint256 i = 0; i < intent.tokensOut.length; i++) {\n balances[i] = ERC20Helpers.balanceOf(intent.tokensOut[i].token, address(this));\n }\n }\n }\n\n /**\n * @dev Tells if the intent and proposal deadlines should be validated\n * @param intent Intent to be fulfilled\n */\n function _shouldValidateDeadlines(Intent memory intent) internal view returns (bool) {\n if (intent.op != uint8(OpType.Swap)) return true;\n SwapIntent memory swapIntent = abi.decode(intent.data, (SwapIntent));\n if (swapIntent.sourceChain == swapIntent.destinationChain) return true;\n return swapIntent.sourceChain == block.chainid;\n }\n\n /**\n * @dev Emits intent custom events\n * @param intent Intent to emit the custom events for\n * @param proposal Proposal that fulfills the intent\n * @param output Encoded array of outputs\n */\n function _emitIntentEvents(Intent memory intent, Proposal memory proposal, bytes memory output) internal {\n for (uint256 i = 0; i < intent.events.length; i++) {\n IntentEvent memory intentEvent = intent.events[i];\n emit IntentExecuted(\n intent.user,\n intentEvent.topic,\n uint8(intent.op),\n intent,\n proposal,\n output,\n intentEvent.data\n );\n }\n }\n\n /**\n * @dev Pays fees\n * @param intent Intent to be fulfilled\n * @param proposal Proposal to be executed\n * @param isSmartAccount Whether the intent user is a smart account\n */\n function _payFees(Intent memory intent, Proposal memory proposal, bool isSmartAccount) internal {\n address from = intent.user;\n address to = _msgSender();\n for (uint256 i = 0; i < intent.maxFees.length; i++) {\n address token = intent.maxFees[i].token;\n if (!Denominations.isUSD(token)) _transferFrom(token, from, to, proposal.fees[i], isSmartAccount);\n }\n }\n\n /**\n * @dev Transfers tokens from one account to another\n * @param token Address of the token to transfer\n * @param from Address of the account sending the tokens\n * @param to Address of the account receiving the tokens\n * @param amount Amount of tokens to transfer\n * @param isSmartAccount Whether the sender is a smart account\n */\n function _transferFrom(address token, address from, address to, uint256 amount, bool isSmartAccount) internal {\n if (isSmartAccount) {\n smartAccountsHandler.transfer(from, token, to, amount);\n } else {\n IERC20(token).safeTransferFrom(from, to, amount);\n }\n }\n\n /**\n * @dev Sets a new smart accounts handler\n * @param newSmartAccountsHandler New smart accounts handler to be set\n */\n function _setSmartAccountsHandler(address newSmartAccountsHandler) internal {\n if (newSmartAccountsHandler == address(0)) revert SmartAccountsHandlerZero();\n smartAccountsHandler = newSmartAccountsHandler;\n emit SmartAccountsHandlerSet(newSmartAccountsHandler);\n }\n\n /**\n * @dev Sets the intents validator\n * @param newIntentsValidator New intents validator to be set\n */\n function _setIntentsValidator(address newIntentsValidator) internal {\n intentsValidator = newIntentsValidator;\n emit IntentsValidatorSet(newIntentsValidator);\n }\n\n /**\n * @dev Sets a safeguard for a user\n * @param user Address of the user to set the safeguard for\n * @param safeguard Safeguard to be set\n */\n function _setSafeguard(address user, bytes memory safeguard) internal {\n delete _userSafeguard[user];\n _userSafeguard[user] = safeguard;\n emit SafeguardSet(user);\n }\n}\n" + }, + "project/contracts/smart-accounts/SmartAccount.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/access/Ownable.sol';\nimport '@openzeppelin/contracts/utils/Address.sol';\nimport '@openzeppelin/contracts/utils/ReentrancyGuard.sol';\nimport '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';\nimport '@openzeppelin/contracts/utils/introspection/ERC165.sol';\n\nimport '../interfaces/ISmartAccount.sol';\nimport '../utils/ERC20Helpers.sol';\n\n/**\n * @title SmartAccount\n * @dev Provides the logic for managing assets, executing arbitrary calls, and controlling permissions\n */\ncontract SmartAccount is ISmartAccount, ERC165, Ownable, ReentrancyGuard {\n // EIP1271 magic return value\n bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e;\n\n // EIP1271 invalid signature return value\n bytes4 internal constant EIP1271_INVALID_SIGNATURE = 0xffffffff;\n\n // List of account permissions\n mapping (address => bool) public isSignerAllowed;\n\n // Mimic settler reference\n address public settler;\n\n /**\n * @dev The settler is zero\n */\n error SmartAccountSettlerZero();\n\n /**\n * @dev The input arrays are not of equal length\n */\n error SmartAccountInputInvalidLength();\n\n /**\n * @dev The sender is not the owner or the settler\n */\n error SmartAccountUnauthorizedSender(address sender);\n\n /**\n * @dev Emitted every time the settler is set\n */\n event SettlerSet(address indexed settler);\n\n /**\n * @dev Emitted every time a signer allowance is set\n */\n event SignerAllowedSet(address indexed account, bool allowed);\n\n /**\n * @dev Reverts unless the sender is the owner or the settler\n */\n modifier onlyOwnerOrSettler() {\n address sender = _msgSender();\n bool isAuthorized = sender == owner() || sender == settler;\n if (!isAuthorized) revert SmartAccountUnauthorizedSender(sender);\n _;\n }\n\n /**\n * @dev Creates a new SmartAccount contract\n * @param _settler Address of the Mimic settler\n * @param _owner Address that will own the contract\n */\n constructor(address _settler, address _owner) Ownable(_owner) {\n _setSettler(_settler);\n }\n\n /**\n * @dev Tells whether the contract supports the given interface ID. Overrides ERC165 to declare support for ISmartAccount interface.\n * @param interfaceId Interface ID is defined as the XOR of all function selectors in the interface\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {\n return interfaceId == type(ISmartAccount).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Tells whether the signature provided belongs to an allowed account.\n * @param hash Message signed by the account\n * @param signature Signature provided to be verified\n */\n function isValidSignature(bytes32 hash, bytes memory signature) external view override returns (bytes4) {\n (address signer, , ) = ECDSA.tryRecover(hash, signature);\n if (signer != address(0) && (signer == owner() || isSignerAllowed[signer])) return EIP1271_MAGIC_VALUE;\n return EIP1271_INVALID_SIGNATURE;\n }\n\n /**\n * @dev It allows receiving native token transfers\n */\n receive() external payable {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n /**\n * @dev Transfers ERC20 or native tokens to the recipient. Sender must be the owner or the settler.\n * @param token Address of the token to be withdrawn\n * @param recipient Address of the account receiving the tokens\n * @param amount Amount of tokens to be withdrawn\n */\n function transfer(address token, address recipient, uint256 amount)\n external\n override\n onlyOwnerOrSettler\n nonReentrant\n {\n ERC20Helpers.transfer(token, recipient, amount);\n emit Transferred(token, recipient, amount);\n }\n\n /**\n * @dev Executes an arbitrary call from the contract. Sender must be the owner or the settler.\n * @param target Address where the call will be sent\n * @param data Calldata to be sent to the target\n * @param value Native token value to send along with the call\n * @return result Call response if it was successful, otherwise it reverts\n */\n function call(address target, bytes memory data, uint256 value)\n external\n override\n onlyOwnerOrSettler\n nonReentrant\n returns (bytes memory result)\n {\n result = Address.functionCallWithValue(target, data, value);\n emit Called(target, data, value, result);\n }\n\n /**\n * @dev Sets the settler. Sender must be the owner.\n * @param newSettler Address of the new settler to be set\n */\n function setSettler(address newSettler) external onlyOwner {\n _setSettler(newSettler);\n }\n\n /**\n * @dev Sets a list of allowed signers. Sender must be the owner.\n * @param accounts List of account addresses\n * @param allowances List of allowed condition per account\n */\n function setAllowedSigners(address[] memory accounts, bool[] memory allowances) external onlyOwner {\n if (accounts.length != allowances.length) revert SmartAccountInputInvalidLength();\n for (uint256 i = 0; i < accounts.length; i++) {\n address account = accounts[i];\n bool allowed = allowances[i];\n isSignerAllowed[account] = allowed;\n emit SignerAllowedSet(account, allowed);\n }\n }\n\n /**\n * @dev Sets the settler\n * @param newSettler Address of the new settler to be set\n */\n function _setSettler(address newSettler) internal {\n if (newSettler == address(0)) revert SmartAccountSettlerZero();\n settler = newSettler;\n emit SettlerSet(newSettler);\n }\n}\n" + }, + "project/contracts/smart-accounts/SmartAccountsHandler.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/utils/Address.sol';\n\nimport '../interfaces/ISafe.sol';\nimport '../interfaces/ISmartAccount.sol';\nimport '../interfaces/ISmartAccountsHandler.sol';\nimport '../utils/Denominations.sol';\n\ncontract SmartAccountsHandler is ISmartAccountsHandler {\n /**\n * @dev Tells whether an account is a supported smart account\n * @param account Address of the account being queried\n */\n function isSmartAccount(address account) external view override returns (bool) {\n if (account.code.length == 0) return false;\n if (_isMimicSmartAccount(account)) return true;\n if (_isSafe(account)) return true;\n return false;\n }\n\n /**\n * @dev Performs a transfer from a smart account\n */\n function transfer(address account, address token, address to, uint256 amount) external override {\n if (_isMimicSmartAccount(account)) return ISmartAccount(account).transfer(token, to, amount);\n if (_isSafe(account)) return _transferSafe(account, token, to, amount);\n revert SmartAccountsHandlerUnsupportedAccount(account);\n }\n\n /**\n * @dev Performs a call from a smart account\n */\n function call(address account, address target, bytes memory data, uint256 value)\n external\n override\n returns (bytes memory)\n {\n // solhint-disable-next-line avoid-low-level-calls\n if (_isMimicSmartAccount(account)) return ISmartAccount(account).call(target, data, value);\n if (_isSafe(account)) return _callSafe(account, target, data, value);\n revert SmartAccountsHandlerUnsupportedAccount(account);\n }\n\n /**\n * @dev Performs a transfer from a safe\n */\n function _transferSafe(address account, address token, address to, uint256 amount) internal {\n Denominations.isNativeToken(token)\n ? _callSafe(account, to, new bytes(0), amount)\n : _callSafe(account, token, abi.encodeWithSelector(IERC20.transfer.selector, to, amount), 0);\n }\n\n /**\n * @dev Performs a call from a safe\n */\n function _callSafe(address account, address target, bytes memory data, uint256 value)\n internal\n returns (bytes memory)\n {\n (bool success, bytes memory result) = ISafe(account).execTransactionFromModuleReturnData(\n target,\n value,\n data,\n SafeOperation.Call\n );\n return\n data.length == 0\n ? Address.verifyCallResult(success, result)\n : Address.verifyCallResultFromTarget(target, success, result);\n }\n\n /**\n * @dev Tells whether an account is a Mimic smart account\n * @param account Address of the account being queried\n */\n function _isMimicSmartAccount(address account) internal view returns (bool) {\n try ISmartAccount(account).supportsInterface(type(ISmartAccount).interfaceId) returns (bool ok) {\n return ok;\n } catch {\n return false;\n }\n }\n\n /**\n * @dev Tells whether an account is a Gnosis Safe\n * @param account Address of the account being queried\n */\n function _isSafe(address account) internal view returns (bool) {\n try ISafe(account).getThreshold() returns (uint256) {\n return true;\n } catch {\n return false;\n }\n }\n}\n" + }, + "project/contracts/smart-accounts/SmartAccountsHandlerHelpers.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/utils/Address.sol';\n\nimport '../interfaces/ISmartAccountsHandler.sol';\n\nlibrary SmartAccountsHandlerHelpers {\n /**\n * @dev Tells whether an account is a supported smart account\n * @param account Address of the account being queried\n */\n function isSmartAccount(address handler, address account) internal view returns (bool) {\n return ISmartAccountsHandler(handler).isSmartAccount(account);\n }\n\n /**\n * @dev Performs a transfer from a smart account\n */\n function transfer(address handler, address account, address token, address to, uint256 amount) internal {\n Address.functionDelegateCall(\n handler,\n abi.encodeWithSelector(ISmartAccountsHandler.transfer.selector, account, token, to, amount)\n );\n }\n\n /**\n * @dev Performs a call from a smart account\n */\n function call(address handler, address account, address target, bytes memory data, uint256 value)\n internal\n returns (bytes memory)\n {\n return\n Address.functionDelegateCall(\n handler,\n abi.encodeWithSelector(ISmartAccountsHandler.call.selector, account, target, data, value)\n );\n }\n}\n" + }, + "project/contracts/test/CallMock.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\ncontract CallMock {\n event CallReceived(address indexed sender, uint256 value);\n error CallError();\n\n function call() external payable {\n emit CallReceived(msg.sender, msg.value);\n }\n\n function callError() external pure {\n revert CallError();\n }\n}\n" + }, + "project/contracts/test/executors/EmptyExecutorMock.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../../interfaces/IExecutor.sol';\n\ncontract EmptyExecutorMock is IExecutor {\n event Executed();\n\n function execute(Intent memory, Proposal memory) external override {\n emit Executed();\n }\n}\n" + }, + "project/contracts/test/executors/MintExecutorMock.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../TokenMock.sol';\nimport '../../interfaces/IExecutor.sol';\n\n/* solhint-disable custom-errors */\n\ncontract MintExecutorMock is IExecutor {\n event Minted();\n\n function execute(Intent memory intent, Proposal memory proposal) external override {\n require(intent.op == uint8(OpType.Swap), 'Invalid intent type');\n\n SwapProposal memory swapProposal = abi.decode(proposal.data, (SwapProposal));\n (address[] memory tokens, uint256[] memory amounts) = abi.decode(swapProposal.data, (address[], uint256[]));\n\n require(tokens.length == amounts.length, 'Invalid inputs');\n\n for (uint256 i = 0; i < tokens.length; i++) {\n TokenMock(tokens[i]).mint(msg.sender, amounts[i]);\n emit Minted();\n }\n }\n}\n" + }, + "project/contracts/test/executors/ReentrantExecutorMock.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../../interfaces/IExecutor.sol';\nimport '../../interfaces/ISettler.sol';\n\ncontract ReentrantExecutorMock is IExecutor {\n // solhint-disable-next-line immutable-vars-naming\n address payable public immutable settler;\n\n constructor(address payable _settler) {\n settler = _settler;\n }\n\n function execute(Intent memory, Proposal memory) external override {\n ISettler(settler).execute(new Execution[](0));\n }\n}\n" + }, + "project/contracts/test/executors/TransferExecutorMock.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../../interfaces/IExecutor.sol';\nimport '../../utils/ERC20Helpers.sol';\n\n/* solhint-disable custom-errors */\n\ncontract TransferExecutorMock is IExecutor {\n event Transferred();\n\n receive() external payable {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n function execute(Intent memory intent, Proposal memory proposal) external override {\n require(intent.op == uint8(OpType.Swap), 'Invalid intent type');\n\n SwapProposal memory swapProposal = abi.decode(proposal.data, (SwapProposal));\n (address[] memory tokens, uint256[] memory amounts) = abi.decode(swapProposal.data, (address[], uint256[]));\n\n require(tokens.length == amounts.length, 'Invalid inputs');\n\n for (uint256 i = 0; i < tokens.length; i++) {\n ERC20Helpers.transfer(tokens[i], msg.sender, amounts[i]);\n emit Transferred();\n }\n }\n}\n" + }, + "project/contracts/test/smart-accounts/SafeMock.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../../interfaces/ISafe.sol';\n\ncontract SafeMock is ISafe {\n event ModuleTxExecuted(\n address indexed target,\n bytes data,\n uint256 value,\n SafeOperation operation,\n bool success,\n bytes result\n );\n\n receive() external payable {}\n\n function getThreshold() external pure returns (uint256) {\n return 1;\n }\n\n function execTransactionFromModuleReturnData(address to, uint256 value, bytes memory data, SafeOperation operation)\n external\n returns (bool success, bytes memory result)\n {\n // solhint-disable-next-line avoid-low-level-calls\n (success, result) = to.call{ value: value }(data);\n emit ModuleTxExecuted(to, data, value, operation, success, result);\n }\n}\n" + }, + "project/contracts/test/TokenMock.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/token/ERC20/ERC20.sol';\n\ncontract TokenMock is ERC20 {\n uint8 internal _decimals;\n\n constructor(string memory symbol, uint8 dec) ERC20(symbol, symbol) {\n _decimals = dec;\n }\n\n function mint(address account, uint256 amount) external {\n _mint(account, amount);\n }\n\n function decimals() public view override returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "project/contracts/test/utils/DenominationsMock.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '../../utils/Denominations.sol';\n\n// solhint-disable func-name-mixedcase\n\ncontract DenominationsMock {\n function NATIVE_TOKEN() external pure returns (address) {\n return Denominations.NATIVE_TOKEN;\n }\n\n function USD() external pure returns (address) {\n return Denominations.USD;\n }\n}\n" + }, + "project/contracts/test/utils/ERC20HelpersMock.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '../../utils/ERC20Helpers.sol';\n\ncontract ERC20HelpersMock {\n receive() external payable {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n function approve(address token, address to, uint256 amount) external {\n ERC20Helpers.approve(token, to, amount);\n }\n\n function transfer(address token, address to, uint256 amount) external {\n ERC20Helpers.transfer(token, to, amount);\n }\n\n function balanceOf(address token, address account) external view returns (uint256) {\n return ERC20Helpers.balanceOf(token, account);\n }\n}\n" + }, + "project/contracts/utils/Denominations.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\n/**\n * @title Denominations\n * @dev Provides a list of ground denominations for those tokens that cannot be represented by an ERC20.\n * For now, the only needed is the native token that could be ETH, MATIC, or other depending on the layer being operated.\n */\nlibrary Denominations {\n address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n // Fiat currencies follow https://en.wikipedia.org/wiki/ISO_4217\n address internal constant USD = address(840);\n\n function isUSD(address token) internal pure returns (bool) {\n return token == USD;\n }\n\n function isNativeToken(address token) internal pure returns (bool) {\n return token == NATIVE_TOKEN;\n }\n}\n" + }, + "project/contracts/utils/ERC20Helpers.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport '@openzeppelin/contracts/utils/Address.sol';\n\nimport './Denominations.sol';\n\n/**\n * @title ERC20Helpers\n * @dev Provides a list of ERC20 helper methods\n */\nlibrary ERC20Helpers {\n function approve(address token, address to, uint256 amount) internal {\n SafeERC20.forceApprove(IERC20(token), to, amount);\n }\n\n function transfer(address token, address to, uint256 amount) internal {\n if (Denominations.isNativeToken(token)) Address.sendValue(payable(to), amount);\n else SafeERC20.safeTransfer(IERC20(token), to, amount);\n }\n\n function balanceOf(address token, address account) internal view returns (uint256) {\n if (Denominations.isNativeToken(token)) return address(account).balance;\n else return IERC20(token).balanceOf(address(account));\n }\n}\n" + } + } + } +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/deployed_addresses.json b/packages/evm/ignition/deployments/chain-1/deployed_addresses.json new file mode 100644 index 0000000..a1a41be --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/deployed_addresses.json @@ -0,0 +1,6 @@ +{ + "Create3Controller#ICreateX": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", + "Create3Controller#Controller": "0x6002825BabC837776A08799404fA55f15DCedA6b", + "Create3Settler#ICreateX": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", + "Create3Settler#Settler": "0x609d831C0068844e11eF85a273c7F356212Fd6D1" +} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/journal.jsonl b/packages/evm/ignition/deployments/chain-1/journal.jsonl new file mode 100644 index 0000000..d0556e8 --- /dev/null +++ b/packages/evm/ignition/deployments/chain-1/journal.jsonl @@ -0,0 +1,20 @@ + +{"chainId":1,"type":"DEPLOYMENT_INITIALIZE"} +{"artifactId":"Create3Controller#ICreateX","contractAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","contractName":"ICreateX","dependencies":[],"futureId":"Create3Controller#ICreateX","futureType":"NAMED_ARTIFACT_CONTRACT_AT","strategy":"basic","strategyConfig":{},"type":"CONTRACT_AT_EXECUTION_STATE_INITIALIZE"} +{"args":["0x3a0ce8115b4913f42c4928d6bc3f554e9a81468b000000000000000000000017","0x608060405234801561000f575f5ffd5b50604051610e6c380380610e6c83398101604081905261002e91610444565b856001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b61006581610172565b505f5b85518110156100a35761009b8682815181106100865761008661051f565b602002602001015160016101c160201b60201c565b600101610068565b505f5b84518110156100e1576100d98582815181106100c4576100c461051f565b6020026020010151600161022060201b60201c565b6001016100a6565b505f5b835181101561011f576101178482815181106101025761010261051f565b6020026020010151600161027760201b60201c565b6001016100e4565b505f5b825181101561015d576101558382815181106101405761014061051f565b602002602001015160016102ce60201b60201c565b600101610122565b5061016781610325565b505050505050610533565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f15091015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610214565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c39101610214565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610214565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b80516001600160a01b0381168114610378575f5ffd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126103a0575f5ffd5b81516001600160401b038111156103b9576103b961037d565b604051600582901b90603f8201601f191681016001600160401b03811182821017156103e7576103e761037d565b604052918252602081850181019290810186841115610404575f5ffd5b6020860192505b8383101561042a5761041c83610362565b81526020928301920161040b565b5095945050505050565b805160ff81168114610378575f5ffd5b5f5f5f5f5f5f60c08789031215610459575f5ffd5b61046287610362565b60208801519096506001600160401b0381111561047d575f5ffd5b61048989828a01610391565b604089015190965090506001600160401b038111156104a6575f5ffd5b6104b289828a01610391565b606089015190955090506001600160401b038111156104cf575f5ffd5b6104db89828a01610391565b608089015190945090506001600160401b038111156104f8575f5ffd5b61050489828a01610391565b92505061051360a08801610434565b90509295509295509295565b634e487b7160e01b5f52603260045260245ffd5b61092c806105405f395ff3fe608060405234801561000f575f5ffd5b50600436106100da575f3560e01c80637d701102116100885780638da5cb5b116100635780638da5cb5b146101ce5780638f72c136146101e8578063d9d8edb9146101fb578063f2fde38b1461021d575f5ffd5b80637d701102146101865780637e8d9637146101a857806384aaa224146101bb575f5ffd5b806361655670116100b8578063616556701461013d578063680038c71461015f578063715018a61461017e575f5ffd5b8063090de7df146100de578063163eed9e146100f35780633d41132f1461012a575b5f5ffd5b6100f16100ec3660046107df565b610230565b005b6101156101013660046108a2565b60036020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100f16101383660046107df565b6102ae565b61011561014b3660046108a2565b60046020525f908152604090205460ff1681565b60055461016c9060ff1681565b60405160ff9091168152602001610121565b6100f1610327565b6101156101943660046108a2565b60026020525f908152604090205460ff1681565b6100f16101b63660046108c2565b61033a565b6100f16101c93660046107df565b61034e565b5f546040516001600160a01b039091168152602001610121565b6100f16101f63660046107df565b6103c7565b6101156102093660046108a2565b60016020525f908152604090205460ff1681565b6100f161022b3660046108a2565b610440565b610238610498565b805182511461025a57604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576102a183828151811061027a5761027a6108e2565b6020026020010151838381518110610294576102946108e2565b60200260200101516104dd565b60010161025c565b505050565b6102b6610498565b80518251146102d857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a95761031f8382815181106102f8576102f86108e2565b6020026020010151838381518110610312576103126108e2565b602002602001015161053c565b6001016102da565b61032f610498565b6103385f610593565b565b610342610498565b61034b816105fa565b50565b610356610498565b805182511461037857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576103bf838281518110610398576103986108e2565b60200260200101518383815181106103b2576103b26108e2565b6020026020010151610637565b60010161037a565b6103cf610498565b80518251146103f157604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a957610438838281518110610411576104116108e2565b602002602001015183838151811061042b5761042b6108e2565b602002602001015161068e565b6001016103f3565b610448610498565b6001600160a01b03811661048f576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61034b81610593565b5f546001600160a01b03163314610338576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610486565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c391015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610530565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610530565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f1509101610530565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610722576107226106e5565b604052919050565b5f67ffffffffffffffff821115610743576107436106e5565b5060051b60200190565b80356001600160a01b0381168114610763575f5ffd5b919050565b5f82601f830112610777575f5ffd5b813561078a6107858261072a565b6106f9565b8082825260208201915060208360051b8601019250858311156107ab575f5ffd5b602085015b838110156107d557803580151581146107c7575f5ffd5b8352602092830192016107b0565b5095945050505050565b5f5f604083850312156107f0575f5ffd5b823567ffffffffffffffff811115610806575f5ffd5b8301601f81018513610816575f5ffd5b80356108246107858261072a565b8082825260208201915060208360051b850101925087831115610845575f5ffd5b6020840193505b8284101561086e5761085d8461074d565b82526020938401939091019061084c565b9450505050602083013567ffffffffffffffff81111561088c575f5ffd5b61089885828601610768565b9150509250929050565b5f602082840312156108b2575f5ffd5b6108bb8261074d565b9392505050565b5f602082840312156108d2575f5ffd5b813560ff811681146108bb575f5ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212204e1a03491d67aedff2296b499cf5fee36529062002d67b286fb08859eac0e13664736f6c634300081c00330000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000e0d76433edd9f5df370561bd0af231e72c83cd3a000000000000000000000000f59eae23353b12ab0561aae1bb74d06623cd3e480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e185b20035753ad9c4c8040fdfeaf05ee2d3454f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000003f4c47e37a94caee31d0b585f54f3ffa1f2294c90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1"],"artifactId":"Create3Controller#ICreateX","contractAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","dependencies":["Create3Controller#ICreateX"],"from":"0x3a0ce8115b4913f42c4928d6bc3f554e9a81468b","functionName":"deployCreate3","futureId":"Create3Controller#ICreateX.deployCreate3","strategy":"basic","strategyConfig":{},"type":"CALL_EXECUTION_STATE_INITIALIZE","value":{"_kind":"bigint","value":"0"}} +{"futureId":"Create3Controller#ICreateX.deployCreate3","networkInteraction":{"data":"0x9c36a2863a0ce8115b4913f42c4928d6bc3f554e9a81468b0000000000000000000000170000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000106c608060405234801561000f575f5ffd5b50604051610e6c380380610e6c83398101604081905261002e91610444565b856001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b61006581610172565b505f5b85518110156100a35761009b8682815181106100865761008661051f565b602002602001015160016101c160201b60201c565b600101610068565b505f5b84518110156100e1576100d98582815181106100c4576100c461051f565b6020026020010151600161022060201b60201c565b6001016100a6565b505f5b835181101561011f576101178482815181106101025761010261051f565b6020026020010151600161027760201b60201c565b6001016100e4565b505f5b825181101561015d576101558382815181106101405761014061051f565b602002602001015160016102ce60201b60201c565b600101610122565b5061016781610325565b505050505050610533565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f15091015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610214565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c39101610214565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610214565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b80516001600160a01b0381168114610378575f5ffd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126103a0575f5ffd5b81516001600160401b038111156103b9576103b961037d565b604051600582901b90603f8201601f191681016001600160401b03811182821017156103e7576103e761037d565b604052918252602081850181019290810186841115610404575f5ffd5b6020860192505b8383101561042a5761041c83610362565b81526020928301920161040b565b5095945050505050565b805160ff81168114610378575f5ffd5b5f5f5f5f5f5f60c08789031215610459575f5ffd5b61046287610362565b60208801519096506001600160401b0381111561047d575f5ffd5b61048989828a01610391565b604089015190965090506001600160401b038111156104a6575f5ffd5b6104b289828a01610391565b606089015190955090506001600160401b038111156104cf575f5ffd5b6104db89828a01610391565b608089015190945090506001600160401b038111156104f8575f5ffd5b61050489828a01610391565b92505061051360a08801610434565b90509295509295509295565b634e487b7160e01b5f52603260045260245ffd5b61092c806105405f395ff3fe608060405234801561000f575f5ffd5b50600436106100da575f3560e01c80637d701102116100885780638da5cb5b116100635780638da5cb5b146101ce5780638f72c136146101e8578063d9d8edb9146101fb578063f2fde38b1461021d575f5ffd5b80637d701102146101865780637e8d9637146101a857806384aaa224146101bb575f5ffd5b806361655670116100b8578063616556701461013d578063680038c71461015f578063715018a61461017e575f5ffd5b8063090de7df146100de578063163eed9e146100f35780633d41132f1461012a575b5f5ffd5b6100f16100ec3660046107df565b610230565b005b6101156101013660046108a2565b60036020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100f16101383660046107df565b6102ae565b61011561014b3660046108a2565b60046020525f908152604090205460ff1681565b60055461016c9060ff1681565b60405160ff9091168152602001610121565b6100f1610327565b6101156101943660046108a2565b60026020525f908152604090205460ff1681565b6100f16101b63660046108c2565b61033a565b6100f16101c93660046107df565b61034e565b5f546040516001600160a01b039091168152602001610121565b6100f16101f63660046107df565b6103c7565b6101156102093660046108a2565b60016020525f908152604090205460ff1681565b6100f161022b3660046108a2565b610440565b610238610498565b805182511461025a57604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576102a183828151811061027a5761027a6108e2565b6020026020010151838381518110610294576102946108e2565b60200260200101516104dd565b60010161025c565b505050565b6102b6610498565b80518251146102d857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a95761031f8382815181106102f8576102f86108e2565b6020026020010151838381518110610312576103126108e2565b602002602001015161053c565b6001016102da565b61032f610498565b6103385f610593565b565b610342610498565b61034b816105fa565b50565b610356610498565b805182511461037857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576103bf838281518110610398576103986108e2565b60200260200101518383815181106103b2576103b26108e2565b6020026020010151610637565b60010161037a565b6103cf610498565b80518251146103f157604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a957610438838281518110610411576104116108e2565b602002602001015183838151811061042b5761042b6108e2565b602002602001015161068e565b6001016103f3565b610448610498565b6001600160a01b03811661048f576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61034b81610593565b5f546001600160a01b03163314610338576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610486565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c391015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610530565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610530565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f1509101610530565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610722576107226106e5565b604052919050565b5f67ffffffffffffffff821115610743576107436106e5565b5060051b60200190565b80356001600160a01b0381168114610763575f5ffd5b919050565b5f82601f830112610777575f5ffd5b813561078a6107858261072a565b6106f9565b8082825260208201915060208360051b8601019250858311156107ab575f5ffd5b602085015b838110156107d557803580151581146107c7575f5ffd5b8352602092830192016107b0565b5095945050505050565b5f5f604083850312156107f0575f5ffd5b823567ffffffffffffffff811115610806575f5ffd5b8301601f81018513610816575f5ffd5b80356108246107858261072a565b8082825260208201915060208360051b850101925087831115610845575f5ffd5b6020840193505b8284101561086e5761085d8461074d565b82526020938401939091019061084c565b9450505050602083013567ffffffffffffffff81111561088c575f5ffd5b61089885828601610768565b9150509250929050565b5f602082840312156108b2575f5ffd5b6108bb8261074d565b9392505050565b5f602082840312156108d2575f5ffd5b813560ff811681146108bb575f5ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212204e1a03491d67aedff2296b499cf5fee36529062002d67b286fb08859eac0e13664736f6c634300081c00330000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000e0d76433edd9f5df370561bd0af231e72c83cd3a000000000000000000000000f59eae23353b12ab0561aae1bb74d06623cd3e480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e185b20035753ad9c4c8040fdfeaf05ee2d3454f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000003f4c47e37a94caee31d0b585f54f3ffa1f2294c90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a10000000000000000000000000000000000000000","id":1,"to":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","type":"ONCHAIN_INTERACTION","value":{"_kind":"bigint","value":"0"}},"type":"NETWORK_INTERACTION_REQUEST"} +{"futureId":"Create3Controller#ICreateX.deployCreate3","networkInteractionId":1,"nonce":1,"type":"TRANSACTION_PREPARE_SEND"} +{"futureId":"Create3Controller#ICreateX.deployCreate3","networkInteractionId":1,"nonce":1,"transaction":{"fees":{"maxFeePerGas":{"_kind":"bigint","value":"417439944"},"maxPriorityFeePerGas":{"_kind":"bigint","value":"31378"}},"hash":"0xd93b41e54c8ae4fee27deada2a1d00cc5cedc2258a20d8f8e5e786dbc607d389"},"type":"TRANSACTION_SEND"} +{"futureId":"Create3Controller#ICreateX.deployCreate3","hash":"0xd93b41e54c8ae4fee27deada2a1d00cc5cedc2258a20d8f8e5e786dbc607d389","networkInteractionId":1,"receipt":{"blockHash":"0xec9e3a07ab7b34accac276b961b036b742235edce0e066857171de6c2f6beb0f","blockNumber":24033712,"logs":[{"address":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","data":"0x","logIndex":501,"topics":["0x2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c9067","0x000000000000000000000000bb77d95dfc82899217d15c6e0fa899a17ebaa664","0x8e2b5241fa30452cbf36e924a0aeac3ea10f92148bebd4551e7dfa642e40ccce"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x","logIndex":502,"topics":["0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":503,"topics":["0x77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f150","0x000000000000000000000000e0d76433edd9f5df370561bd0af231e72c83cd3a"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":504,"topics":["0x77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f150","0x000000000000000000000000f59eae23353b12ab0561aae1bb74d06623cd3e48"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":505,"topics":["0x0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d9924","0x000000000000000000000000e185b20035753ad9c4c8040fdfeaf05ee2d3454f"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":506,"topics":["0xfa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c3","0x0000000000000000000000003f4c47e37a94caee31d0b585f54f3ffa1f2294c9"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":507,"topics":["0xa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c","0x000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":508,"topics":["0xa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c","0x000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x","logIndex":509,"topics":["0x17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358","0x0000000000000000000000000000000000000000000000000000000000000001"]},{"address":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","data":"0x","logIndex":510,"topics":["0x4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b511","0x0000000000000000000000006002825babc837776a08799404fa55f15dceda6b"]}],"status":"SUCCESS"},"type":"TRANSACTION_CONFIRM"} +{"futureId":"Create3Controller#ICreateX.deployCreate3","result":{"type":"SUCCESS"},"type":"CALL_EXECUTION_STATE_COMPLETE"} +{"artifactId":"Create3Controller#ICreateX","dependencies":["Create3Controller#ICreateX.deployCreate3","Create3Controller#ICreateX"],"emitterAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","eventIndex":0,"eventName":"ContractCreation","futureId":"Create3Controller#ICreateX.ContractCreation.newContract.0","nameOrIndex":"newContract","result":"0x6002825BabC837776A08799404fA55f15DCedA6b","strategy":"basic","strategyConfig":{},"txToReadFrom":"0xd93b41e54c8ae4fee27deada2a1d00cc5cedc2258a20d8f8e5e786dbc607d389","type":"READ_EVENT_ARGUMENT_EXECUTION_STATE_INITIALIZE"} +{"artifactId":"Create3Controller#Controller","contractAddress":"0x6002825BabC837776A08799404fA55f15DCedA6b","contractName":"Controller","dependencies":["Create3Controller#ICreateX.ContractCreation.newContract.0"],"futureId":"Create3Controller#Controller","futureType":"NAMED_ARTIFACT_CONTRACT_AT","strategy":"basic","strategyConfig":{},"type":"CONTRACT_AT_EXECUTION_STATE_INITIALIZE"} +{"artifactId":"Create3Settler#ICreateX","contractAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","contractName":"ICreateX","dependencies":[],"futureId":"Create3Settler#ICreateX","futureType":"NAMED_ARTIFACT_CONTRACT_AT","strategy":"basic","strategyConfig":{},"type":"CONTRACT_AT_EXECUTION_STATE_INITIALIZE"} +{"args":["0x3a0ce8115b4913f42c4928d6bc3f554e9a81468b000000000000000000000018","0x610180604052348015610010575f5ffd5b506040516150e63803806150e683398101604081905261002f916102aa565b604080518082018252601681527f4d696d69632050726f746f636f6c20536574746c657200000000000000000000602080830191909152825180840190935260018352603160f81b9083015290826001600160a01b0381166100ab57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6100b4816101c4565b50600180556100c4826002610213565b610120526100d3816003610213565b61014052815160208084019190912060e052815190820120610100524660a05261015f60e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60805250503060c0526001600160a01b0382166101605260405161018290610282565b604051809103905ff08015801561019b573d5f5f3e3d5ffd5b50600480546001600160a01b0319166001600160a01b0392909216919091179055506104859050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f60208351101561022e5761022783610245565b905061023f565b816102398482610373565b5060ff90505b92915050565b5f5f829050601f8151111561026f578260405163305a27a960e01b81526004016100a2919061042d565b805161027a82610462565b179392505050565b6109c58061472183390190565b80516001600160a01b03811681146102a5575f5ffd5b919050565b5f5f604083850312156102bb575f5ffd5b6102c48361028f565b91506102d26020840161028f565b90509250929050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061030357607f821691505b60208210810361032157634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561036e57805f5260205f20601f840160051c8101602085101561034c5750805b601f840160051c820191505b8181101561036b575f8155600101610358565b50505b505050565b81516001600160401b0381111561038c5761038c6102db565b6103a08161039a84546102ef565b84610327565b6020601f8211600181146103d2575f83156103bb5750848201515b5f19600385901b1c1916600184901b17845561036b565b5f84815260208120601f198516915b8281101561040157878501518255602094850194600190920191016103e1565b508482101561041e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80516020808301519190811015610321575f1960209190910360031b1b16919050565b60805160a05160c05160e0516101005161012051610140516101605161421761050a5f395f81816103440152818161049c015281816106cd015281816112770152818161147401528181611585015261223b01525f610bd601525f610ba401525f61294301525f61291b01525f61287601525f6128a001525f6128ca01526142175ff3fe60806040526004361061010c575f3560e01c80638da5cb5b116100a1578063cbc9104511610071578063f2fde38b11610057578063f2fde38b14610314578063f77c479114610333578063fee2b7a714610366575f5ffd5b8063cbc91045146102d6578063f0801868146102f5575f5ffd5b80638da5cb5b14610239578063911929df14610255578063c422661f14610281578063c4bb6af5146102a0575f5ffd5b8063715018a6116100dc578063715018a6146101c057806377c8fb6b146101d45780637f411c79146101f357806384b0196e14610212575f5ffd5b80630fc27cf6146101175780632e5a2458146101385780634987ed2f1461016a5780636ccae054146101a1575f5ffd5b3661011357005b5f5ffd5b348015610122575f5ffd5b50610136610131366004612dc9565b610385565b005b348015610143575f5ffd5b50610157610152366004613366565b610399565b6040519081526020015b60405180910390f35b348015610175575f5ffd5b50600454610189906001600160a01b031681565b6040516001600160a01b039091168152602001610161565b3480156101ac575f5ffd5b506101366101bb3660046133de565b6103af565b3480156101cb575f5ffd5b50610136610465565b3480156101df575f5ffd5b50600554610189906001600160a01b031681565b3480156101fe575f5ffd5b5061013661020d36600461341c565b610478565b34801561021d575f5ffd5b50610226610587565b60405161016197969594939291906135ca565b348015610244575f5ffd5b505f546001600160a01b0316610189565b348015610260575f5ffd5b5061027461026f366004612dc9565b6105e5565b6040516101619190613653565b34801561028c575f5ffd5b5061013661029b366004612dc9565b61068e565b3480156102ab575f5ffd5b506101576102ba366004613665565b600660209081525f928352604080842090915290825290205481565b3480156102e1575f5ffd5b506101366102f036600461368f565b61069f565b348015610300575f5ffd5b5061013661030f36600461341c565b6106a9565b34801561031f575f5ffd5b5061013661032e366004612dc9565b61076e565b34801561033e575f5ffd5b506101897f000000000000000000000000000000000000000000000000000000000000000081565b348015610371575f5ffd5b506101576103803660046136c9565b6107c1565b61038d6107d1565b61039681610816565b50565b5f6103a58484846108ac565b90505b9392505050565b6103b76107d1565b6103bf610948565b6001600160a01b0382166103ff576040517f54328a0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61040a83838361098b565b816001600160a01b0316836001600160a01b03167fed2837b80b3489773c5f1ed30dd2884b4c90dbf7b87428ea03c49b24ef59a8058360405161044f91815260200190565b60405180910390a361046060018055565b505050565b61046d6107d1565b6104765f6109c4565b565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa1580156104e3573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050791906136fb565b6105345760405163a7ac57a960e01b81526001600160a01b03821660048201526024015b60405180910390fd5b5f5a9050610543836001610a20565b5f5a61054f908361371a565b9050806040517fa523ba4900000000000000000000000000000000000000000000000000000000815260040161052b91815260200190565b5f6060805f5f5f6060610598610b9d565b6105a0610bcf565b604080515f808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009b939a50919850469750309650945092509050565b6001600160a01b0381165f90815260076020526040902080546060919061060b90613739565b80601f016020809104026020016040519081016040528092919081815260200182805461063790613739565b80156106825780601f1061065957610100808354040283529160200191610682565b820191905f5260205f20905b81548152906001019060200180831161066557829003601f168201915b50505050509050919050565b6106966107d1565b61039681610bfc565b6103963382610c52565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa158015610714573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061073891906136fb565b6107605760405163a7ac57a960e01b81526001600160a01b038216600482015260240161052b565b61076a825f610a20565b5050565b6107766107d1565b6001600160a01b0381166107b8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f600482015260240161052b565b610396816109c4565b5f6107cb82610ccb565b92915050565b5f546001600160a01b03163314610476576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052b565b6001600160a01b038116610856576040517f42af048a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f93fa00498c991ad852fbb3361afb0e3507720aa9ff69a8386009b76134a6e654905f90a250565b5f7fabd98b1a5498f7ce6ce5fd671f1a1c2ad437b155c4c12281f925612823c72e886108d784610ccb565b83865f01518760200151805190602001206108f58960400151610d6e565b6040805160208101979097528601949094526001600160a01b039092166060850152608084015260a083015260c082015260e0016040516020818303038152906040528051906020012090509392505050565b600260015403610984576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036109b9576104608282610d80565b610460838383610e2b565b5f80546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610a28610948565b5f5b8251811015610b93575f838281518110610a4657610a4661376b565b60200260200101515f015190505f848381518110610a6657610a6661376b565b60200260200101516020015190505f858481518110610a8757610a8761376b565b6020026020010151604001519050610aa183838388610e9f565b6020838101516001600160a01b03165f9081526006825260408082206060870151835290925220439055825160ff16610ae357610ade838361164c565b610b4a565b825160ff165f1901610af957610ade83836119d0565b825160ff1660011901610b1057610ade8383611a90565b82516040517f7cdbcbf100000000000000000000000000000000000000000000000000000000815260ff909116600482015260240161052b565b610b558284336108ac565b6040518581527f120985525cfb78f6c03f96986f9687f49caf81e29350d21fa052e8c0df211ebb9060200160405180910390a2505050600101610a2a565b5061076a60018055565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006002611bb6565b905090565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006003611bb6565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f493e783f4ed464086f434b9d42b1b8fb97375d1fdc91692c73551281526d22be905f90a250565b6001600160a01b0382165f908152600760205260408120610c7291612d60565b6001600160a01b0382165f908152600760205260409020610c9382826137d7565b506040516001600160a01b038316907f5ac98b81e10e07689d7de8e9ec09900564d65647de8d039328d4cbdf575b28f4905f90a25050565b5f7fbd51147cb801db5aa8dc80920aabcbe45ea95c1767e31bc3339f62d8f0d046f9825f015183602001518460400151856060015186608001518760a0015180519060200120610d1e8960c00151611c60565b610d2b8a60e00151611da6565b8a61010001518b6101200151604051602001610d519b9a99989796959493929190613892565b604051602081830303815290604052805190602001209050919050565b5f81604051602001610d51919061390c565b80471015610dc3576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810182905260440161052b565b5f5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610e0d576040519150601f19603f3d011682016040523d82523d5f602084013e610e12565b606091505b509150915081610e2557610e2581611eba565b50505050565b6040516001600160a01b0383811660248301526044820183905261046091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611efc565b60408401516001600160a01b03163014610ef65760408085015190517f30b078bf0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6060840151610f31576040517f58e8d18100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808501516001600160a01b03165f90815260068252604080822060608801518352909252205415610fab57602084015160608501516040517f9756f94d0000000000000000000000000000000000000000000000000000000081526001600160a01b039092166004830152602482015260440161052b565b6005546001600160a01b0316156110e4576020808501516001600160a01b03165f9081526007909152604081208054610fe390613739565b80601f016020809104026020016040519081016040528092919081815260200182805461100f90613739565b801561105a5780601f106110315761010080835404028352916020019161105a565b820191905f5260205f20905b81548152906001019060200180831161103d57829003601f168201915b505050505090505f815111156110e2576005546040517fdbd902180000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063dbd90218906110b59088908590600401613b29565b5f6040518083038186803b1580156110cb575f5ffd5b505afa1580156110dd573d5f5f3e3d5ffd5b505050505b505b5f6110ee85611f81565b9050801561118d57428560800151116111425760808501516040517f95d3dc22000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b8351421080159061118b5784516040517f2c39717b000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b505b8360400151518560c0015151146111d0576040517f9dca144800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8560c0015151811015611273575f8660c0015182815181106111f6576111f661376b565b60200260200101516020015190505f8660400151838151811061121b5761121b61376b565b6020026020010151905081811115611269576040517f8ffa61fa000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161052b565b50506001016111d2565b505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663680038c76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112d1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190613b4d565b90505f8160ff1687610120015111611310578160ff16611317565b8661012001515b90508087610140015151101561136b57610140870151516040517fcd0ce69900000000000000000000000000000000000000000000000000000000815261052b918391600401918252602082015260400190565b5f5f90505f60405180602001604052806113848b610ccb565b905290505f61139a61139583611fd0565b61200d565b90505f5b8a61014001515181101561152f575f6113d5838d610140015184815181106113c8576113c861376b565b6020026020010151612054565b9050846001600160a01b0316816001600160a01b031611611435576040517fce98854c0000000000000000000000000000000000000000000000000000000081526001600160a01b0380871660048301528216602482015260440161052b565b6040517f616556700000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015291955085915f917f000000000000000000000000000000000000000000000000000000000000000090911690636165567090602401602060405180830381865afa1580156114bb573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114df91906136fb565b1590508015611525576040517ff24706660000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505060010161139e565b505f6115486115426113958c8e336108ac565b8a612054565b6040517f163eed9e0000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301529192505f917f0000000000000000000000000000000000000000000000000000000000000000169063163eed9e90602401602060405180830381865afa1580156115ca573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ee91906136fb565b1580156115f9575088155b9050801561163e576040517f100158a80000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505050505050505050505050565b5f8260a001518060200190518101906116659190613c16565b90505f82602001518060200190518101906116809190613d93565b905061168c828261207c565b60208401516004545f916116a9916001600160a01b0316906122ed565b905046835f01510361170b575f5b836040015151811015611709575f846040015182815181106116db576116db61376b565b60200260200101519050611700815f01518860200151865f0151846020015187612371565b506001016116b7565b505b5f611715846123b1565b83516040517f7b4da5de0000000000000000000000000000000000000000000000000000000081529192506001600160a01b031690637b4da5de906117609089908990600401613efe565b5f604051808303815f87803b158015611777575f5ffd5b505af1158015611789573d5f5f3e3d5ffd5b50505050468460200151036119c8575f84606001515167ffffffffffffffff8111156117b7576117b7612de4565b6040519080825280602002602001820160405280156117e0578160200160208202803683370190505b5090505f5b856060015151811015611990575f866060015182815181106118095761180961376b565b602002602001015190505f611821825f01513061246d565b90505f8584815181106118365761183661376b565b602002602001015190508082101561188b576040517ff5ad5ab700000000000000000000000000000000000000000000000000000000815260048101859052602481018390526044810182905260640161052b565b611895818361371a565b8585815181106118a7576118a761376b565b6020026020010181815250505f886040015185815181106118ca576118ca61376b565b60200260200101519050808686815181106118e7576118e761376b565b6020026020010151101561195557848686815181106119085761190861376b565b60209081029190910101516040517ff0a43aa7000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044810182905260640161052b565b611980845f015185604001518888815181106119735761197361376b565b602002602001015161098b565b5050600190920191506117e59050565b506119bb8787836040516020016119a79190613f22565b604051602081830303815290604052612524565b6119c68787856125b7565b505b505050505050565b5f8260a001518060200190518101906119e99190613f34565b90506119f5818361263b565b60208301516004545f91611a12916001600160a01b0316906122ed565b90505f5b826020015151811015611a69575f83602001518281518110611a3a57611a3a61376b565b60200260200101519050611a60815f015187602001518360400151846020015187612371565b50600101611a16565b50604080515f815260208101909152611a859085908590612524565b610e258484836125b7565b5f8260a00151806020019051810190611aa99190613fc0565b9050611aba818385602001516126fc565b5f81602001515167ffffffffffffffff811115611ad957611ad9612de4565b604051908082528060200260200182016040528015611b0c57816020015b6060815260200190600190039081611af75790505b5090505f5b826020015151811015611b93575f83602001518281518110611b3557611b3561376b565b602090810291909101810151878201518151928201516040830151600454939550611b6d946001600160a01b0390941693919061279a565b838381518110611b7f57611b7f61376b565b602090810291909101015250600101611b11565b50611baa8484836040516020016119a7919061410a565b610e25848460016125b7565b606060ff8314611bd057611bc98361282d565b90506107cb565b818054611bdc90613739565b80601f0160208091040260200160405190810160405280929190818152602001828054611c0890613739565b8015611c535780601f10611c2a57610100808354040283529160200191611c53565b820191905f5260205f20905b815481529060010190602001808311611c3657829003601f168201915b5050505050905092915050565b5f5f825167ffffffffffffffff811115611c7c57611c7c612de4565b604051908082528060200260200182016040528015611ca5578160200160208202803683370190505b5090505f5b8351811015611d76577fe44101eb81f50b64d4005e31312ea7bafd6f81357f9dce4fe51d5f2dcb939da4848281518110611ce657611ce661376b565b60200260200101515f0151858381518110611d0357611d0361376b565b602002602001015160200151604051602001611d3b939291909283526001600160a01b03919091166020830152604082015260600190565b60405160208183030381529060405280519060200120828281518110611d6357611d6361376b565b6020908102919091010152600101611caa565b5080604051602001611d88919061390c565b60405160208183030381529060405280519060200120915050919050565b5f5f825167ffffffffffffffff811115611dc257611dc2612de4565b604051908082528060200260200182016040528015611deb578160200160208202803683370190505b5090505f5b8351811015611d76577ff7a396f96f3810db1cfde06de38d32fc29d56b63cf9d9aee2c10c91ef7273bb1848281518110611e2c57611e2c61376b565b60200260200101515f0151858381518110611e4957611e4961376b565b60200260200101516020015180519060200120604051602001611e7f939291909283526020830191909152604082015260600190565b60405160208183030381529060405280519060200120828281518110611ea757611ea761376b565b6020908102919091010152600101611df0565b805115611eca5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f60205f8451602086015f885af180611f1b576040513d5f823e3d81fd5b50505f513d91508115611f32578060011415611f3f565b6001600160a01b0384163b155b15610e25576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b80515f9060ff1615611f9557506001919050565b5f8260a00151806020019051810190611fae9190613c16565b90508060200151815f015103611fc75750600192915050565b51461492915050565b8051604080517f07094367b2db06e3dbb414cf947644f8e08067ea238c5f41cb2fa5fea5be628e6020820152908101919091525f90606001610d51565b5f6107cb61201961286a565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b5f5f5f5f6120628686612993565b92509250925061207282826129dc565b5090949350505050565b81515f904614801590612093575046836020015114155b905080156120b65760405163308a43dd60e11b815246600482015260240161052b565b826060015151826040015151146120f9576040517f1816c20200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8360600151518110156121f2575f8460600151828151811061211f5761211f61376b565b602002602001015190505f81604001519050306001600160a01b0316816001600160a01b03160361216e57604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b5f826020015190505f8660400151858151811061218d5761218d61376b565b60200260200101519050818110156121e2576040517f1fef5f3300000000000000000000000000000000000000000000000000000000815260048101869052602481018290526044810183905260640161052b565b5050600190920191506120fb9050565b5060208301518351146104605781516040517f7d7011020000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201525f917f00000000000000000000000000000000000000000000000000000000000000001690637d70110290602401602060405180830381865afa158015612280573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122a491906136fb565b1590508015610e255782516040517fdb489af40000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6040517f5e07a6e60000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301525f9190841690635e07a6e690602401602060405180830381865afa15801561234d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a891906136fb565b801561239557600454612390906001600160a01b031685878686612adf565b6123aa565b6123aa6001600160a01b038616858585612b38565b5050505050565b606081606001515167ffffffffffffffff8111156123d1576123d1612de4565b6040519080825280602002602001820160405280156123fa578160200160208202803683370190505b50905046826020015103612468575f5b82606001515181101561246657612441836060015182815181106124305761243061376b565b60200260200101515f01513061246d565b8282815181106124535761245361376b565b602090810291909101015260010161240a565b505b919050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036124a357506001600160a01b038116316107cb565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015612500573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc9919061411c565b5f5b8360e0015151811015610e25575f8460e00151828151811061254a5761254a61376b565b60200260200101519050845f015160ff16815f015186602001516001600160a01b03167fee2c98c99b683f71058b8744fea294a411482f07d55c98b392917e9286f22f1388888887602001516040516125a69493929190614133565b60405180910390a450600101612526565b6020830151335f5b8560c00151518110156119c8575f8660c0015182815181106125e3576125e361376b565b60200260200101515f01519050612604816001600160a01b03166103481490565b61263257612632818585896040015186815181106126245761262461376b565b602002602001015189612371565b506001016125bf565b8151461461265e5760405163308a43dd60e11b815246600482015260240161052b565b60208101515115612682576040516393c58e1160e01b815260040160405180910390fd5b5f5b826020015151811015610460575f836020015182815181106126a8576126a861376b565b6020026020010151604001519050306001600160a01b0316816001600160a01b0316036126f357604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b50600101612684565b8251461461271f5760405163308a43dd60e11b815246600482015260240161052b565b60208201515115612743576040516393c58e1160e01b815260040160405180910390fd5b600454612759906001600160a01b0316826122ed565b610460576040517fcaec9bac0000000000000000000000000000000000000000000000000000000081526001600160a01b038216600482015260240161052b565b60606128238663ff883fcf60e01b878787876040516024016127bf949392919061418a565b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612b71565b9695505050505050565b60605f61283983612be3565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156128c257507f000000000000000000000000000000000000000000000000000000000000000046145b156128ec57507f000000000000000000000000000000000000000000000000000000000000000090565b610bca604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f5f83516041036129ca576020840151604085015160608601515f1a6129bc88828585612c23565b9550955095505050506129d5565b505081515f91506002905b9250925092565b5f8260038111156129ef576129ef61377f565b036129f8575050565b6001826003811115612a0c57612a0c61377f565b03612a43576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826003811115612a5757612a5761377f565b03612a91576040517ffce698f70000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6003826003811115612aa557612aa561377f565b0361076a576040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6040516001600160a01b038086166024830152808516604483015283166064820152608481018290526119c89086907ff18d03cc000000000000000000000000000000000000000000000000000000009060a4016127bf565b6040516001600160a01b038481166024830152838116604483015260648201839052610e259186918216906323b872dd90608401610e58565b60605f5f846001600160a01b031684604051612b8d91906141cb565b5f60405180830381855af49150503d805f8114612bc5576040519150601f19603f3d011682016040523d82523d5f602084013e612bca565b606091505b5091509150612bda858383612ceb565b95945050505050565b5f60ff8216601f8111156107cb576040517fb3512b0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115612c5c57505f91506003905082612ce1565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015612cad573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116612cd857505f925060019150829050612ce1565b92505f91508190505b9450945094915050565b606082612d0057612cfb82611eba565b6103a8565b8151158015612d1757506001600160a01b0384163b155b15612d59576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b50806103a8565b508054612d6c90613739565b5f825580601f10612d7b575050565b601f0160209004905f5260205f209081019061039691905b80821115612da6575f8155600101612d93565b5090565b6001600160a01b0381168114610396575f5ffd5b803561246881612daa565b5f60208284031215612dd9575f5ffd5b81356103a881612daa565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b60405290565b6040805190810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051610160810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b6040516080810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612eb457612eb4612de4565b604052919050565b5f67ffffffffffffffff821115612ed557612ed5612de4565b50601f01601f191660200190565b5f82601f830112612ef2575f5ffd5b8135612f05612f0082612ebc565b612e8b565b818152846020838601011115612f19575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f67ffffffffffffffff821115612f4e57612f4e612de4565b5060051b60200190565b5f60608284031215612f68575f5ffd5b612f70612df8565b823581529050602082013567ffffffffffffffff811115612f8f575f5ffd5b612f9b84828501612ee3565b602083015250604082013567ffffffffffffffff811115612fba575f5ffd5b8201601f81018413612fca575f5ffd5b8035612fd8612f0082612f35565b8082825260208201915060208360051b850101925086831115612ff9575f5ffd5b6020840193505b8284101561301b578335825260209384019390910190613000565b60408501525091949350505050565b60ff81168114610396575f5ffd5b80356124688161302a565b5f82601f830112613052575f5ffd5b8135613060612f0082612f35565b8082825260208201915060208360061b860101925085831115613081575f5ffd5b602085015b838110156130cc576040818803121561309d575f5ffd5b6130a5612e21565b81356130b081612daa565b8152602082810135818301529084529290920191604001613086565b5095945050505050565b5f82601f8301126130e5575f5ffd5b81356130f3612f0082612f35565b8082825260208201915060208360051b860101925085831115613114575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613137575f5ffd5b86016040818903601f1901121561314c575f5ffd5b613154612e21565b60208201358152604082013567ffffffffffffffff811115613174575f5ffd5b6131838a602083860101612ee3565b6020830152508085525050602083019250602081019050613119565b5f82601f8301126131ae575f5ffd5b81356131bc612f0082612f35565b8082825260208201915060208360051b8601019250858311156131dd575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613200575f5ffd5b61320f886020838a0101612ee3565b845250602092830192016131e2565b5f610160828403121561322f575f5ffd5b613237612e44565b905061324282613038565b815261325060208301612dbe565b602082015261326160408301612dbe565b6040820152606082810135908201526080808301359082015260a082013567ffffffffffffffff811115613293575f5ffd5b61329f84828501612ee3565b60a08301525060c082013567ffffffffffffffff8111156132be575f5ffd5b6132ca84828501613043565b60c08301525060e082013567ffffffffffffffff8111156132e9575f5ffd5b6132f5848285016130d6565b60e08301525061010082013567ffffffffffffffff811115613315575f5ffd5b61332184828501612ee3565b61010083015250610120828101359082015261014082013567ffffffffffffffff81111561334d575f5ffd5b6133598482850161319f565b6101408301525092915050565b5f5f5f60608486031215613378575f5ffd5b833567ffffffffffffffff81111561338e575f5ffd5b61339a86828701612f58565b935050602084013567ffffffffffffffff8111156133b6575f5ffd5b6133c28682870161321e565b92505060408401356133d381612daa565b809150509250925092565b5f5f5f606084860312156133f0575f5ffd5b83356133fb81612daa565b9250602084013561340b81612daa565b929592945050506040919091013590565b5f6020828403121561342c575f5ffd5b813567ffffffffffffffff811115613442575f5ffd5b8201601f81018413613452575f5ffd5b8035613460612f0082612f35565b8082825260208201915060208360051b850101925086831115613481575f5ffd5b602084015b8381101561355757803567ffffffffffffffff8111156134a4575f5ffd5b85016060818a03601f190112156134b9575f5ffd5b6134c1612df8565b602082013567ffffffffffffffff8111156134da575f5ffd5b6134e98b60208386010161321e565b825250604082013567ffffffffffffffff811115613505575f5ffd5b6135148b602083860101612f58565b602083015250606082013567ffffffffffffffff811115613533575f5ffd5b6135428b602083860101612ee3565b60408301525084525060209283019201613486565b509695505050505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f8151808452602084019350602083015f5b828110156135c05781518652602095860195909101906001016135a2565b5093949350505050565b7fff000000000000000000000000000000000000000000000000000000000000008816815260e060208201525f61360460e0830189613562565b82810360408401526136168189613562565b90508660608401526001600160a01b03861660808401528460a084015282810360c08401526136458185613590565b9a9950505050505050505050565b602081525f6103a86020830184613562565b5f5f60408385031215613676575f5ffd5b823561368181612daa565b946020939093013593505050565b5f6020828403121561369f575f5ffd5b813567ffffffffffffffff8111156136b5575f5ffd5b6136c184828501612ee3565b949350505050565b5f602082840312156136d9575f5ffd5b813567ffffffffffffffff8111156136ef575f5ffd5b6136c18482850161321e565b5f6020828403121561370b575f5ffd5b815180151581146103a8575f5ffd5b818103818111156107cb57634e487b7160e01b5f52601160045260245ffd5b600181811c9082168061374d57607f821691505b60208210810361246657634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b601f82111561046057805f5260205f20601f840160051c810160208510156137b85750805b601f840160051c820191505b818110156123aa575f81556001016137c4565b815167ffffffffffffffff8111156137f1576137f1612de4565b613805816137ff8454613739565b84613793565b6020601f821160018114613837575f83156138205750848201515b5f19600385901b1c1916600184901b1784556123aa565b5f84815260208120601f198516915b828110156138665787850151825560209485019460019092019101613846565b508482101561388357868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8b815260ff8b1660208201526001600160a01b038a1660408201526001600160a01b03891660608201528760808201528660a08201528560c08201528460e0820152836101008201526101606101208201525f6138f3610160830185613562565b9050826101408301529c9b505050505050505050505050565b81515f90829060208501835b82811015613936578151845260209384019390910190600101613918565b509195945050505050565b5f8151808452602084019350602083015f5b828110156135c057815180516001600160a01b031687526020908101518188015260409096019590910190600101613953565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f1985840301885281518051845260208101519050604060208501526139d46040850182613562565b6020998a01999094509290920191506001016139a2565b50909695505050505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f19858403018852613a2f838351613562565b6020988901989093509190910190600101613a13565b805160ff1682525f6020820151613a6760208501826001600160a01b03169052565b506040820151613a8260408501826001600160a01b03169052565b50606082015160608401526080820151608084015260a082015161016060a0850152613ab2610160850182613562565b905060c083015184820360c0860152613acb8282613941565b91505060e083015184820360e0860152613ae58282613986565b915050610100830151848203610100860152613b018282613562565b915050610120830151610120850152610140830151848203610140860152612bda82826139f7565b604081525f613b3b6040830185613a45565b8281036020840152612bda8185613562565b5f60208284031215613b5d575f5ffd5b81516103a88161302a565b5f613b75612f0084612f35565b83815290506020810160608402830185811115613b90575f5ffd5b835b81811015613bee575f60608289031215613baa575f5ffd5b613bb2612df8565b90508151613bbf81612daa565b8152602082810151908201526040820151613bd981612daa565b60408201528352602090920191606001613b92565b5050509392505050565b5f82601f830112613c07575f5ffd5b6103a883835160208501613b68565b5f60208284031215613c26575f5ffd5b815167ffffffffffffffff811115613c3c575f5ffd5b820160808185031215613c4d575f5ffd5b613c55612e68565b8151815260208083015190820152604082015167ffffffffffffffff811115613c7c575f5ffd5b8201601f81018613613c8c575f5ffd5b8051613c9a612f0082612f35565b8082825260208201915060208360061b850101925088831115613cbb575f5ffd5b6020840193505b82841015613d0b576040848a031215613cd9575f5ffd5b613ce1612e21565b8451613cec81612daa565b8152602085810151818301529083526040909401939190910190613cc2565b6040850152505050606082015167ffffffffffffffff811115613d2c575f5ffd5b613d3886828501613bf8565b606083015250949350505050565b5f82601f830112613d55575f5ffd5b8151613d63612f0082612ebc565b818152846020838601011115613d77575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613da3575f5ffd5b815167ffffffffffffffff811115613db9575f5ffd5b820160608185031215613dca575f5ffd5b613dd2612df8565b8151613ddd81612daa565b8152602082015167ffffffffffffffff811115613df8575f5ffd5b613e0486828501613d46565b602083015250604082015167ffffffffffffffff811115613e23575f5ffd5b80830192505084601f830112613e37575f5ffd5b8151613e45612f0082612f35565b8082825260208201915060208360051b860101925087831115613e66575f5ffd5b6020850194505b82851015613e88578451825260209485019490910190613e6d565b6040840152509095945050505050565b805182525f602082015160606020850152613eb66060850182613562565b9050604083015184820360408601528181518084526020840191506020830193505f92505b808310156130cc5783518252602082019150602084019350600183019250613edb565b604081525f613f106040830185613a45565b8281036020840152612bda8185613e98565b602081525f6103a86020830184613590565b5f60208284031215613f44575f5ffd5b815167ffffffffffffffff811115613f5a575f5ffd5b820160408185031215613f6b575f5ffd5b613f73612e21565b81518152602082015167ffffffffffffffff811115613f90575f5ffd5b80830192505084601f830112613fa4575f5ffd5b613fb385835160208501613b68565b6020820152949350505050565b5f60208284031215613fd0575f5ffd5b815167ffffffffffffffff811115613fe6575f5ffd5b820160408185031215613ff7575f5ffd5b613fff612e21565b81518152602082015167ffffffffffffffff81111561401c575f5ffd5b80830192505084601f830112614030575f5ffd5b815161403e612f0082612f35565b8082825260208201915060208360051b86010192508783111561405f575f5ffd5b602085015b838110156140f957805167ffffffffffffffff811115614082575f5ffd5b86016060818b03601f19011215614097575f5ffd5b61409f612df8565b60208201516140ad81612daa565b8152604082015167ffffffffffffffff8111156140c8575f5ffd5b6140d78c602083860101613d46565b6020838101919091526060939093015160408301525084529283019201614064565b506020840152509095945050505050565b602081525f6103a860208301846139f7565b5f6020828403121561412c575f5ffd5b5051919050565b608081525f6141456080830187613a45565b82810360208401526141578187613e98565b9050828103604084015261416b8186613562565b9050828103606084015261417f8185613562565b979650505050505050565b6001600160a01b03851681526001600160a01b0384166020820152608060408201525f6141ba6080830185613562565b905082606083015295945050505050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220fae3247dd7a33c3cd300206de44d56570a43a7690ab25372a739d3c2704b371e64736f6c634300081c00336080604052348015600e575f5ffd5b506109a98061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80635e07a6e614610043578063f18d03cc1461006b578063ff883fcf14610080575b5f5ffd5b610056610051366004610636565b6100a0565b60405190151581526020015b60405180910390f35b61007e61007936600461064f565b6100ec565b005b61009361008e366004610703565b6101cb565b60405161006291906107d3565b5f816001600160a01b03163b5f036100b957505f919050565b6100c2826102b3565b156100cf57506001919050565b6100d882610364565b156100e557506001919050565b505f919050565b6100f5846102b3565b1561017d576040517fbeabacc80000000000000000000000000000000000000000000000000000000081526001600160a01b03848116600483015283811660248301526044820183905285169063beabacc8906064015f604051808303815f87803b158015610162575f5ffd5b505af1158015610174573d5f5f3e3d5ffd5b505050506101c5565b61018684610364565b1561019c57610197848484846103d3565b6101c5565b604051636b94637360e11b81526001600160a01b03851660048201526024015b60405180910390fd5b50505050565b60606101d6856102b3565b1561026d576040517f4ae000410000000000000000000000000000000000000000000000000000000081526001600160a01b03861690634ae0004190610224908790879087906004016107e5565b5f604051808303815f875af115801561023f573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526102669190810190610863565b90506102ab565b61027685610364565b1561028757610266858585856104a2565b604051636b94637360e11b81526001600160a01b03861660048201526024016101bc565b949350505050565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527ff44bac890000000000000000000000000000000000000000000000000000000060048201525f906001600160a01b038316906301ffc9a790602401602060405180830381865afa92505050801561034e575060408051601f3d908101601f1916820190925261034b918101906108a4565b60015b61035957505f919050565b92915050565b919050565b5f816001600160a01b031663e75235b86040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156103bf575060408051601f3d908101601f191682019092526103bc918101906108bd565b60015b6103ca57505f919050565b50600192915050565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b0384161461047f57604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261047a90859085905f6104a2565b61049b565b604080515f81526020810190915261049b9085908490846104a2565b5050505050565b60605f5f866001600160a01b0316635229073f8786885f6040518563ffffffff1660e01b81526004016104d894939291906108d4565b5f604051808303815f875af11580156104f3573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261051a9190810190610928565b9150915084515f146105365761053186838361054b565b610540565b61054082826105c3565b979650505050505050565b6060826105605761055b826105de565b6105bc565b815115801561057757506001600160a01b0384163b155b156105b9576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016101bc565b50805b9392505050565b6060826105d8576105d3826105de565b610359565b50919050565b8051156105ee5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461035f575f5ffd5b5f60208284031215610646575f5ffd5b6105bc82610620565b5f5f5f5f60808587031215610662575f5ffd5b61066b85610620565b935061067960208601610620565b925061068760408601610620565b9396929550929360600135925050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff811182821017156106d4576106d4610697565b604052919050565b5f67ffffffffffffffff8211156106f5576106f5610697565b50601f01601f191660200190565b5f5f5f5f60808587031215610716575f5ffd5b61071f85610620565b935061072d60208601610620565b9250604085013567ffffffffffffffff811115610748575f5ffd5b8501601f81018713610758575f5ffd5b803561076b610766826106dc565b6106ab565b81815288602083850101111561077f575f5ffd5b816020840160208301375f91810160200191909152949793965093946060013593505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6105bc60208301846107a5565b6001600160a01b0384168152606060208201525f61080660608301856107a5565b9050826040830152949350505050565b5f82601f830112610825575f5ffd5b8151610833610766826106dc565b818152846020838601011115610847575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215610873575f5ffd5b815167ffffffffffffffff811115610889575f5ffd5b6102ab84828501610816565b8051801515811461035f575f5ffd5b5f602082840312156108b4575f5ffd5b6105bc82610895565b5f602082840312156108cd575f5ffd5b5051919050565b6001600160a01b0385168152836020820152608060408201525f6108fb60808301856107a5565b90506002831061091957634e487b7160e01b5f52602160045260245ffd5b82606083015295945050505050565b5f5f60408385031215610939575f5ffd5b61094283610895565b9150602083015167ffffffffffffffff81111561095d575f5ffd5b61096985828601610816565b915050925092905056fea264697066735822122063c7f17d2676ae9340ccf0078bc40725749c3c4dd9198f82832bc411d2d1333764736f6c634300081c00330000000000000000000000006002825babc837776a08799404fa55f15dceda6b0000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b"],"artifactId":"Create3Settler#ICreateX","contractAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","dependencies":["Create3Settler#ICreateX"],"from":"0x3a0ce8115b4913f42c4928d6bc3f554e9a81468b","functionName":"deployCreate3","futureId":"Create3Settler#ICreateX.deployCreate3","strategy":"basic","strategyConfig":{},"type":"CALL_EXECUTION_STATE_INITIALIZE","value":{"_kind":"bigint","value":"0"}} +{"futureId":"Create3Settler#ICreateX.deployCreate3","networkInteraction":{"data":"0x9c36a2863a0ce8115b4913f42c4928d6bc3f554e9a81468b00000000000000000000001800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000005126610180604052348015610010575f5ffd5b506040516150e63803806150e683398101604081905261002f916102aa565b604080518082018252601681527f4d696d69632050726f746f636f6c20536574746c657200000000000000000000602080830191909152825180840190935260018352603160f81b9083015290826001600160a01b0381166100ab57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6100b4816101c4565b50600180556100c4826002610213565b610120526100d3816003610213565b61014052815160208084019190912060e052815190820120610100524660a05261015f60e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60805250503060c0526001600160a01b0382166101605260405161018290610282565b604051809103905ff08015801561019b573d5f5f3e3d5ffd5b50600480546001600160a01b0319166001600160a01b0392909216919091179055506104859050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f60208351101561022e5761022783610245565b905061023f565b816102398482610373565b5060ff90505b92915050565b5f5f829050601f8151111561026f578260405163305a27a960e01b81526004016100a2919061042d565b805161027a82610462565b179392505050565b6109c58061472183390190565b80516001600160a01b03811681146102a5575f5ffd5b919050565b5f5f604083850312156102bb575f5ffd5b6102c48361028f565b91506102d26020840161028f565b90509250929050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061030357607f821691505b60208210810361032157634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561036e57805f5260205f20601f840160051c8101602085101561034c5750805b601f840160051c820191505b8181101561036b575f8155600101610358565b50505b505050565b81516001600160401b0381111561038c5761038c6102db565b6103a08161039a84546102ef565b84610327565b6020601f8211600181146103d2575f83156103bb5750848201515b5f19600385901b1c1916600184901b17845561036b565b5f84815260208120601f198516915b8281101561040157878501518255602094850194600190920191016103e1565b508482101561041e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80516020808301519190811015610321575f1960209190910360031b1b16919050565b60805160a05160c05160e0516101005161012051610140516101605161421761050a5f395f81816103440152818161049c015281816106cd015281816112770152818161147401528181611585015261223b01525f610bd601525f610ba401525f61294301525f61291b01525f61287601525f6128a001525f6128ca01526142175ff3fe60806040526004361061010c575f3560e01c80638da5cb5b116100a1578063cbc9104511610071578063f2fde38b11610057578063f2fde38b14610314578063f77c479114610333578063fee2b7a714610366575f5ffd5b8063cbc91045146102d6578063f0801868146102f5575f5ffd5b80638da5cb5b14610239578063911929df14610255578063c422661f14610281578063c4bb6af5146102a0575f5ffd5b8063715018a6116100dc578063715018a6146101c057806377c8fb6b146101d45780637f411c79146101f357806384b0196e14610212575f5ffd5b80630fc27cf6146101175780632e5a2458146101385780634987ed2f1461016a5780636ccae054146101a1575f5ffd5b3661011357005b5f5ffd5b348015610122575f5ffd5b50610136610131366004612dc9565b610385565b005b348015610143575f5ffd5b50610157610152366004613366565b610399565b6040519081526020015b60405180910390f35b348015610175575f5ffd5b50600454610189906001600160a01b031681565b6040516001600160a01b039091168152602001610161565b3480156101ac575f5ffd5b506101366101bb3660046133de565b6103af565b3480156101cb575f5ffd5b50610136610465565b3480156101df575f5ffd5b50600554610189906001600160a01b031681565b3480156101fe575f5ffd5b5061013661020d36600461341c565b610478565b34801561021d575f5ffd5b50610226610587565b60405161016197969594939291906135ca565b348015610244575f5ffd5b505f546001600160a01b0316610189565b348015610260575f5ffd5b5061027461026f366004612dc9565b6105e5565b6040516101619190613653565b34801561028c575f5ffd5b5061013661029b366004612dc9565b61068e565b3480156102ab575f5ffd5b506101576102ba366004613665565b600660209081525f928352604080842090915290825290205481565b3480156102e1575f5ffd5b506101366102f036600461368f565b61069f565b348015610300575f5ffd5b5061013661030f36600461341c565b6106a9565b34801561031f575f5ffd5b5061013661032e366004612dc9565b61076e565b34801561033e575f5ffd5b506101897f000000000000000000000000000000000000000000000000000000000000000081565b348015610371575f5ffd5b506101576103803660046136c9565b6107c1565b61038d6107d1565b61039681610816565b50565b5f6103a58484846108ac565b90505b9392505050565b6103b76107d1565b6103bf610948565b6001600160a01b0382166103ff576040517f54328a0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61040a83838361098b565b816001600160a01b0316836001600160a01b03167fed2837b80b3489773c5f1ed30dd2884b4c90dbf7b87428ea03c49b24ef59a8058360405161044f91815260200190565b60405180910390a361046060018055565b505050565b61046d6107d1565b6104765f6109c4565b565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa1580156104e3573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050791906136fb565b6105345760405163a7ac57a960e01b81526001600160a01b03821660048201526024015b60405180910390fd5b5f5a9050610543836001610a20565b5f5a61054f908361371a565b9050806040517fa523ba4900000000000000000000000000000000000000000000000000000000815260040161052b91815260200190565b5f6060805f5f5f6060610598610b9d565b6105a0610bcf565b604080515f808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009b939a50919850469750309650945092509050565b6001600160a01b0381165f90815260076020526040902080546060919061060b90613739565b80601f016020809104026020016040519081016040528092919081815260200182805461063790613739565b80156106825780601f1061065957610100808354040283529160200191610682565b820191905f5260205f20905b81548152906001019060200180831161066557829003601f168201915b50505050509050919050565b6106966107d1565b61039681610bfc565b6103963382610c52565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa158015610714573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061073891906136fb565b6107605760405163a7ac57a960e01b81526001600160a01b038216600482015260240161052b565b61076a825f610a20565b5050565b6107766107d1565b6001600160a01b0381166107b8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f600482015260240161052b565b610396816109c4565b5f6107cb82610ccb565b92915050565b5f546001600160a01b03163314610476576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052b565b6001600160a01b038116610856576040517f42af048a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f93fa00498c991ad852fbb3361afb0e3507720aa9ff69a8386009b76134a6e654905f90a250565b5f7fabd98b1a5498f7ce6ce5fd671f1a1c2ad437b155c4c12281f925612823c72e886108d784610ccb565b83865f01518760200151805190602001206108f58960400151610d6e565b6040805160208101979097528601949094526001600160a01b039092166060850152608084015260a083015260c082015260e0016040516020818303038152906040528051906020012090509392505050565b600260015403610984576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036109b9576104608282610d80565b610460838383610e2b565b5f80546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610a28610948565b5f5b8251811015610b93575f838281518110610a4657610a4661376b565b60200260200101515f015190505f848381518110610a6657610a6661376b565b60200260200101516020015190505f858481518110610a8757610a8761376b565b6020026020010151604001519050610aa183838388610e9f565b6020838101516001600160a01b03165f9081526006825260408082206060870151835290925220439055825160ff16610ae357610ade838361164c565b610b4a565b825160ff165f1901610af957610ade83836119d0565b825160ff1660011901610b1057610ade8383611a90565b82516040517f7cdbcbf100000000000000000000000000000000000000000000000000000000815260ff909116600482015260240161052b565b610b558284336108ac565b6040518581527f120985525cfb78f6c03f96986f9687f49caf81e29350d21fa052e8c0df211ebb9060200160405180910390a2505050600101610a2a565b5061076a60018055565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006002611bb6565b905090565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006003611bb6565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f493e783f4ed464086f434b9d42b1b8fb97375d1fdc91692c73551281526d22be905f90a250565b6001600160a01b0382165f908152600760205260408120610c7291612d60565b6001600160a01b0382165f908152600760205260409020610c9382826137d7565b506040516001600160a01b038316907f5ac98b81e10e07689d7de8e9ec09900564d65647de8d039328d4cbdf575b28f4905f90a25050565b5f7fbd51147cb801db5aa8dc80920aabcbe45ea95c1767e31bc3339f62d8f0d046f9825f015183602001518460400151856060015186608001518760a0015180519060200120610d1e8960c00151611c60565b610d2b8a60e00151611da6565b8a61010001518b6101200151604051602001610d519b9a99989796959493929190613892565b604051602081830303815290604052805190602001209050919050565b5f81604051602001610d51919061390c565b80471015610dc3576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810182905260440161052b565b5f5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610e0d576040519150601f19603f3d011682016040523d82523d5f602084013e610e12565b606091505b509150915081610e2557610e2581611eba565b50505050565b6040516001600160a01b0383811660248301526044820183905261046091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611efc565b60408401516001600160a01b03163014610ef65760408085015190517f30b078bf0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6060840151610f31576040517f58e8d18100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808501516001600160a01b03165f90815260068252604080822060608801518352909252205415610fab57602084015160608501516040517f9756f94d0000000000000000000000000000000000000000000000000000000081526001600160a01b039092166004830152602482015260440161052b565b6005546001600160a01b0316156110e4576020808501516001600160a01b03165f9081526007909152604081208054610fe390613739565b80601f016020809104026020016040519081016040528092919081815260200182805461100f90613739565b801561105a5780601f106110315761010080835404028352916020019161105a565b820191905f5260205f20905b81548152906001019060200180831161103d57829003601f168201915b505050505090505f815111156110e2576005546040517fdbd902180000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063dbd90218906110b59088908590600401613b29565b5f6040518083038186803b1580156110cb575f5ffd5b505afa1580156110dd573d5f5f3e3d5ffd5b505050505b505b5f6110ee85611f81565b9050801561118d57428560800151116111425760808501516040517f95d3dc22000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b8351421080159061118b5784516040517f2c39717b000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b505b8360400151518560c0015151146111d0576040517f9dca144800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8560c0015151811015611273575f8660c0015182815181106111f6576111f661376b565b60200260200101516020015190505f8660400151838151811061121b5761121b61376b565b6020026020010151905081811115611269576040517f8ffa61fa000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161052b565b50506001016111d2565b505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663680038c76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112d1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190613b4d565b90505f8160ff1687610120015111611310578160ff16611317565b8661012001515b90508087610140015151101561136b57610140870151516040517fcd0ce69900000000000000000000000000000000000000000000000000000000815261052b918391600401918252602082015260400190565b5f5f90505f60405180602001604052806113848b610ccb565b905290505f61139a61139583611fd0565b61200d565b90505f5b8a61014001515181101561152f575f6113d5838d610140015184815181106113c8576113c861376b565b6020026020010151612054565b9050846001600160a01b0316816001600160a01b031611611435576040517fce98854c0000000000000000000000000000000000000000000000000000000081526001600160a01b0380871660048301528216602482015260440161052b565b6040517f616556700000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015291955085915f917f000000000000000000000000000000000000000000000000000000000000000090911690636165567090602401602060405180830381865afa1580156114bb573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114df91906136fb565b1590508015611525576040517ff24706660000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505060010161139e565b505f6115486115426113958c8e336108ac565b8a612054565b6040517f163eed9e0000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301529192505f917f0000000000000000000000000000000000000000000000000000000000000000169063163eed9e90602401602060405180830381865afa1580156115ca573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ee91906136fb565b1580156115f9575088155b9050801561163e576040517f100158a80000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505050505050505050505050565b5f8260a001518060200190518101906116659190613c16565b90505f82602001518060200190518101906116809190613d93565b905061168c828261207c565b60208401516004545f916116a9916001600160a01b0316906122ed565b905046835f01510361170b575f5b836040015151811015611709575f846040015182815181106116db576116db61376b565b60200260200101519050611700815f01518860200151865f0151846020015187612371565b506001016116b7565b505b5f611715846123b1565b83516040517f7b4da5de0000000000000000000000000000000000000000000000000000000081529192506001600160a01b031690637b4da5de906117609089908990600401613efe565b5f604051808303815f87803b158015611777575f5ffd5b505af1158015611789573d5f5f3e3d5ffd5b50505050468460200151036119c8575f84606001515167ffffffffffffffff8111156117b7576117b7612de4565b6040519080825280602002602001820160405280156117e0578160200160208202803683370190505b5090505f5b856060015151811015611990575f866060015182815181106118095761180961376b565b602002602001015190505f611821825f01513061246d565b90505f8584815181106118365761183661376b565b602002602001015190508082101561188b576040517ff5ad5ab700000000000000000000000000000000000000000000000000000000815260048101859052602481018390526044810182905260640161052b565b611895818361371a565b8585815181106118a7576118a761376b565b6020026020010181815250505f886040015185815181106118ca576118ca61376b565b60200260200101519050808686815181106118e7576118e761376b565b6020026020010151101561195557848686815181106119085761190861376b565b60209081029190910101516040517ff0a43aa7000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044810182905260640161052b565b611980845f015185604001518888815181106119735761197361376b565b602002602001015161098b565b5050600190920191506117e59050565b506119bb8787836040516020016119a79190613f22565b604051602081830303815290604052612524565b6119c68787856125b7565b505b505050505050565b5f8260a001518060200190518101906119e99190613f34565b90506119f5818361263b565b60208301516004545f91611a12916001600160a01b0316906122ed565b90505f5b826020015151811015611a69575f83602001518281518110611a3a57611a3a61376b565b60200260200101519050611a60815f015187602001518360400151846020015187612371565b50600101611a16565b50604080515f815260208101909152611a859085908590612524565b610e258484836125b7565b5f8260a00151806020019051810190611aa99190613fc0565b9050611aba818385602001516126fc565b5f81602001515167ffffffffffffffff811115611ad957611ad9612de4565b604051908082528060200260200182016040528015611b0c57816020015b6060815260200190600190039081611af75790505b5090505f5b826020015151811015611b93575f83602001518281518110611b3557611b3561376b565b602090810291909101810151878201518151928201516040830151600454939550611b6d946001600160a01b0390941693919061279a565b838381518110611b7f57611b7f61376b565b602090810291909101015250600101611b11565b50611baa8484836040516020016119a7919061410a565b610e25848460016125b7565b606060ff8314611bd057611bc98361282d565b90506107cb565b818054611bdc90613739565b80601f0160208091040260200160405190810160405280929190818152602001828054611c0890613739565b8015611c535780601f10611c2a57610100808354040283529160200191611c53565b820191905f5260205f20905b815481529060010190602001808311611c3657829003601f168201915b5050505050905092915050565b5f5f825167ffffffffffffffff811115611c7c57611c7c612de4565b604051908082528060200260200182016040528015611ca5578160200160208202803683370190505b5090505f5b8351811015611d76577fe44101eb81f50b64d4005e31312ea7bafd6f81357f9dce4fe51d5f2dcb939da4848281518110611ce657611ce661376b565b60200260200101515f0151858381518110611d0357611d0361376b565b602002602001015160200151604051602001611d3b939291909283526001600160a01b03919091166020830152604082015260600190565b60405160208183030381529060405280519060200120828281518110611d6357611d6361376b565b6020908102919091010152600101611caa565b5080604051602001611d88919061390c565b60405160208183030381529060405280519060200120915050919050565b5f5f825167ffffffffffffffff811115611dc257611dc2612de4565b604051908082528060200260200182016040528015611deb578160200160208202803683370190505b5090505f5b8351811015611d76577ff7a396f96f3810db1cfde06de38d32fc29d56b63cf9d9aee2c10c91ef7273bb1848281518110611e2c57611e2c61376b565b60200260200101515f0151858381518110611e4957611e4961376b565b60200260200101516020015180519060200120604051602001611e7f939291909283526020830191909152604082015260600190565b60405160208183030381529060405280519060200120828281518110611ea757611ea761376b565b6020908102919091010152600101611df0565b805115611eca5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f60205f8451602086015f885af180611f1b576040513d5f823e3d81fd5b50505f513d91508115611f32578060011415611f3f565b6001600160a01b0384163b155b15610e25576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b80515f9060ff1615611f9557506001919050565b5f8260a00151806020019051810190611fae9190613c16565b90508060200151815f015103611fc75750600192915050565b51461492915050565b8051604080517f07094367b2db06e3dbb414cf947644f8e08067ea238c5f41cb2fa5fea5be628e6020820152908101919091525f90606001610d51565b5f6107cb61201961286a565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b5f5f5f5f6120628686612993565b92509250925061207282826129dc565b5090949350505050565b81515f904614801590612093575046836020015114155b905080156120b65760405163308a43dd60e11b815246600482015260240161052b565b826060015151826040015151146120f9576040517f1816c20200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8360600151518110156121f2575f8460600151828151811061211f5761211f61376b565b602002602001015190505f81604001519050306001600160a01b0316816001600160a01b03160361216e57604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b5f826020015190505f8660400151858151811061218d5761218d61376b565b60200260200101519050818110156121e2576040517f1fef5f3300000000000000000000000000000000000000000000000000000000815260048101869052602481018290526044810183905260640161052b565b5050600190920191506120fb9050565b5060208301518351146104605781516040517f7d7011020000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201525f917f00000000000000000000000000000000000000000000000000000000000000001690637d70110290602401602060405180830381865afa158015612280573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122a491906136fb565b1590508015610e255782516040517fdb489af40000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6040517f5e07a6e60000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301525f9190841690635e07a6e690602401602060405180830381865afa15801561234d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a891906136fb565b801561239557600454612390906001600160a01b031685878686612adf565b6123aa565b6123aa6001600160a01b038616858585612b38565b5050505050565b606081606001515167ffffffffffffffff8111156123d1576123d1612de4565b6040519080825280602002602001820160405280156123fa578160200160208202803683370190505b50905046826020015103612468575f5b82606001515181101561246657612441836060015182815181106124305761243061376b565b60200260200101515f01513061246d565b8282815181106124535761245361376b565b602090810291909101015260010161240a565b505b919050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036124a357506001600160a01b038116316107cb565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015612500573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc9919061411c565b5f5b8360e0015151811015610e25575f8460e00151828151811061254a5761254a61376b565b60200260200101519050845f015160ff16815f015186602001516001600160a01b03167fee2c98c99b683f71058b8744fea294a411482f07d55c98b392917e9286f22f1388888887602001516040516125a69493929190614133565b60405180910390a450600101612526565b6020830151335f5b8560c00151518110156119c8575f8660c0015182815181106125e3576125e361376b565b60200260200101515f01519050612604816001600160a01b03166103481490565b61263257612632818585896040015186815181106126245761262461376b565b602002602001015189612371565b506001016125bf565b8151461461265e5760405163308a43dd60e11b815246600482015260240161052b565b60208101515115612682576040516393c58e1160e01b815260040160405180910390fd5b5f5b826020015151811015610460575f836020015182815181106126a8576126a861376b565b6020026020010151604001519050306001600160a01b0316816001600160a01b0316036126f357604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b50600101612684565b8251461461271f5760405163308a43dd60e11b815246600482015260240161052b565b60208201515115612743576040516393c58e1160e01b815260040160405180910390fd5b600454612759906001600160a01b0316826122ed565b610460576040517fcaec9bac0000000000000000000000000000000000000000000000000000000081526001600160a01b038216600482015260240161052b565b60606128238663ff883fcf60e01b878787876040516024016127bf949392919061418a565b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612b71565b9695505050505050565b60605f61283983612be3565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156128c257507f000000000000000000000000000000000000000000000000000000000000000046145b156128ec57507f000000000000000000000000000000000000000000000000000000000000000090565b610bca604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f5f83516041036129ca576020840151604085015160608601515f1a6129bc88828585612c23565b9550955095505050506129d5565b505081515f91506002905b9250925092565b5f8260038111156129ef576129ef61377f565b036129f8575050565b6001826003811115612a0c57612a0c61377f565b03612a43576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826003811115612a5757612a5761377f565b03612a91576040517ffce698f70000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6003826003811115612aa557612aa561377f565b0361076a576040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6040516001600160a01b038086166024830152808516604483015283166064820152608481018290526119c89086907ff18d03cc000000000000000000000000000000000000000000000000000000009060a4016127bf565b6040516001600160a01b038481166024830152838116604483015260648201839052610e259186918216906323b872dd90608401610e58565b60605f5f846001600160a01b031684604051612b8d91906141cb565b5f60405180830381855af49150503d805f8114612bc5576040519150601f19603f3d011682016040523d82523d5f602084013e612bca565b606091505b5091509150612bda858383612ceb565b95945050505050565b5f60ff8216601f8111156107cb576040517fb3512b0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115612c5c57505f91506003905082612ce1565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015612cad573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116612cd857505f925060019150829050612ce1565b92505f91508190505b9450945094915050565b606082612d0057612cfb82611eba565b6103a8565b8151158015612d1757506001600160a01b0384163b155b15612d59576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b50806103a8565b508054612d6c90613739565b5f825580601f10612d7b575050565b601f0160209004905f5260205f209081019061039691905b80821115612da6575f8155600101612d93565b5090565b6001600160a01b0381168114610396575f5ffd5b803561246881612daa565b5f60208284031215612dd9575f5ffd5b81356103a881612daa565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b60405290565b6040805190810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051610160810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b6040516080810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612eb457612eb4612de4565b604052919050565b5f67ffffffffffffffff821115612ed557612ed5612de4565b50601f01601f191660200190565b5f82601f830112612ef2575f5ffd5b8135612f05612f0082612ebc565b612e8b565b818152846020838601011115612f19575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f67ffffffffffffffff821115612f4e57612f4e612de4565b5060051b60200190565b5f60608284031215612f68575f5ffd5b612f70612df8565b823581529050602082013567ffffffffffffffff811115612f8f575f5ffd5b612f9b84828501612ee3565b602083015250604082013567ffffffffffffffff811115612fba575f5ffd5b8201601f81018413612fca575f5ffd5b8035612fd8612f0082612f35565b8082825260208201915060208360051b850101925086831115612ff9575f5ffd5b6020840193505b8284101561301b578335825260209384019390910190613000565b60408501525091949350505050565b60ff81168114610396575f5ffd5b80356124688161302a565b5f82601f830112613052575f5ffd5b8135613060612f0082612f35565b8082825260208201915060208360061b860101925085831115613081575f5ffd5b602085015b838110156130cc576040818803121561309d575f5ffd5b6130a5612e21565b81356130b081612daa565b8152602082810135818301529084529290920191604001613086565b5095945050505050565b5f82601f8301126130e5575f5ffd5b81356130f3612f0082612f35565b8082825260208201915060208360051b860101925085831115613114575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613137575f5ffd5b86016040818903601f1901121561314c575f5ffd5b613154612e21565b60208201358152604082013567ffffffffffffffff811115613174575f5ffd5b6131838a602083860101612ee3565b6020830152508085525050602083019250602081019050613119565b5f82601f8301126131ae575f5ffd5b81356131bc612f0082612f35565b8082825260208201915060208360051b8601019250858311156131dd575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613200575f5ffd5b61320f886020838a0101612ee3565b845250602092830192016131e2565b5f610160828403121561322f575f5ffd5b613237612e44565b905061324282613038565b815261325060208301612dbe565b602082015261326160408301612dbe565b6040820152606082810135908201526080808301359082015260a082013567ffffffffffffffff811115613293575f5ffd5b61329f84828501612ee3565b60a08301525060c082013567ffffffffffffffff8111156132be575f5ffd5b6132ca84828501613043565b60c08301525060e082013567ffffffffffffffff8111156132e9575f5ffd5b6132f5848285016130d6565b60e08301525061010082013567ffffffffffffffff811115613315575f5ffd5b61332184828501612ee3565b61010083015250610120828101359082015261014082013567ffffffffffffffff81111561334d575f5ffd5b6133598482850161319f565b6101408301525092915050565b5f5f5f60608486031215613378575f5ffd5b833567ffffffffffffffff81111561338e575f5ffd5b61339a86828701612f58565b935050602084013567ffffffffffffffff8111156133b6575f5ffd5b6133c28682870161321e565b92505060408401356133d381612daa565b809150509250925092565b5f5f5f606084860312156133f0575f5ffd5b83356133fb81612daa565b9250602084013561340b81612daa565b929592945050506040919091013590565b5f6020828403121561342c575f5ffd5b813567ffffffffffffffff811115613442575f5ffd5b8201601f81018413613452575f5ffd5b8035613460612f0082612f35565b8082825260208201915060208360051b850101925086831115613481575f5ffd5b602084015b8381101561355757803567ffffffffffffffff8111156134a4575f5ffd5b85016060818a03601f190112156134b9575f5ffd5b6134c1612df8565b602082013567ffffffffffffffff8111156134da575f5ffd5b6134e98b60208386010161321e565b825250604082013567ffffffffffffffff811115613505575f5ffd5b6135148b602083860101612f58565b602083015250606082013567ffffffffffffffff811115613533575f5ffd5b6135428b602083860101612ee3565b60408301525084525060209283019201613486565b509695505050505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f8151808452602084019350602083015f5b828110156135c05781518652602095860195909101906001016135a2565b5093949350505050565b7fff000000000000000000000000000000000000000000000000000000000000008816815260e060208201525f61360460e0830189613562565b82810360408401526136168189613562565b90508660608401526001600160a01b03861660808401528460a084015282810360c08401526136458185613590565b9a9950505050505050505050565b602081525f6103a86020830184613562565b5f5f60408385031215613676575f5ffd5b823561368181612daa565b946020939093013593505050565b5f6020828403121561369f575f5ffd5b813567ffffffffffffffff8111156136b5575f5ffd5b6136c184828501612ee3565b949350505050565b5f602082840312156136d9575f5ffd5b813567ffffffffffffffff8111156136ef575f5ffd5b6136c18482850161321e565b5f6020828403121561370b575f5ffd5b815180151581146103a8575f5ffd5b818103818111156107cb57634e487b7160e01b5f52601160045260245ffd5b600181811c9082168061374d57607f821691505b60208210810361246657634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b601f82111561046057805f5260205f20601f840160051c810160208510156137b85750805b601f840160051c820191505b818110156123aa575f81556001016137c4565b815167ffffffffffffffff8111156137f1576137f1612de4565b613805816137ff8454613739565b84613793565b6020601f821160018114613837575f83156138205750848201515b5f19600385901b1c1916600184901b1784556123aa565b5f84815260208120601f198516915b828110156138665787850151825560209485019460019092019101613846565b508482101561388357868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8b815260ff8b1660208201526001600160a01b038a1660408201526001600160a01b03891660608201528760808201528660a08201528560c08201528460e0820152836101008201526101606101208201525f6138f3610160830185613562565b9050826101408301529c9b505050505050505050505050565b81515f90829060208501835b82811015613936578151845260209384019390910190600101613918565b509195945050505050565b5f8151808452602084019350602083015f5b828110156135c057815180516001600160a01b031687526020908101518188015260409096019590910190600101613953565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f1985840301885281518051845260208101519050604060208501526139d46040850182613562565b6020998a01999094509290920191506001016139a2565b50909695505050505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f19858403018852613a2f838351613562565b6020988901989093509190910190600101613a13565b805160ff1682525f6020820151613a6760208501826001600160a01b03169052565b506040820151613a8260408501826001600160a01b03169052565b50606082015160608401526080820151608084015260a082015161016060a0850152613ab2610160850182613562565b905060c083015184820360c0860152613acb8282613941565b91505060e083015184820360e0860152613ae58282613986565b915050610100830151848203610100860152613b018282613562565b915050610120830151610120850152610140830151848203610140860152612bda82826139f7565b604081525f613b3b6040830185613a45565b8281036020840152612bda8185613562565b5f60208284031215613b5d575f5ffd5b81516103a88161302a565b5f613b75612f0084612f35565b83815290506020810160608402830185811115613b90575f5ffd5b835b81811015613bee575f60608289031215613baa575f5ffd5b613bb2612df8565b90508151613bbf81612daa565b8152602082810151908201526040820151613bd981612daa565b60408201528352602090920191606001613b92565b5050509392505050565b5f82601f830112613c07575f5ffd5b6103a883835160208501613b68565b5f60208284031215613c26575f5ffd5b815167ffffffffffffffff811115613c3c575f5ffd5b820160808185031215613c4d575f5ffd5b613c55612e68565b8151815260208083015190820152604082015167ffffffffffffffff811115613c7c575f5ffd5b8201601f81018613613c8c575f5ffd5b8051613c9a612f0082612f35565b8082825260208201915060208360061b850101925088831115613cbb575f5ffd5b6020840193505b82841015613d0b576040848a031215613cd9575f5ffd5b613ce1612e21565b8451613cec81612daa565b8152602085810151818301529083526040909401939190910190613cc2565b6040850152505050606082015167ffffffffffffffff811115613d2c575f5ffd5b613d3886828501613bf8565b606083015250949350505050565b5f82601f830112613d55575f5ffd5b8151613d63612f0082612ebc565b818152846020838601011115613d77575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613da3575f5ffd5b815167ffffffffffffffff811115613db9575f5ffd5b820160608185031215613dca575f5ffd5b613dd2612df8565b8151613ddd81612daa565b8152602082015167ffffffffffffffff811115613df8575f5ffd5b613e0486828501613d46565b602083015250604082015167ffffffffffffffff811115613e23575f5ffd5b80830192505084601f830112613e37575f5ffd5b8151613e45612f0082612f35565b8082825260208201915060208360051b860101925087831115613e66575f5ffd5b6020850194505b82851015613e88578451825260209485019490910190613e6d565b6040840152509095945050505050565b805182525f602082015160606020850152613eb66060850182613562565b9050604083015184820360408601528181518084526020840191506020830193505f92505b808310156130cc5783518252602082019150602084019350600183019250613edb565b604081525f613f106040830185613a45565b8281036020840152612bda8185613e98565b602081525f6103a86020830184613590565b5f60208284031215613f44575f5ffd5b815167ffffffffffffffff811115613f5a575f5ffd5b820160408185031215613f6b575f5ffd5b613f73612e21565b81518152602082015167ffffffffffffffff811115613f90575f5ffd5b80830192505084601f830112613fa4575f5ffd5b613fb385835160208501613b68565b6020820152949350505050565b5f60208284031215613fd0575f5ffd5b815167ffffffffffffffff811115613fe6575f5ffd5b820160408185031215613ff7575f5ffd5b613fff612e21565b81518152602082015167ffffffffffffffff81111561401c575f5ffd5b80830192505084601f830112614030575f5ffd5b815161403e612f0082612f35565b8082825260208201915060208360051b86010192508783111561405f575f5ffd5b602085015b838110156140f957805167ffffffffffffffff811115614082575f5ffd5b86016060818b03601f19011215614097575f5ffd5b61409f612df8565b60208201516140ad81612daa565b8152604082015167ffffffffffffffff8111156140c8575f5ffd5b6140d78c602083860101613d46565b6020838101919091526060939093015160408301525084529283019201614064565b506020840152509095945050505050565b602081525f6103a860208301846139f7565b5f6020828403121561412c575f5ffd5b5051919050565b608081525f6141456080830187613a45565b82810360208401526141578187613e98565b9050828103604084015261416b8186613562565b9050828103606084015261417f8185613562565b979650505050505050565b6001600160a01b03851681526001600160a01b0384166020820152608060408201525f6141ba6080830185613562565b905082606083015295945050505050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220fae3247dd7a33c3cd300206de44d56570a43a7690ab25372a739d3c2704b371e64736f6c634300081c00336080604052348015600e575f5ffd5b506109a98061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80635e07a6e614610043578063f18d03cc1461006b578063ff883fcf14610080575b5f5ffd5b610056610051366004610636565b6100a0565b60405190151581526020015b60405180910390f35b61007e61007936600461064f565b6100ec565b005b61009361008e366004610703565b6101cb565b60405161006291906107d3565b5f816001600160a01b03163b5f036100b957505f919050565b6100c2826102b3565b156100cf57506001919050565b6100d882610364565b156100e557506001919050565b505f919050565b6100f5846102b3565b1561017d576040517fbeabacc80000000000000000000000000000000000000000000000000000000081526001600160a01b03848116600483015283811660248301526044820183905285169063beabacc8906064015f604051808303815f87803b158015610162575f5ffd5b505af1158015610174573d5f5f3e3d5ffd5b505050506101c5565b61018684610364565b1561019c57610197848484846103d3565b6101c5565b604051636b94637360e11b81526001600160a01b03851660048201526024015b60405180910390fd5b50505050565b60606101d6856102b3565b1561026d576040517f4ae000410000000000000000000000000000000000000000000000000000000081526001600160a01b03861690634ae0004190610224908790879087906004016107e5565b5f604051808303815f875af115801561023f573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526102669190810190610863565b90506102ab565b61027685610364565b1561028757610266858585856104a2565b604051636b94637360e11b81526001600160a01b03861660048201526024016101bc565b949350505050565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527ff44bac890000000000000000000000000000000000000000000000000000000060048201525f906001600160a01b038316906301ffc9a790602401602060405180830381865afa92505050801561034e575060408051601f3d908101601f1916820190925261034b918101906108a4565b60015b61035957505f919050565b92915050565b919050565b5f816001600160a01b031663e75235b86040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156103bf575060408051601f3d908101601f191682019092526103bc918101906108bd565b60015b6103ca57505f919050565b50600192915050565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b0384161461047f57604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261047a90859085905f6104a2565b61049b565b604080515f81526020810190915261049b9085908490846104a2565b5050505050565b60605f5f866001600160a01b0316635229073f8786885f6040518563ffffffff1660e01b81526004016104d894939291906108d4565b5f604051808303815f875af11580156104f3573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261051a9190810190610928565b9150915084515f146105365761053186838361054b565b610540565b61054082826105c3565b979650505050505050565b6060826105605761055b826105de565b6105bc565b815115801561057757506001600160a01b0384163b155b156105b9576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016101bc565b50805b9392505050565b6060826105d8576105d3826105de565b610359565b50919050565b8051156105ee5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461035f575f5ffd5b5f60208284031215610646575f5ffd5b6105bc82610620565b5f5f5f5f60808587031215610662575f5ffd5b61066b85610620565b935061067960208601610620565b925061068760408601610620565b9396929550929360600135925050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff811182821017156106d4576106d4610697565b604052919050565b5f67ffffffffffffffff8211156106f5576106f5610697565b50601f01601f191660200190565b5f5f5f5f60808587031215610716575f5ffd5b61071f85610620565b935061072d60208601610620565b9250604085013567ffffffffffffffff811115610748575f5ffd5b8501601f81018713610758575f5ffd5b803561076b610766826106dc565b6106ab565b81815288602083850101111561077f575f5ffd5b816020840160208301375f91810160200191909152949793965093946060013593505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6105bc60208301846107a5565b6001600160a01b0384168152606060208201525f61080660608301856107a5565b9050826040830152949350505050565b5f82601f830112610825575f5ffd5b8151610833610766826106dc565b818152846020838601011115610847575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215610873575f5ffd5b815167ffffffffffffffff811115610889575f5ffd5b6102ab84828501610816565b8051801515811461035f575f5ffd5b5f602082840312156108b4575f5ffd5b6105bc82610895565b5f602082840312156108cd575f5ffd5b5051919050565b6001600160a01b0385168152836020820152608060408201525f6108fb60808301856107a5565b90506002831061091957634e487b7160e01b5f52602160045260245ffd5b82606083015295945050505050565b5f5f60408385031215610939575f5ffd5b61094283610895565b9150602083015167ffffffffffffffff81111561095d575f5ffd5b61096985828601610816565b915050925092905056fea264697066735822122063c7f17d2676ae9340ccf0078bc40725749c3c4dd9198f82832bc411d2d1333764736f6c634300081c00330000000000000000000000006002825babc837776a08799404fa55f15dceda6b0000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b0000000000000000000000000000000000000000000000000000","id":1,"to":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","type":"ONCHAIN_INTERACTION","value":{"_kind":"bigint","value":"0"}},"type":"NETWORK_INTERACTION_REQUEST"} +{"futureId":"Create3Settler#ICreateX.deployCreate3","networkInteractionId":1,"nonce":2,"type":"TRANSACTION_PREPARE_SEND"} +{"futureId":"Create3Settler#ICreateX.deployCreate3","networkInteractionId":1,"nonce":2,"transaction":{"fees":{"maxFeePerGas":{"_kind":"bigint","value":"330105689"},"maxPriorityFeePerGas":{"_kind":"bigint","value":"26357"}},"hash":"0x70b453c357ad30c3d8629a9795b891150a72afbb169ef93c00443b162e394ae7"},"type":"TRANSACTION_SEND"} +{"futureId":"Create3Settler#ICreateX.deployCreate3","hash":"0x70b453c357ad30c3d8629a9795b891150a72afbb169ef93c00443b162e394ae7","networkInteractionId":1,"receipt":{"blockHash":"0xbe6d3f3c5213120d5e05c0738a66be132a51a3bc5299761bf64677d52a453ad4","blockNumber":24033735,"logs":[{"address":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","data":"0x","logIndex":442,"topics":["0x2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c9067","0x0000000000000000000000001a68cee754105b3f806c92f6f59e3175f0b0fc2c","0xd5d101dbdd8374fa4f730d105879acbe6c9855ed7be06a019574ba923a935d7d"]},{"address":"0x609d831C0068844e11eF85a273c7F356212Fd6D1","data":"0x","logIndex":443,"topics":["0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b"]},{"address":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","data":"0x","logIndex":444,"topics":["0x4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b511","0x000000000000000000000000609d831c0068844e11ef85a273c7f356212fd6d1"]}],"status":"SUCCESS"},"type":"TRANSACTION_CONFIRM"} +{"futureId":"Create3Settler#ICreateX.deployCreate3","result":{"type":"SUCCESS"},"type":"CALL_EXECUTION_STATE_COMPLETE"} +{"artifactId":"Create3Settler#ICreateX","dependencies":["Create3Settler#ICreateX.deployCreate3","Create3Settler#ICreateX"],"emitterAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","eventIndex":0,"eventName":"ContractCreation","futureId":"Create3Settler#ICreateX.ContractCreation.newContract.0","nameOrIndex":"newContract","result":"0x609d831C0068844e11eF85a273c7F356212Fd6D1","strategy":"basic","strategyConfig":{},"txToReadFrom":"0x70b453c357ad30c3d8629a9795b891150a72afbb169ef93c00443b162e394ae7","type":"READ_EVENT_ARGUMENT_EXECUTION_STATE_INITIALIZE"} +{"artifactId":"Create3Settler#Settler","contractAddress":"0x609d831C0068844e11eF85a273c7F356212Fd6D1","contractName":"Settler","dependencies":["Create3Settler#ICreateX.ContractCreation.newContract.0"],"futureId":"Create3Settler#Settler","futureType":"NAMED_ARTIFACT_CONTRACT_AT","strategy":"basic","strategyConfig":{},"type":"CONTRACT_AT_EXECUTION_STATE_INITIALIZE"} \ No newline at end of file From a20c7692e0b1a6ceeab3719350efff05ae9695c4 Mon Sep 17 00:00:00 2001 From: Facu Spagnuolo Date: Thu, 18 Dec 2025 17:43:38 -0300 Subject: [PATCH 12/39] chore: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a474ea6..49de975 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Mimic Protocol -

Blockchain automation protocol

+

Blockchain execution protocol

From ea6b0fb39e1e5d62ff0e2d52ad658bfd45108d51 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Mon, 22 Dec 2025 13:51:05 -0300 Subject: [PATCH 13/39] Check deadlines on macros instead of ix fn body consistently --- .../settler/src/instructions/add_axia_sig.rs | 11 +++++------ .../instructions/add_instructions_to_proposal.rs | 11 ++++++----- .../src/instructions/add_validator_sig.rs | 9 +++------ .../src/instructions/claim_stale_intent.rs | 10 ++-------- ...tale_proposal.rs => claim_stale_proposals.rs} | 6 +++--- .../settler/src/instructions/execute_proposal.rs | 5 +---- .../settler/src/instructions/extend_intent.rs | 1 + .../svm/programs/settler/src/instructions/mod.rs | 4 ++-- packages/svm/programs/settler/src/lib.rs | 6 +++--- packages/svm/sdks/settler/Settler.ts | 4 ++-- packages/svm/tests/settler.test.ts | 16 ++++++++-------- 11 files changed, 36 insertions(+), 47 deletions(-) rename packages/svm/programs/settler/src/instructions/{claim_stale_proposal.rs => claim_stale_proposals.rs} (88%) diff --git a/packages/svm/programs/settler/src/instructions/add_axia_sig.rs b/packages/svm/programs/settler/src/instructions/add_axia_sig.rs index afebef9..ab970ab 100644 --- a/packages/svm/programs/settler/src/instructions/add_axia_sig.rs +++ b/packages/svm/programs/settler/src/instructions/add_axia_sig.rs @@ -37,7 +37,11 @@ pub struct AddAxiaSig<'info> { pub axia_registry: Box>, /// CHECK: Any proposal - #[account(mut)] + #[account( + mut, + constraint = proposal.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::ProposalIsExpired, + constraint = proposal.is_final @ SettlerError::ProposalIsNotFinal, + )] pub proposal: Box>, /// CHECK: The address check is needed because otherwise @@ -54,11 +58,6 @@ pub fn add_axia_sig(ctx: Context) -> Result<()> { return Ok(()); } - let now = Clock::get()?.unix_timestamp as u64; - - require!(proposal.deadline > now, SettlerError::ProposalIsExpired); - require!(proposal.is_final, SettlerError::ProposalIsNotFinal); - // Get Ed25519 instruction let ed25519_ix: Instruction = get_instruction_relative(-1, &ctx.accounts.ix_sysvar)?; let ed25519_ix_args: Ed25519Args = get_args_from_ed25519_ix_data(&ed25519_ix.data)?; diff --git a/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs b/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs index 9755cee..d36a133 100644 --- a/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs @@ -18,7 +18,12 @@ pub struct AddInstructionsToProposal<'info> { realloc::zero = true, has_one = proposal_creator @ SettlerError::IncorrectProposalCreator )] - // Any proposal + + /// Any proposal + #[account( + constraint = proposal.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::ProposalIsExpired, + constraint = !proposal.is_final @ SettlerError::ProposalIsFinal + )] pub proposal: Box>, pub system_program: Program<'info, System>, @@ -29,12 +34,8 @@ pub fn add_instructions_to_proposal( more_instructions: Vec, finalize: bool, ) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; let proposal = &mut ctx.accounts.proposal; - require!(proposal.deadline > now, SettlerError::ProposalIsExpired); - require!(!proposal.is_final, SettlerError::ProposalIsFinal); - proposal.instructions.extend_from_slice(&more_instructions); if finalize { diff --git a/packages/svm/programs/settler/src/instructions/add_validator_sig.rs b/packages/svm/programs/settler/src/instructions/add_validator_sig.rs index f08c3b6..451dfe4 100644 --- a/packages/svm/programs/settler/src/instructions/add_validator_sig.rs +++ b/packages/svm/programs/settler/src/instructions/add_validator_sig.rs @@ -30,6 +30,7 @@ pub struct AddValidatorSig<'info> { // Any Intent #[account( mut, + constraint = intent.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::IntentIsExpired, constraint = intent.is_final @ SettlerError::IntentIsNotFinal )] pub intent: Box>, @@ -55,12 +56,6 @@ pub struct AddValidatorSig<'info> { } pub fn add_validator_sig(ctx: Context) -> Result<()> { - // Verify Intent is not expired - let now = Clock::get()?.unix_timestamp as u64; - let intent = &mut ctx.accounts.intent; - - require!(intent.deadline > now, SettlerError::IntentIsExpired); - // Get Ed25519 instruction let ed25519_ix: Instruction = get_instruction_relative(-1, &ctx.accounts.ix_sysvar)?; let ed25519_ix_args: Ed25519Args = get_args_from_ed25519_ix_data(&ed25519_ix.data)?; @@ -69,6 +64,8 @@ pub fn add_validator_sig(ctx: Context) -> Result<()> { check_ed25519_ix(&ed25519_ix)?; // Verify correct message was signed + let intent = &mut ctx.accounts.intent; + if ed25519_ix_args.msg != intent.intent_hash { return err!(SettlerError::SigVerificationFailed); } diff --git a/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs b/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs index bd0b2cd..1e4dbdc 100644 --- a/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs +++ b/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs @@ -11,17 +11,11 @@ pub struct ClaimStaleIntent<'info> { mut, close = intent_creator, has_one = intent_creator @ SettlerError::IncorrectIntentCreator, + constraint = intent.deadline < Clock::get()?.unix_timestamp as u64 @ SettlerError::IntentNotYetExpired )] pub intent: Box>, } -pub fn claim_stale_intent(ctx: Context) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; - - require!( - ctx.accounts.intent.deadline < now, - SettlerError::IntentNotYetExpired - ); - +pub fn claim_stale_intent(_ctx: Context) -> Result<()> { Ok(()) } diff --git a/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs b/packages/svm/programs/settler/src/instructions/claim_stale_proposals.rs similarity index 88% rename from packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs rename to packages/svm/programs/settler/src/instructions/claim_stale_proposals.rs index 25a7b9c..5c7fa55 100644 --- a/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/claim_stale_proposals.rs @@ -3,7 +3,7 @@ use anchor_lang::prelude::*; use crate::{errors::SettlerError, state::Proposal}; #[derive(Accounts)] -pub struct ClaimStaleProposal<'info> { +pub struct ClaimStaleProposals<'info> { #[account(mut)] pub proposal_creator: Signer<'info>, // @@ -17,8 +17,8 @@ pub struct ClaimStaleProposal<'info> { // pub proposal_n: Box>, } -pub fn claim_stale_proposal<'info>( - ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposal<'info>>, +pub fn claim_stale_proposals<'info>( + ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposals<'info>>, ) -> Result<()> { let now = Clock::get()?.unix_timestamp as u64; let proposal_creator = ctx.accounts.proposal_creator.to_account_info(); diff --git a/packages/svm/programs/settler/src/instructions/execute_proposal.rs b/packages/svm/programs/settler/src/instructions/execute_proposal.rs index a77fbbd..272a250 100644 --- a/packages/svm/programs/settler/src/instructions/execute_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/execute_proposal.rs @@ -33,6 +33,7 @@ pub struct ExecuteProposal<'info> { has_one = intent @ SettlerError::IncorrectIntentForProposal, has_one = proposal_creator @ SettlerError::IncorrectProposalCreator, constraint = proposal.is_signed @ SettlerError::ProposalIsNotSigned, + constraint = proposal.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::ProposalIsExpired, close = proposal_creator )] pub proposal: Box>, @@ -61,12 +62,8 @@ pub struct ExecuteProposal<'info> { } pub fn execute_proposal(ctx: Context) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; - let proposal = &ctx.accounts.proposal; let intent = &ctx.accounts.intent; - require!(proposal.deadline > now, SettlerError::ProposalIsExpired); - // TODO: Execute proposal // TODO: Validate execution diff --git a/packages/svm/programs/settler/src/instructions/extend_intent.rs b/packages/svm/programs/settler/src/instructions/extend_intent.rs index f14c596..f126e80 100644 --- a/packages/svm/programs/settler/src/instructions/extend_intent.rs +++ b/packages/svm/programs/settler/src/instructions/extend_intent.rs @@ -16,6 +16,7 @@ pub struct ExtendIntent<'info> { mut, has_one = intent_creator @ SettlerError::IncorrectIntentCreator, constraint = !intent.is_final @ SettlerError::IntentIsFinal, + constraint = intent.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::IntentIsExpired, realloc = Intent::extended_size(intent.to_account_info().data_len(), &more_data, &more_max_fees, &more_events)?, realloc::payer = intent_creator, diff --git a/packages/svm/programs/settler/src/instructions/mod.rs b/packages/svm/programs/settler/src/instructions/mod.rs index f335e7a..67fc5f2 100644 --- a/packages/svm/programs/settler/src/instructions/mod.rs +++ b/packages/svm/programs/settler/src/instructions/mod.rs @@ -3,7 +3,7 @@ pub mod add_instructions_to_proposal; pub mod add_validator_sig; pub mod change_whitelist_program; pub mod claim_stale_intent; -pub mod claim_stale_proposal; +pub mod claim_stale_proposals; pub mod create_intent; pub mod create_proposal; pub mod execute_proposal; @@ -16,7 +16,7 @@ pub use add_instructions_to_proposal::*; pub use add_validator_sig::*; pub use change_whitelist_program::*; pub use claim_stale_intent::*; -pub use claim_stale_proposal::*; +pub use claim_stale_proposals::*; pub use create_intent::*; pub use create_proposal::*; pub use execute_proposal::*; diff --git a/packages/svm/programs/settler/src/lib.rs b/packages/svm/programs/settler/src/lib.rs index 77058f3..d7fc045 100644 --- a/packages/svm/programs/settler/src/lib.rs +++ b/packages/svm/programs/settler/src/lib.rs @@ -40,10 +40,10 @@ pub mod settler { instructions::claim_stale_intent(ctx) } - pub fn claim_stale_proposal<'info>( - ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposal<'info>>, + pub fn claim_stale_proposals<'info>( + ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposals<'info>>, ) -> Result<()> { - instructions::claim_stale_proposal(ctx) + instructions::claim_stale_proposals(ctx) } pub fn create_intent( diff --git a/packages/svm/sdks/settler/Settler.ts b/packages/svm/sdks/settler/Settler.ts index 2d0978b..5ea00df 100644 --- a/packages/svm/sdks/settler/Settler.ts +++ b/packages/svm/sdks/settler/Settler.ts @@ -154,12 +154,12 @@ export default class SettlerSDK { return ix } - async claimStaleProposalIx( + async claimStaleProposalsIx( intentHashesHex: string[], solverPubkey?: web3.PublicKey ): Promise { const ix = await this.program.methods - .claimStaleProposal() + .claimStaleProposals() .accountsPartial({ proposalCreator: this.getSignerKey(), }) diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 9ef7418..573b3de 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -1787,7 +1787,7 @@ describe('Settler Program', () => { Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0 const proposalCreatorBalanceBefore = Number(provider.client.getBalance(proposalBefore.proposalCreator)) || 0 - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalsIx([intentHash]) await makeTxSignAndSend(solverProvider, ix) const proposalBalanceAfter = @@ -1828,7 +1828,7 @@ describe('Settler Program', () => { ) const proposalCreatorBalanceBefore = Number(provider.client.getBalance(solver.publicKey)) || 0 - const ix = await solverSdk.claimStaleProposalIx(intentHashes) + const ix = await solverSdk.claimStaleProposalsIx(intentHashes) await makeTxSignAndSend(solverProvider, ix) const proposalBalancesAfter = intentHashes.reduce( @@ -1860,7 +1860,7 @@ describe('Settler Program', () => { warpSeconds(provider, WARP_TIME_SHORT) - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalsIx([intentHash]) const res = await makeTxSignAndSend(solverProvider, ix) expectTransactionError(res, `Proposal not yet expired`) @@ -1873,7 +1873,7 @@ describe('Settler Program', () => { warpSeconds(provider, MEDIUM_DEADLINE) - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalsIx([intentHash]) const res = await makeTxSignAndSend(solverProvider, ix) expectTransactionError(res, `Proposal not yet expired`) @@ -1886,7 +1886,7 @@ describe('Settler Program', () => { warpSeconds(provider, EXPIRATION_TEST_DELAY_PLUS_ONE) - const ix = await maliciousSdk.claimStaleProposalIx([intentHash], solver.publicKey) + const ix = await maliciousSdk.claimStaleProposalsIx([intentHash], solver.publicKey) const res = await makeTxSignAndSend(maliciousProvider, ix) expectTransactionError(res, `Signer must be proposal creator`) @@ -1895,7 +1895,7 @@ describe('Settler Program', () => { it('cannot claim non-existent proposal', async () => { const intentHash = generateIntentHash() - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalsIx([intentHash]) const res = await makeTxSignAndSend(solverProvider, ix) expectTransactionError(res, `AccountNotInitialized`) @@ -1908,11 +1908,11 @@ describe('Settler Program', () => { warpSeconds(provider, DOUBLE_CLAIM_DELAY_PLUS_ONE) - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalsIx([intentHash]) await makeTxSignAndSend(solverProvider, ix) client.expireBlockhash() - const ix2 = await solverSdk.claimStaleProposalIx([intentHash]) + const ix2 = await solverSdk.claimStaleProposalsIx([intentHash]) const res = await makeTxSignAndSend(solverProvider, ix2) const errorMsg = res.toString() From fc51d1c73d2ad8808cc9d250251aeed8f66f5936 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Mon, 22 Dec 2025 15:15:33 -0300 Subject: [PATCH 14/39] Refactor if-return-err to require --- .../settler/src/instructions/add_axia_sig.rs | 14 ++++++++------ .../settler/src/instructions/add_validator_sig.rs | 11 ++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/add_axia_sig.rs b/packages/svm/programs/settler/src/instructions/add_axia_sig.rs index ab970ab..64d1e1c 100644 --- a/packages/svm/programs/settler/src/instructions/add_axia_sig.rs +++ b/packages/svm/programs/settler/src/instructions/add_axia_sig.rs @@ -66,14 +66,16 @@ pub fn add_axia_sig(ctx: Context) -> Result<()> { check_ed25519_ix(&ed25519_ix)?; // Verify correct message was signed - if ed25519_ix_args.msg != proposal.key().as_array() { - return err!(SettlerError::SigVerificationFailed); - } + require!( + ed25519_ix_args.msg == proposal.key().as_array(), + SettlerError::SigVerificationFailed + ); // Verify pubkey is whitelisted Axia - if ed25519_ix_args.pubkey != &ctx.accounts.axia_registry.entity_pubkey.to_bytes() { - return err!(SettlerError::AxiaNotWhitelisted); - } + require!( + ed25519_ix_args.pubkey == &ctx.accounts.axia_registry.entity_pubkey.to_bytes(), + SettlerError::AxiaNotWhitelisted + ); // Updates proposal as signed proposal.is_signed = true; diff --git a/packages/svm/programs/settler/src/instructions/add_validator_sig.rs b/packages/svm/programs/settler/src/instructions/add_validator_sig.rs index 451dfe4..f6fd619 100644 --- a/packages/svm/programs/settler/src/instructions/add_validator_sig.rs +++ b/packages/svm/programs/settler/src/instructions/add_validator_sig.rs @@ -56,6 +56,8 @@ pub struct AddValidatorSig<'info> { } pub fn add_validator_sig(ctx: Context) -> Result<()> { + let intent = &mut ctx.accounts.intent; + // Get Ed25519 instruction let ed25519_ix: Instruction = get_instruction_relative(-1, &ctx.accounts.ix_sysvar)?; let ed25519_ix_args: Ed25519Args = get_args_from_ed25519_ix_data(&ed25519_ix.data)?; @@ -64,11 +66,10 @@ pub fn add_validator_sig(ctx: Context) -> Result<()> { check_ed25519_ix(&ed25519_ix)?; // Verify correct message was signed - let intent = &mut ctx.accounts.intent; - - if ed25519_ix_args.msg != intent.intent_hash { - return err!(SettlerError::SigVerificationFailed); - } + require!( + ed25519_ix_args.msg == intent.intent_hash, + SettlerError::SigVerificationFailed + ); // Verify pubkey is a whitelisted Validator require_keys_eq!( From 804e2a46f42db804b09167ef66da8932b701a8eb Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 12:47:23 -0300 Subject: [PATCH 15/39] 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 16/39] 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 17/39] 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 18/39] 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 19/39] 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 20/39] 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 21/39] 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 e916fa172a3632c83096449fb8519a32d7b8e99e Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 15:53:27 -0300 Subject: [PATCH 22/39] Code review: adapt Settler to Controller changes --- packages/svm/idls/controller.json | 320 +++++++----------- packages/svm/programs/settler/src/errors.rs | 12 +- .../settler/src/instructions/create_intent.rs | 9 +- .../settler/src/instructions/initialize.rs | 2 +- .../programs/settler/src/instructions/mod.rs | 2 - .../src/instructions/set_paused_state.rs | 46 --- packages/svm/programs/settler/src/lib.rs | 6 +- .../settler/src/state/settler_settings.rs | 2 +- packages/svm/sdks/settler/Settler.ts | 6 +- packages/svm/tests/helpers/settler-helpers.ts | 16 +- packages/svm/tests/settler.test.ts | 21 +- 11 files changed, 150 insertions(+), 292 deletions(-) delete mode 100644 packages/svm/programs/settler/src/instructions/set_paused_state.rs diff --git a/packages/svm/idls/controller.json b/packages/svm/idls/controller.json index 71f8265..cba53ed 100644 --- a/packages/svm/idls/controller.json +++ b/packages/svm/idls/controller.json @@ -4,30 +4,36 @@ "name": "controller", "version": "0.1.0", "spec": "0.1.0", - "description": "Created with Anchor" + "description": "Manages allowlist for Mimic Protocol entities" }, "instructions": [ { - "name": "initialize", + "name": "close_entity_registry", "discriminator": [ - 175, - 175, - 109, - 31, - 13, - 152, - 155, - 237 + 107, + 232, + 103, + 154, + 200, + 121, + 184, + 37 ], "accounts": [ { - "name": "deployer", + "name": "admin", "writable": true, - "signer": true + "signer": true, + "relations": [ + "global_settings" + ] + }, + { + "name": "entity_registry", + "writable": true }, { "name": "global_settings", - "writable": true, "pda": { "seeds": [ { @@ -60,26 +66,30 @@ ], "args": [ { - "name": "admin", - "type": "pubkey" + "name": "entity_type", + "type": { + "defined": { + "name": "EntityType" + } + } }, { - "name": "proposed_admin_cooldown", - "type": "u64" + "name": "entity_pubkey", + "type": "pubkey" } ] }, { - "name": "propose_admin", + "name": "create_entity_registry", "discriminator": [ - 121, - 214, - 199, - 212, - 87, - 39, - 117, - 234 + 59, + 253, + 73, + 126, + 171, + 188, + 172, + 156 ], "accounts": [ { @@ -90,9 +100,12 @@ "global_settings" ] }, + { + "name": "entity_registry", + "writable": true + }, { "name": "global_settings", - "writable": true, "pda": { "seeds": [ { @@ -117,42 +130,48 @@ } ] } + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" } ], "args": [ { - "name": "proposed_admin", + "name": "entity_type", + "type": { + "defined": { + "name": "EntityType" + } + } + }, + { + "name": "entity_pubkey", "type": "pubkey" } ] }, { - "name": "set_entity_allowlist_status", + "name": "initialize", "discriminator": [ - 100, - 20, - 23, - 73, - 220, - 118, - 179, - 50 + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 ], "accounts": [ { - "name": "admin", + "name": "deployer", "writable": true, - "signer": true, - "relations": [ - "global_settings" - ] - }, - { - "name": "entity_registry", - "writable": true + "signer": true }, { "name": "global_settings", + "writable": true, "pda": { "seeds": [ { @@ -185,44 +204,31 @@ ], "args": [ { - "name": "entity_type", - "type": { - "defined": { - "name": "EntityType" - } - } - }, - { - "name": "entity_pubkey", + "name": "admin", "type": "pubkey" - }, - { - "name": "status", - "type": { - "defined": { - "name": "AllowlistStatus" - } - } } ] }, { - "name": "set_proposed_admin", + "name": "set_admin", "discriminator": [ - 160, - 170, - 199, - 240, - 246, - 244, - 199, - 2 + 251, + 163, + 0, + 52, + 91, + 194, + 187, + 92 ], "accounts": [ { - "name": "proposed_admin", + "name": "admin", "writable": true, - "signer": true + "signer": true, + "relations": [ + "global_settings" + ] }, { "name": "global_settings", @@ -253,7 +259,12 @@ } } ], - "args": [] + "args": [ + { + "name": "new_admin", + "type": "pubkey" + } + ] } ], "accounts": [ @@ -286,29 +297,29 @@ ], "events": [ { - "name": "SetEntityAllowlistStatusEvent", + "name": "CloseEntityRegistryEvent", "discriminator": [ - 137, - 194, - 109, - 101, - 80, - 30, - 4, - 114 + 133, + 120, + 94, + 102, + 119, + 102, + 166, + 228 ] }, { - "name": "SetProposedAdminEvent", + "name": "SetAdminEvent", "discriminator": [ - 153, - 83, - 248, - 103, + 240, + 117, + 204, + 254, + 89, + 150, 132, - 126, - 171, - 96 + 94 ] } ], @@ -322,41 +333,11 @@ "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", + "name": "CloseEntityRegistryEvent", "type": { "kind": "struct", "fields": [ @@ -373,19 +354,27 @@ "type": "pubkey" }, { - "name": "status", + "name": "timestamp", + "type": "u64" + } + ] + } + }, + { + "name": "EntityRegistry", + "type": { + "kind": "struct", + "fields": [ + { + "name": "entity_type", "type": { "defined": { - "name": "AllowlistStatus" + "name": "EntityType" } } }, { - "name": "last_update", - "type": "u64" - }, - { - "name": "updated_by", + "name": "entity_pubkey", "type": "pubkey" }, { @@ -424,20 +413,6 @@ "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" @@ -446,70 +421,17 @@ } }, { - "name": "SetEntityAllowlistStatusEvent", + "name": "SetAdminEvent", "type": { "kind": "struct", "fields": [ { - "name": "entity_type", - "type": { - "defined": { - "name": "EntityType" - } - } - }, - { - "name": "entity_pubkey", + "name": "new_admin", "type": "pubkey" }, - { - "name": "status", - "type": { - "defined": { - "name": "AllowlistStatus" - } - } - }, { "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": "AllowlistStatus", - "repr": { - "kind": "rust" - }, - "type": { - "kind": "enum", - "variants": [ - { - "name": "Allowlisted" - }, - { - "name": "Blacklisted" } ] } diff --git a/packages/svm/programs/settler/src/errors.rs b/packages/svm/programs/settler/src/errors.rs index ae62bae..0c008e0 100644 --- a/packages/svm/programs/settler/src/errors.rs +++ b/packages/svm/programs/settler/src/errors.rs @@ -5,20 +5,20 @@ pub enum SettlerError { #[msg("Only Deployer can call this instruction")] OnlyDeployer, - #[msg("Only a whitelisted solver can call this instruction")] + #[msg("Only an allowlisted solver can call this instruction")] OnlySolver, - #[msg("Provided Axia address is not whitelisted")] - AxiaNotWhitelisted, + #[msg("Provided Axia address is not allowlisted")] + AxiaNotAllowlisted, - #[msg("Only a whitelisted validator can call this instruction")] + #[msg("Only a allowlisted validator can call this instruction")] OnlyValidator, #[msg("No max fees provided")] NoMaxFees, - #[msg("Validator is not whitelisted")] - ValidatorNotWhitelisted, + #[msg("Validator is not allowlisted")] + ValidatorNotAllowlisted, #[msg("Signer must be intent creator")] IncorrectIntentCreator, diff --git a/packages/svm/programs/settler/src/instructions/create_intent.rs b/packages/svm/programs/settler/src/instructions/create_intent.rs index aa62b6d..781a326 100644 --- a/packages/svm/programs/settler/src/instructions/create_intent.rs +++ b/packages/svm/programs/settler/src/instructions/create_intent.rs @@ -1,13 +1,10 @@ use anchor_lang::prelude::*; use crate::{ + controller::{accounts::EntityRegistry, types::EntityType}, errors::SettlerError, state::Intent, types::{IntentEvent, OpType, TokenFee}, - whitelist::{ - accounts::EntityRegistry, - types::{EntityType, WhitelistStatus}, - }, }; #[derive(Accounts)] @@ -20,9 +17,7 @@ pub struct CreateIntent<'info> { #[account( seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], bump = solver_registry.bump, - seeds::program = crate::whitelist::ID, - constraint = - solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + seeds::program = crate::controller::ID )] pub solver_registry: Box>, diff --git a/packages/svm/programs/settler/src/instructions/initialize.rs b/packages/svm/programs/settler/src/instructions/initialize.rs index 7aa463c..dd967a6 100644 --- a/packages/svm/programs/settler/src/instructions/initialize.rs +++ b/packages/svm/programs/settler/src/instructions/initialize.rs @@ -29,7 +29,7 @@ pub fn initialize(ctx: Context) -> Result<()> { let settler_settings = &mut ctx.accounts.settler_settings; - settler_settings.whitelist_program = crate::whitelist::ID; + settler_settings.controller_program = crate::controller::ID; settler_settings.is_paused = false; settler_settings.bump = ctx.bumps.settler_settings; diff --git a/packages/svm/programs/settler/src/instructions/mod.rs b/packages/svm/programs/settler/src/instructions/mod.rs index a1c371a..a7aa8cc 100644 --- a/packages/svm/programs/settler/src/instructions/mod.rs +++ b/packages/svm/programs/settler/src/instructions/mod.rs @@ -2,10 +2,8 @@ pub mod claim_stale_intent; pub mod create_intent; pub mod extend_intent; pub mod initialize; -pub mod set_paused_state; pub use claim_stale_intent::*; pub use create_intent::*; pub use extend_intent::*; pub use initialize::*; -pub use set_paused_state::*; diff --git a/packages/svm/programs/settler/src/instructions/set_paused_state.rs b/packages/svm/programs/settler/src/instructions/set_paused_state.rs deleted file mode 100644 index ca8b52e..0000000 --- a/packages/svm/programs/settler/src/instructions/set_paused_state.rs +++ /dev/null @@ -1,46 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::state::SettlerSettings; - -#[derive(Accounts)] -pub struct SetPausedState<'info> { - #[account(mut)] - pub admin: Signer<'info>, - - #[account( - seeds = [b"global-settings"], - bump = whitelist_program_global_settings.bump, - seeds::program = settler_settings.whitelist_program, - has_one = admin @ crate::whitelist::errors::ProgramError::OnlyAdmin - )] - pub whitelist_program_global_settings: - Box>, - - #[account( - mut, - seeds = [b"settler-settings"], - bump = settler_settings.bump - )] - pub settler_settings: Box>, -} - -pub fn set_paused_state(ctx: Context, is_paused: bool) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; - - ctx.accounts.settler_settings.is_paused = is_paused; - - emit!(SetPausedStateEvent { - changed_at: now, - changed_by: ctx.accounts.admin.key(), - is_paused, - }); - - Ok(()) -} - -#[event] -pub struct SetPausedStateEvent { - changed_at: u64, - changed_by: Pubkey, - is_paused: bool, -} diff --git a/packages/svm/programs/settler/src/lib.rs b/packages/svm/programs/settler/src/lib.rs index 50db194..c9052c3 100644 --- a/packages/svm/programs/settler/src/lib.rs +++ b/packages/svm/programs/settler/src/lib.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; declare_id!("HbNt35Ng8aM4NUy39evpCQqXEC4Nmaq16ewY8dyNF6NF"); -declare_program!(whitelist); +declare_program!(controller); pub mod constants; pub mod errors; @@ -61,8 +61,4 @@ pub mod settler { pub fn initialize(ctx: Context) -> Result<()> { instructions::initialize(ctx) } - - pub fn set_paused_state(ctx: Context, is_paused: bool) -> Result<()> { - instructions::set_paused_state(ctx, is_paused) - } } diff --git a/packages/svm/programs/settler/src/state/settler_settings.rs b/packages/svm/programs/settler/src/state/settler_settings.rs index 01a3942..e59b592 100644 --- a/packages/svm/programs/settler/src/state/settler_settings.rs +++ b/packages/svm/programs/settler/src/state/settler_settings.rs @@ -3,7 +3,7 @@ use anchor_lang::prelude::*; #[account] #[derive(InitSpace)] pub struct SettlerSettings { - pub whitelist_program: Pubkey, + pub controller_program: Pubkey, pub is_paused: bool, pub bump: u8, } diff --git a/packages/svm/sdks/settler/Settler.ts b/packages/svm/sdks/settler/Settler.ts index 6925901..1b4beaa 100644 --- a/packages/svm/sdks/settler/Settler.ts +++ b/packages/svm/sdks/settler/Settler.ts @@ -1,9 +1,9 @@ import { BN, IdlTypes, Program, Provider, web3 } from '@coral-xyz/anchor' +import * as ControllerIDL from '../../target/idl/controller.json' import * as SettlerIDL from '../../target/idl/settler.json' -import * as WhitelistIDL from '../../target/idl/whitelist.json' import { Settler } from '../../target/types/settler' -import { EntityType } from '../whitelist/Whitelist' +import { EntityType } from '../controller/Controller' import { CreateIntentParams, ExtendIntentParams, IntentEvent, OpType, TokenFee } from './types' type TokenFeeAnchor = { @@ -121,7 +121,7 @@ export default class SettlerSDK { getEntityRegistryPubkey(entityType: EntityType, entityPubkey: web3.PublicKey): web3.PublicKey { return web3.PublicKey.findProgramAddressSync( [Buffer.from('entity-registry'), Buffer.from([entityType]), entityPubkey.toBuffer()], - new web3.PublicKey(WhitelistIDL.address) + new web3.PublicKey(ControllerIDL.address) )[0] } diff --git a/packages/svm/tests/helpers/settler-helpers.ts b/packages/svm/tests/helpers/settler-helpers.ts index 02ad7d9..7c4fc73 100644 --- a/packages/svm/tests/helpers/settler-helpers.ts +++ b/packages/svm/tests/helpers/settler-helpers.ts @@ -3,9 +3,9 @@ import { LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' import { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm' +import ControllerSDK, { EntityType } from '../../sdks/controller/Controller' import SettlerSDK from '../../sdks/settler/Settler' import { CreateIntentParams, IntentEvent, OpType, TokenFee } from '../../sdks/settler/types' -import WhitelistSDK, { EntityType, WhitelistStatus } from '../../sdks/whitelist/Whitelist' import { makeTxSignAndSend } from '../utils' import { DEFAULT_DATA_HEX, @@ -125,21 +125,17 @@ export async function createValidatedIntent( } /** - * Create a whitelisted entity (validator, axia, or solver) + * Creates an allowlisted entity (validator, axia, or solver) */ -export async function createWhitelistedEntity( - whitelistSdk: WhitelistSDK, +export async function createAllowlistedEntity( + controllerSdk: ControllerSDK, provider: LiteSVMProvider, entityType: EntityType, entityKeypair?: Keypair ): Promise { const entity = entityKeypair || Keypair.generate() - const whitelistIx = await whitelistSdk.setEntityWhitelistStatusIx( - entityType, - entity.publicKey, - WhitelistStatus.Whitelisted - ) - await makeTxSignAndSend(provider, whitelistIx) + const allowlistIx = await controllerSdk.createEntityRegistryIx(entityType, entity.publicKey) + await makeTxSignAndSend(provider, allowlistIx) return entity } diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 1e011c9..696adb2 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -10,11 +10,11 @@ import { LiteSVM } from 'litesvm' import os from 'os' import path from 'path' +import ControllerSDK, { EntityType } from '../sdks/controller/Controller' import SettlerSDK from '../sdks/settler/Settler' import { OpType } from '../sdks/settler/types' -import WhitelistSDK, { EntityType, WhitelistStatus } from '../sdks/whitelist/Whitelist' +import * as ControllerIDL from '../target/idl/controller.json' import * as SettlerIDL from '../target/idl/settler.json' -import * as WhitelistIDL from '../target/idl/whitelist.json' import { Settler } from '../target/types/settler' import { ACCOUNT_CLOSE_FEE, @@ -60,7 +60,7 @@ describe('Settler Program', () => { let maliciousSdk: SettlerSDK let solverSdk: SettlerSDK - let whitelistSdk: WhitelistSDK + let controllerSdk: ControllerSDK before(async () => { admin = Keypair.fromSecretKey( @@ -85,13 +85,10 @@ describe('Settler Program', () => { provider.client.airdrop(malicious.publicKey, BigInt(100_000_000_000)) provider.client.airdrop(solver.publicKey, BigInt(100_000_000_000)) - // Initialize Whitelist and whitelist Solver - whitelistSdk = new WhitelistSDK(provider) - await makeTxSignAndSend(provider, await whitelistSdk.initializeIx(admin.publicKey, 1)) - await makeTxSignAndSend( - provider, - await whitelistSdk.setEntityWhitelistStatusIx(EntityType.Solver, solver.publicKey, WhitelistStatus.Whitelisted) - ) + // Initialize Controller and add Solver to allowlist + controllerSdk = new ControllerSDK(provider) + await makeTxSignAndSend(provider, await controllerSdk.initializeIx(admin.publicKey)) + await makeTxSignAndSend(provider, await controllerSdk.createEntityRegistryIx(EntityType.Solver, solver.publicKey)) }) beforeEach(() => { @@ -112,7 +109,7 @@ describe('Settler Program', () => { await makeTxSignAndSend(provider, ix) const settings = await program.account.settlerSettings.fetch(sdk.getSettlerSettingsPubkey()) - expect(settings.whitelistProgram.toString()).to.be.eq(WhitelistIDL.address) + expect(settings.controllerProgram.toString()).to.be.eq(ControllerIDL.address) expect(settings.isPaused).to.be.false }) @@ -252,7 +249,7 @@ describe('Settler Program', () => { expect(intent.isFinal).to.be.false }) - it('cannot create intent if not whitelisted solver', async () => { + it('cannot create intent if not allowlisted solver', async () => { const intentHash = generateIntentHash() const nonce = generateNonce() const user = Keypair.generate().publicKey From a1eb61d5f161ed973610fe267e820372fe03c4c3 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 16:09:19 -0300 Subject: [PATCH 23/39] Code review: rename controller::GlobalSettings to ControllerSettings, plus other suggestions --- packages/svm/idls/controller.json | 58 +++++++++---------- .../src/instructions/close_entity_registry.rs | 8 +-- .../instructions/create_entity_registry.rs | 8 +-- .../controller/src/instructions/initialize.rs | 14 ++--- .../controller/src/instructions/set_admin.rs | 12 ++-- ...bal_settings.rs => controller_settings.rs} | 2 +- .../svm/programs/controller/src/state/mod.rs | 4 +- .../src/instructions/claim_stale_intent.rs | 6 +- .../settler/src/instructions/create_intent.rs | 7 +-- .../settler/src/instructions/extend_intent.rs | 8 +-- .../settler/src/state/fulfilled_intent.rs | 4 +- .../svm/programs/settler/src/state/intent.rs | 12 ++-- .../svm/programs/settler/src/types/op_type.rs | 6 +- packages/svm/sdks/controller/Controller.ts | 20 +++---- packages/svm/sdks/settler/Settler.ts | 4 +- packages/svm/sdks/settler/types.ts | 15 +++-- packages/svm/tests/controller.test.ts | 6 +- packages/svm/tests/settler.test.ts | 23 ++++---- 18 files changed, 105 insertions(+), 112 deletions(-) rename packages/svm/programs/controller/src/state/{global_settings.rs => controller_settings.rs} (76%) diff --git a/packages/svm/idls/controller.json b/packages/svm/idls/controller.json index cba53ed..e4494a5 100644 --- a/packages/svm/idls/controller.json +++ b/packages/svm/idls/controller.json @@ -268,6 +268,19 @@ } ], "accounts": [ + { + "name": "ControllerSettings", + "discriminator": [ + 15, + 70, + 199, + 238, + 59, + 33, + 179, + 47 + ] + }, { "name": "EntityRegistry", "discriminator": [ @@ -280,19 +293,6 @@ 158, 163 ] - }, - { - "name": "GlobalSettings", - "discriminator": [ - 109, - 67, - 50, - 55, - 2, - 20, - 148, - 62 - ] } ], "events": [ @@ -360,6 +360,22 @@ ] } }, + { + "name": "ControllerSettings", + "type": { + "kind": "struct", + "fields": [ + { + "name": "admin", + "type": "pubkey" + }, + { + "name": "bump", + "type": "u8" + } + ] + } + }, { "name": "EntityRegistry", "type": { @@ -404,22 +420,6 @@ ] } }, - { - "name": "GlobalSettings", - "type": { - "kind": "struct", - "fields": [ - { - "name": "admin", - "type": "pubkey" - }, - { - "name": "bump", - "type": "u8" - } - ] - } - }, { "name": "SetAdminEvent", "type": { diff --git a/packages/svm/programs/controller/src/instructions/close_entity_registry.rs b/packages/svm/programs/controller/src/instructions/close_entity_registry.rs index 91390bf..d14903d 100644 --- a/packages/svm/programs/controller/src/instructions/close_entity_registry.rs +++ b/packages/svm/programs/controller/src/instructions/close_entity_registry.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use crate::{ errors::ControllerError, - state::{EntityRegistry, GlobalSettings}, + state::{ControllerSettings, EntityRegistry}, types::EntityType, }; @@ -21,11 +21,11 @@ pub struct CloseEntityRegistry<'info> { pub entity_registry: Box>, #[account( - seeds = [b"global-settings"], - bump = global_settings.bump, + seeds = [b"controller-settings"], + bump = controller_settings.bump, has_one = admin @ ControllerError::OnlyAdmin )] - pub global_settings: Box>, + pub controller_settings: Box>, pub system_program: Program<'info, System>, } 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 d328cc6..cbd0aba 100644 --- a/packages/svm/programs/controller/src/instructions/create_entity_registry.rs +++ b/packages/svm/programs/controller/src/instructions/create_entity_registry.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use crate::{ errors::ControllerError, - state::{EntityRegistry, GlobalSettings}, + state::{ControllerSettings, EntityRegistry}, types::EntityType, }; @@ -22,11 +22,11 @@ pub struct CreateEntityRegistry<'info> { pub entity_registry: Box>, #[account( - seeds = [b"global-settings"], - bump = global_settings.bump, + seeds = [b"controller-settings"], + bump = controller_settings.bump, has_one = admin @ ControllerError::OnlyAdmin )] - pub global_settings: Box>, + pub controller_settings: Box>, pub system_program: Program<'info, System>, } diff --git a/packages/svm/programs/controller/src/instructions/initialize.rs b/packages/svm/programs/controller/src/instructions/initialize.rs index 094cd0a..b565f07 100644 --- a/packages/svm/programs/controller/src/instructions/initialize.rs +++ b/packages/svm/programs/controller/src/instructions/initialize.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use std::str::FromStr; -use crate::{constants::DEPLOYER_KEY, errors::ControllerError, state::GlobalSettings}; +use crate::{constants::DEPLOYER_KEY, errors::ControllerError, state::ControllerSettings}; #[derive(Accounts)] pub struct Initialize<'info> { @@ -10,12 +10,12 @@ pub struct Initialize<'info> { #[account( init, - seeds = [b"global-settings"], + seeds = [b"controller-settings"], bump, payer = deployer, - space = 8 + GlobalSettings::INIT_SPACE + space = 8 + ControllerSettings::INIT_SPACE )] - pub global_settings: Box>, + pub controller_settings: Box>, pub system_program: Program<'info, System>, } @@ -27,10 +27,10 @@ pub fn initialize(ctx: Context, admin: Pubkey) -> Result<()> { ControllerError::OnlyDeployer, ); - let global_settings = &mut ctx.accounts.global_settings; + let controller_settings = &mut ctx.accounts.controller_settings; - global_settings.admin = admin; - global_settings.bump = ctx.bumps.global_settings; + controller_settings.admin = admin; + controller_settings.bump = ctx.bumps.controller_settings; Ok(()) } diff --git a/packages/svm/programs/controller/src/instructions/set_admin.rs b/packages/svm/programs/controller/src/instructions/set_admin.rs index dc59043..8e44a34 100644 --- a/packages/svm/programs/controller/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::ControllerError, state::GlobalSettings}; +use crate::{errors::ControllerError, state::ControllerSettings}; #[derive(Accounts)] pub struct SetAdmin<'info> { @@ -9,17 +9,17 @@ pub struct SetAdmin<'info> { #[account( mut, - seeds = [b"global-settings"], - bump = global_settings.bump, + seeds = [b"controller-settings"], + bump = controller_settings.bump, has_one = admin @ ControllerError::OnlyAdmin )] - pub global_settings: Box>, + pub controller_settings: Box>, } pub fn set_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { - let global_settings = &mut ctx.accounts.global_settings; + let controller_settings = &mut ctx.accounts.controller_settings; - global_settings.admin = new_admin; + controller_settings.admin = new_admin; emit!(SetAdminEvent { new_admin, diff --git a/packages/svm/programs/controller/src/state/global_settings.rs b/packages/svm/programs/controller/src/state/controller_settings.rs similarity index 76% rename from packages/svm/programs/controller/src/state/global_settings.rs rename to packages/svm/programs/controller/src/state/controller_settings.rs index 6027636..3c8d463 100644 --- a/packages/svm/programs/controller/src/state/global_settings.rs +++ b/packages/svm/programs/controller/src/state/controller_settings.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; #[account] #[derive(InitSpace)] -pub struct GlobalSettings { +pub struct ControllerSettings { pub admin: Pubkey, pub bump: u8, } diff --git a/packages/svm/programs/controller/src/state/mod.rs b/packages/svm/programs/controller/src/state/mod.rs index e0bef0c..1ad604e 100644 --- a/packages/svm/programs/controller/src/state/mod.rs +++ b/packages/svm/programs/controller/src/state/mod.rs @@ -1,5 +1,5 @@ +pub mod controller_settings; pub mod entity_registry; -pub mod global_settings; +pub use controller_settings::*; pub use entity_registry::*; -pub use global_settings::*; diff --git a/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs b/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs index bd0b2cd..d5b397e 100644 --- a/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs +++ b/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs @@ -5,12 +5,12 @@ use crate::{errors::SettlerError, state::Intent}; #[derive(Accounts)] pub struct ClaimStaleIntent<'info> { #[account(mut)] - pub intent_creator: Signer<'info>, + pub creator: Signer<'info>, #[account( mut, - close = intent_creator, - has_one = intent_creator @ SettlerError::IncorrectIntentCreator, + close = creator, + has_one = creator @ SettlerError::IncorrectIntentCreator, )] pub intent: Box>, } diff --git a/packages/svm/programs/settler/src/instructions/create_intent.rs b/packages/svm/programs/settler/src/instructions/create_intent.rs index 781a326..7f3c26e 100644 --- a/packages/svm/programs/settler/src/instructions/create_intent.rs +++ b/packages/svm/programs/settler/src/instructions/create_intent.rs @@ -65,14 +65,13 @@ pub fn create_intent( intent.op = op; intent.user = user; - intent.intent_creator = ctx.accounts.solver.key(); - intent.intent_hash = intent_hash; + intent.creator = ctx.accounts.solver.key(); + intent.hash = intent_hash; intent.nonce = nonce; intent.deadline = deadline; intent.min_validations = min_validations; - intent.validations = 0; intent.is_final = is_final; - intent.intent_data = data; + intent.data = data; intent.max_fees = max_fees; intent.events = events; intent.validators = vec![]; diff --git a/packages/svm/programs/settler/src/instructions/extend_intent.rs b/packages/svm/programs/settler/src/instructions/extend_intent.rs index f14c596..46b8f05 100644 --- a/packages/svm/programs/settler/src/instructions/extend_intent.rs +++ b/packages/svm/programs/settler/src/instructions/extend_intent.rs @@ -10,15 +10,15 @@ use crate::{ #[instruction(more_data: Option>, more_max_fees: Option>, more_events: Option>)] pub struct ExtendIntent<'info> { #[account(mut)] - pub intent_creator: Signer<'info>, + pub creator: Signer<'info>, #[account( mut, - has_one = intent_creator @ SettlerError::IncorrectIntentCreator, + has_one = creator @ SettlerError::IncorrectIntentCreator, constraint = !intent.is_final @ SettlerError::IntentIsFinal, realloc = Intent::extended_size(intent.to_account_info().data_len(), &more_data, &more_max_fees, &more_events)?, - realloc::payer = intent_creator, + realloc::payer = creator, realloc::zero = true )] pub intent: Box>, @@ -36,7 +36,7 @@ pub fn extend_intent( let intent = &mut ctx.accounts.intent; if let Some(_more_data) = more_data { - intent.intent_data.extend_from_slice(&_more_data); + intent.data.extend_from_slice(&_more_data); } if let Some(_more_max_fees) = more_max_fees { diff --git a/packages/svm/programs/settler/src/state/fulfilled_intent.rs b/packages/svm/programs/settler/src/state/fulfilled_intent.rs index 812a6e2..21e3452 100644 --- a/packages/svm/programs/settler/src/state/fulfilled_intent.rs +++ b/packages/svm/programs/settler/src/state/fulfilled_intent.rs @@ -2,6 +2,4 @@ use anchor_lang::prelude::*; #[account] #[derive(InitSpace)] -pub struct FulfilledIntent { - pub fulfilled_at: u64, -} +pub struct FulfilledIntent {} diff --git a/packages/svm/programs/settler/src/state/intent.rs b/packages/svm/programs/settler/src/state/intent.rs index 087eb01..c8b2522 100644 --- a/packages/svm/programs/settler/src/state/intent.rs +++ b/packages/svm/programs/settler/src/state/intent.rs @@ -9,15 +9,14 @@ use crate::{ pub struct Intent { pub op: OpType, pub user: Pubkey, - pub intent_creator: Pubkey, - pub intent_hash: [u8; 32], + pub creator: Pubkey, + pub hash: [u8; 32], pub nonce: [u8; 32], pub deadline: u64, pub min_validations: u16, - pub validations: u16, pub is_final: bool, pub validators: Vec, // TODO: how to store more efficiently? - pub intent_data: Vec, + pub data: Vec, pub max_fees: Vec, pub events: Vec, pub bump: u8, @@ -28,12 +27,11 @@ impl Intent { pub const BASE_LEN: usize = 1 + // op 32 + // user - 32 + // intent_creator - 32 + // intent_hash + 32 + // creator + 32 + // hash 32 + // nonce 8 + // deadline 2 + // min_validations - 2 + // validations 1 + // is_final 1 // bump ; diff --git a/packages/svm/programs/settler/src/types/op_type.rs b/packages/svm/programs/settler/src/types/op_type.rs index 620ab7e..fa60e8e 100644 --- a/packages/svm/programs/settler/src/types/op_type.rs +++ b/packages/svm/programs/settler/src/types/op_type.rs @@ -3,7 +3,7 @@ use anchor_lang::prelude::*; #[repr(u8)] #[derive(Clone, AnchorSerialize, AnchorDeserialize)] pub enum OpType { - Swap = 1, - Transfer = 2, - Call = 3, + Swap = 0, + Transfer = 1, + Call = 2, } diff --git a/packages/svm/sdks/controller/Controller.ts b/packages/svm/sdks/controller/Controller.ts index 5bc252b..0c26c59 100644 --- a/packages/svm/sdks/controller/Controller.ts +++ b/packages/svm/sdks/controller/Controller.ts @@ -19,24 +19,24 @@ export default class ControllerSDK { } async initializeIx(admin: web3.PublicKey): Promise { - const globalSettings = this.getGlobalSettingsPubkey() + const controllerSettings = this.getControllerSettingsPubkey() const ix = await this.program.methods .initialize(admin) .accountsPartial({ deployer: this.getSignerKey(), - globalSettings, + controllerSettings, }) .instruction() return ix } async setAdmin(newAdmin: web3.PublicKey): Promise { - const globalSettings = this.getGlobalSettingsPubkey() + const controllerSettings = this.getControllerSettingsPubkey() const ix = await this.program.methods .setAdmin(newAdmin) .accountsPartial({ admin: this.getSignerKey(), - globalSettings, + controllerSettings, }) .instruction() return ix @@ -47,13 +47,13 @@ export default class ControllerSDK { entityPubkey: web3.PublicKey ): Promise { const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) - const globalSettings = this.getGlobalSettingsPubkey() + const controllerSettings = this.getControllerSettingsPubkey() const ix = await this.program.methods .createEntityRegistry(this.entityTypeToAnchorEnum(entityType), entityPubkey) .accountsPartial({ admin: this.getSignerKey(), entityRegistry, - globalSettings, + controllerSettings, }) .instruction() return ix @@ -64,13 +64,13 @@ export default class ControllerSDK { entityPubkey: web3.PublicKey ): Promise { const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) - const globalSettings = this.getGlobalSettingsPubkey() + const controllerSettings = this.getControllerSettingsPubkey() const ix = await this.program.methods .closeEntityRegistry(this.entityTypeToAnchorEnum(entityType), entityPubkey) .accountsPartial({ admin: this.getSignerKey(), entityRegistry, - globalSettings, + controllerSettings, }) .instruction() return ix @@ -81,8 +81,8 @@ export default class ControllerSDK { return this.program.provider.wallet?.publicKey } - getGlobalSettingsPubkey(): web3.PublicKey { - return web3.PublicKey.findProgramAddressSync([Buffer.from('global-settings')], this.program.programId)[0] + getControllerSettingsPubkey(): web3.PublicKey { + return web3.PublicKey.findProgramAddressSync([Buffer.from('controller-settings')], this.program.programId)[0] } getEntityRegistryPubkey(entityType: EntityType, entityPubkey: web3.PublicKey): web3.PublicKey { diff --git a/packages/svm/sdks/settler/Settler.ts b/packages/svm/sdks/settler/Settler.ts index 1b4beaa..8bea75d 100644 --- a/packages/svm/sdks/settler/Settler.ts +++ b/packages/svm/sdks/settler/Settler.ts @@ -77,7 +77,7 @@ export default class SettlerSDK { const ix = await this.program.methods .extendIntent(moreData, moreMaxFeesBn, moreEvents, finalize) .accountsPartial({ - intentCreator: this.getSignerKey(), + creator: this.getSignerKey(), intent: this.getIntentKey(intentHashHex), }) .instruction() @@ -89,7 +89,7 @@ export default class SettlerSDK { const ix = await this.program.methods .claimStaleIntent() .accountsPartial({ - intentCreator: this.getSignerKey(), + creator: this.getSignerKey(), intent: this.getIntentKey(intentHashHex), }) .instruction() diff --git a/packages/svm/sdks/settler/types.ts b/packages/svm/sdks/settler/types.ts index c8dce69..92d487a 100644 --- a/packages/svm/sdks/settler/types.ts +++ b/packages/svm/sdks/settler/types.ts @@ -10,14 +10,13 @@ export type IntentEvent = { dataHex: string } -export enum OpType { - // eslint-disable-next-line no-unused-vars - Transfer = 1, - // eslint-disable-next-line no-unused-vars - Swap = 2, - // eslint-disable-next-line no-unused-vars - Call = 3, -} +export const OpType = { + Transfer: 0, + Swap: 1, + Call: 2, +} as const + +export type OpType = (typeof OpType)[keyof typeof OpType] export type CreateIntentParams = { op: OpType diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts index 7e2c171..f9aa51f 100644 --- a/packages/svm/tests/controller.test.ts +++ b/packages/svm/tests/controller.test.ts @@ -85,7 +85,7 @@ describe('Controller Program', () => { const ix = await deployerSdk.initializeIx(admin.publicKey) await makeTxSignAndSend(deployerProvider, ix) - const settings = await program.account.globalSettings.fetch(deployerSdk.getGlobalSettingsPubkey()) + const settings = await program.account.controllerSettings.fetch(deployerSdk.getControllerSettingsPubkey()) expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) }) @@ -111,7 +111,7 @@ describe('Controller Program', () => { const ix = await adminSdk.setAdmin(otherAdmin.publicKey) await makeTxSignAndSend(adminProvider, ix) - const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) // Reset admin to original for subsequent tests @@ -184,7 +184,7 @@ describe('Controller Program', () => { const ix = await adminSdk.setAdmin(otherAdmin.publicKey) await makeTxSignAndSend(adminProvider, ix) - const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 696adb2..6191b8f 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -156,13 +156,12 @@ describe('Settler Program', () => { const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) expect(intent.op).to.deep.include({ transfer: {} }) expect(intent.user.toString()).to.be.eq(user.toString()) - expect(intent.intentCreator.toString()).to.be.eq(solver.publicKey.toString()) + expect(intent.creator.toString()).to.be.eq(solver.publicKey.toString()) expect(Buffer.from(intent.nonce).toString('hex')).to.be.eq(nonce) expect(intent.deadline.toNumber()).to.be.eq(deadline) expect(intent.minValidations).to.be.eq(DEFAULT_MIN_VALIDATIONS) - expect(intent.validations).to.be.eq(0) expect(intent.isFinal).to.be.false - expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq(DEFAULT_DATA_HEX) + expect(Buffer.from(intent.data).toString('hex')).to.be.eq(DEFAULT_DATA_HEX) expect(intent.maxFees.length).to.be.eq(1) expect(intent.maxFees[0].mint.toString()).to.be.eq(params.maxFees[0].mint.toString()) expect(intent.maxFees[0].amount.toNumber()).to.be.eq(DEFAULT_MAX_FEE) @@ -189,7 +188,7 @@ describe('Settler Program', () => { const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) expect(intent.op).to.deep.include({ swap: {} }) - expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq(EMPTY_DATA_HEX) + expect(Buffer.from(intent.data).toString('hex')).to.be.eq(EMPTY_DATA_HEX) expect(intent.isFinal).to.be.true }) @@ -452,7 +451,7 @@ describe('Settler Program', () => { await makeTxSignAndSend(solverProvider, ix) const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq('010203070809') + expect(Buffer.from(intent.data).toString('hex')).to.be.eq('010203070809') expect(intent.isFinal).to.be.false }) @@ -529,7 +528,7 @@ describe('Settler Program', () => { await makeTxSignAndSend(solverProvider, ix) const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq('0102030d0e0f') + expect(Buffer.from(intent.data).toString('hex')).to.be.eq('0102030d0e0f') expect(intent.maxFees.length).to.be.eq(2) expect(intent.maxFees[1].amount.toNumber()).to.be.eq(3000) expect(intent.events.length).to.be.eq(2) @@ -573,11 +572,11 @@ describe('Settler Program', () => { const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) const intentAcc = client.getAccount(intentKey) - expect(intent.intentData.length).to.be.eq(3 + 5000) // Keep literal for specific test case + expect(intent.data.length).to.be.eq(3 + 5000) // Keep literal for specific test case expect(intent.maxFees.length).to.be.eq(58) expect(intent.events.length).to.be.eq(51) expect(intent.isFinal).to.be.false - expect(intentAcc?.data.length).to.be.eq(19361) + expect(intentAcc?.data.length).to.be.eq(19359) }) it('should finalize an intent', async () => { @@ -603,7 +602,7 @@ describe('Settler Program', () => { await makeTxSignAndSend(solverProvider, ix) const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq('010203191a1b') + expect(Buffer.from(intent.data).toString('hex')).to.be.eq('010203191a1b') expect(intent.isFinal).to.be.true }) @@ -623,7 +622,7 @@ describe('Settler Program', () => { await makeTxSignAndSend(solverProvider, ix2) const intent = await program.account.intent.fetch(sdk.getIntentKey(intentHash)) - expect(Buffer.from(intent.intentData).toString('hex')).to.be.eq('0102031c1d1e1f2021') + expect(Buffer.from(intent.data).toString('hex')).to.be.eq('0102031c1d1e1f2021') expect(intent.isFinal).to.be.false }) @@ -694,13 +693,13 @@ describe('Settler Program', () => { warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) const intentBalanceBefore = Number(provider.client.getBalance(sdk.getIntentKey(intentHash))) || 0 - const intentCreatorBalanceBefore = Number(provider.client.getBalance(intentBefore.intentCreator)) || 0 + const intentCreatorBalanceBefore = Number(provider.client.getBalance(intentBefore.creator)) || 0 const ix = await solverSdk.claimStaleIntentIx(intentHash) await makeTxSignAndSend(solverProvider, ix) const intentBalanceAfter = Number(provider.client.getBalance(sdk.getIntentKey(intentHash))) || 0 - const intentCreatorBalanceAfter = Number(provider.client.getBalance(intentBefore.intentCreator)) || 0 + const intentCreatorBalanceAfter = Number(provider.client.getBalance(intentBefore.creator)) || 0 try { await program.account.intent.fetch(sdk.getIntentKey(intentHash)) From 5f8eb31dae2664b513032a6cf26d218d9150476a Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 16:17:02 -0300 Subject: [PATCH 24/39] Code review: rm is_paused from SettlerSettings --- packages/svm/programs/settler/src/state/settler_settings.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svm/programs/settler/src/state/settler_settings.rs b/packages/svm/programs/settler/src/state/settler_settings.rs index e59b592..b026d38 100644 --- a/packages/svm/programs/settler/src/state/settler_settings.rs +++ b/packages/svm/programs/settler/src/state/settler_settings.rs @@ -4,6 +4,5 @@ use anchor_lang::prelude::*; #[derive(InitSpace)] pub struct SettlerSettings { pub controller_program: Pubkey, - pub is_paused: bool, pub bump: u8, } From 103944277e4c9b07c21fc3ac552e86c8d0115a87 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 16:33:59 -0300 Subject: [PATCH 25/39] Code review: adapt Settler to match Controller changes --- .../src/instructions/create_proposal.rs | 12 ++-- .../src/instructions/execute_proposal.rs | 12 ++-- .../settler/src/instructions/initialize.rs | 1 - packages/svm/tests/helpers/settler-helpers.ts | 65 +++++++++++++----- packages/svm/tests/settler.test.ts | 67 +++---------------- 5 files changed, 70 insertions(+), 87 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/create_proposal.rs b/packages/svm/programs/settler/src/instructions/create_proposal.rs index c0e892d..91399ef 100644 --- a/packages/svm/programs/settler/src/instructions/create_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/create_proposal.rs @@ -4,9 +4,9 @@ use crate::{ errors::SettlerError, state::{Intent, Proposal, ProposalInstruction}, types::TokenFee, - whitelist::{ + controller::{ accounts::EntityRegistry, - types::{EntityType, WhitelistStatus}, + types::EntityType, }, }; @@ -19,9 +19,7 @@ pub struct CreateProposal<'info> { #[account( seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], bump = solver_registry.bump, - seeds::program = crate::whitelist::ID, - constraint = - solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + seeds::program = crate::controller::ID, )] pub solver_registry: Box>, @@ -29,7 +27,7 @@ pub struct CreateProposal<'info> { pub intent: Box>, #[account( - seeds = [b"fulfilled-intent", intent.intent_hash.as_ref()], + seeds = [b"fulfilled-intent", intent.hash.as_ref()], bump )] /// This PDA must be uninitialized @@ -64,7 +62,7 @@ pub fn create_proposal( SettlerError::ProposalDeadlineExceedsIntentDeadline ); require!( - intent.validations >= intent.min_validations, + intent.validators.len() >= intent.min_validations as usize, SettlerError::InsufficientIntentValidations ); require!(intent.is_final, SettlerError::IntentIsNotFinal); diff --git a/packages/svm/programs/settler/src/instructions/execute_proposal.rs b/packages/svm/programs/settler/src/instructions/execute_proposal.rs index a77fbbd..b5643e2 100644 --- a/packages/svm/programs/settler/src/instructions/execute_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/execute_proposal.rs @@ -4,9 +4,9 @@ use crate::{ errors::SettlerError, state::{FulfilledIntent, Intent, Proposal}, types::IntentEvent, - whitelist::{ + controller::{ accounts::EntityRegistry, - types::{EntityType, WhitelistStatus}, + types::EntityType, }, }; @@ -18,9 +18,7 @@ pub struct ExecuteProposal<'info> { #[account( seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], bump = solver_registry.bump, - seeds::program = crate::whitelist::ID, - constraint = - solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + seeds::program = crate::controller::ID, )] pub solver_registry: Box>, @@ -43,14 +41,14 @@ pub struct ExecuteProposal<'info> { #[account( mut, - has_one = intent_creator @ SettlerError::IncorrectIntentCreator, + constraint = intent.creator == intent_creator.key() @ SettlerError::IncorrectIntentCreator, close = intent_creator )] pub intent: Box>, #[account( init, - seeds = [b"fulfilled-intent", intent.intent_hash.as_ref()], + seeds = [b"fulfilled-intent", intent.hash.as_ref()], bump, space = 8 + FulfilledIntent::INIT_SPACE, payer = solver diff --git a/packages/svm/programs/settler/src/instructions/initialize.rs b/packages/svm/programs/settler/src/instructions/initialize.rs index dd967a6..b65f334 100644 --- a/packages/svm/programs/settler/src/instructions/initialize.rs +++ b/packages/svm/programs/settler/src/instructions/initialize.rs @@ -30,7 +30,6 @@ pub fn initialize(ctx: Context) -> Result<()> { let settler_settings = &mut ctx.accounts.settler_settings; settler_settings.controller_program = crate::controller::ID; - settler_settings.is_paused = false; settler_settings.bump = ctx.bumps.settler_settings; Ok(()) diff --git a/packages/svm/tests/helpers/settler-helpers.ts b/packages/svm/tests/helpers/settler-helpers.ts index 1367a72..3bec4fa 100644 --- a/packages/svm/tests/helpers/settler-helpers.ts +++ b/packages/svm/tests/helpers/settler-helpers.ts @@ -7,6 +7,7 @@ import { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm import ControllerSDK, { EntityType } from '../../sdks/controller/Controller' import SettlerSDK from '../../sdks/settler/Settler' import { CreateIntentParams, IntentEvent, OpType, ProposalInstruction, TokenFee } from '../../sdks/settler/types' +import * as SettlerIDL from '../../target/idl/settler.json' import { Settler } from '../../target/types/settler' import { makeTxSignAndSend } from '../utils' import { @@ -90,7 +91,49 @@ export async function createTestIntent( } /** - * Create a validated intent (with validations set to meet min_validations requirement) + * Add mock validators to an intent account + */ +export async function addValidatorsToIntent( + intentHash: string, + solverSdk: SettlerSDK, + solverProvider: LiteSVMProvider, + client: LiteSVM, + numValidators: number, + program?: Program +): Promise { + const intentKey = solverSdk.getIntentKey(intentHash) + const programInstance = program || new Program(SettlerIDL, solverProvider) + + // Fetch and deserialize the intent account + const intent = await programInstance.account.intent.fetch(intentKey) + + // Generate validators + const validators: PublicKey[] = [] + for (let i = 0; i < numValidators; i++) { + validators.push(Keypair.generate().publicKey) + } + + // Modify the intent to add validators + const modifiedIntent = { + ...intent, + validators, + } + + // Serialize the modified intent back to account data + const serializedData = await programInstance.coder.accounts.encode('intent', modifiedIntent) + + // Update the account data + const intentAccount = client.getAccount(intentKey) + if (intentAccount) { + client.setAccount(intentKey, { + ...intentAccount, + data: serializedData, + }) + } +} + +/** + * Create a validated intent (with validators added to meet min_validations requirement) */ export async function createValidatedIntent( solverSdk: SettlerSDK, @@ -101,6 +144,7 @@ export async function createValidatedIntent( minValidations?: number isFinal?: boolean deadline?: number + program?: Program } = {} ): Promise { const intentHash = await createTestIntent(solverSdk, solverProvider, { @@ -108,20 +152,9 @@ export async function createValidatedIntent( isFinal: options.isFinal ?? true, }) - // Set validations to meet min_validations requirement - const intentKey = solverSdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - // validations is at offset: 8 (disc) + 1 (op) + 32 (user) + 32 (intent_creator) + 32 (intent_hash) + 32 (nonce) + 8 (deadline) + 2 (min_validations) = 147 - // validations is u16, so 2 bytes - const minValidations = options.minValidations ?? DEFAULT_MIN_VALIDATIONS - intentData.writeUInt16LE(minValidations, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } + // Add validators to meet min_validations requirement + const minValidations = options.minValidations ?? DEFAULT_MIN_VALIDATIONS + await addValidatorsToIntent(intentHash, solverSdk, solverProvider, client, minValidations, options.program) return intentHash } @@ -179,7 +212,7 @@ export async function createFinalizedProposal( } /** - * Creates an allowlisted entity (validator, axia, or solver) + * Creates an allowlisted entity (validator, axia, or solver) */ export async function createAllowlistedEntity( controllerSdk: ControllerSDK, diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index b54a78c..7ebb86d 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -45,6 +45,7 @@ import { WARP_TIME_SHORT, } from './helpers/constants' import { + addValidatorsToIntent, createTestIntent, createValidatedIntent, expectTransactionError, @@ -120,7 +121,6 @@ describe('Settler Program', () => { const settings = await program.account.settlerSettings.fetch(sdk.getSettlerSettingsPubkey()) expect(settings.controllerProgram.toString()).to.be.eq(ControllerIDL.address) - expect(settings.isPaused).to.be.false }) it('cannot call initialize again', async () => { @@ -1021,17 +1021,8 @@ describe('Settler Program', () => { const ix = await solverSdk.createIntentIx(intentHash, params) await makeTxSignAndSend(solverProvider, ix) - // Set validations - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } + // Add validators + await addValidatorsToIntent(intentHash, solverSdk, solverProvider, client, 1, program) warpSeconds(provider, 101) @@ -1095,17 +1086,8 @@ describe('Settler Program', () => { const ix = await solverSdk.createIntentIx(intentHash, params) await makeTxSignAndSend(solverProvider, ix) - // Set validations to 1 (less than min_validations of 2) - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } + // Add validators to 1 (less than min_validations of 2) + await addValidatorsToIntent(intentHash, solverSdk, solverProvider, client, 1, program) const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET const instructions = [ @@ -1259,17 +1241,8 @@ describe('Settler Program', () => { const ix = await solverSdk.createIntentIx(intentHash, params, true) await makeTxSignAndSend(solverProvider, ix) - // Set validations - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } + // Add validators + await addValidatorsToIntent(intentHash, solverSdk, solverProvider, client, 1, program) const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET const instructions = [ @@ -1323,17 +1296,8 @@ describe('Settler Program', () => { const ix = await solverSdk.createIntentIx(intentHash, params, true) await makeTxSignAndSend(solverProvider, ix) - // Set validations - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } + // Add validators + await addValidatorsToIntent(intentHash, solverSdk, solverProvider, client, 1, program) const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET const instructions = [ @@ -1386,17 +1350,8 @@ describe('Settler Program', () => { const ix = await solverSdk.createIntentIx(intentHash, params, true) await makeTxSignAndSend(solverProvider, ix) - // Set validations - const intentKey = sdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - intentData.writeUInt16LE(1, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } + // Add validators + await addValidatorsToIntent(intentHash, solverSdk, solverProvider, client, 1, program) const proposalDeadline = now + PROPOSAL_DEADLINE_OFFSET const instructions = [ From 435b7dcae39a9b552020e389282f36793c7c42af Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 16:34:51 -0300 Subject: [PATCH 26/39] Rm unused is_paused flag --- packages/svm/programs/settler/src/instructions/initialize.rs | 1 - packages/svm/tests/settler.test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/initialize.rs b/packages/svm/programs/settler/src/instructions/initialize.rs index dd967a6..b65f334 100644 --- a/packages/svm/programs/settler/src/instructions/initialize.rs +++ b/packages/svm/programs/settler/src/instructions/initialize.rs @@ -30,7 +30,6 @@ pub fn initialize(ctx: Context) -> Result<()> { let settler_settings = &mut ctx.accounts.settler_settings; settler_settings.controller_program = crate::controller::ID; - settler_settings.is_paused = false; settler_settings.bump = ctx.bumps.settler_settings; Ok(()) diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 6191b8f..1a2cc85 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -110,7 +110,6 @@ describe('Settler Program', () => { const settings = await program.account.settlerSettings.fetch(sdk.getSettlerSettingsPubkey()) expect(settings.controllerProgram.toString()).to.be.eq(ControllerIDL.address) - expect(settings.isPaused).to.be.false }) it('cannot call initialize again', async () => { From b638369b7ea222c243e6156c8296d86895e8e4e8 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 16:35:33 -0300 Subject: [PATCH 27/39] Fix lint --- .../svm/programs/settler/src/instructions/create_proposal.rs | 5 +---- .../programs/settler/src/instructions/execute_proposal.rs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/create_proposal.rs b/packages/svm/programs/settler/src/instructions/create_proposal.rs index 91399ef..d4eb706 100644 --- a/packages/svm/programs/settler/src/instructions/create_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/create_proposal.rs @@ -1,13 +1,10 @@ use anchor_lang::prelude::*; use crate::{ + controller::{accounts::EntityRegistry, types::EntityType}, errors::SettlerError, state::{Intent, Proposal, ProposalInstruction}, types::TokenFee, - controller::{ - accounts::EntityRegistry, - types::EntityType, - }, }; #[derive(Accounts)] diff --git a/packages/svm/programs/settler/src/instructions/execute_proposal.rs b/packages/svm/programs/settler/src/instructions/execute_proposal.rs index b5643e2..198759b 100644 --- a/packages/svm/programs/settler/src/instructions/execute_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/execute_proposal.rs @@ -1,13 +1,10 @@ use anchor_lang::prelude::*; use crate::{ + controller::{accounts::EntityRegistry, types::EntityType}, errors::SettlerError, state::{FulfilledIntent, Intent, Proposal}, types::IntentEvent, - controller::{ - accounts::EntityRegistry, - types::EntityType, - }, }; #[derive(Accounts)] From 3130209525b1a8017530b89ce1ca1fd0877aff18 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 16:38:04 -0300 Subject: [PATCH 28/39] Code review: rename proposal_creator to creator --- .../src/instructions/add_instructions_to_proposal.rs | 6 +++--- .../settler/src/instructions/claim_stale_proposal.rs | 2 +- .../programs/settler/src/instructions/create_proposal.rs | 2 +- .../programs/settler/src/instructions/execute_proposal.rs | 2 +- packages/svm/programs/settler/src/state/proposal.rs | 4 ++-- packages/svm/tests/settler.test.ts | 8 ++++---- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs b/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs index 9755cee..9ba9ed3 100644 --- a/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/add_instructions_to_proposal.rs @@ -9,14 +9,14 @@ use crate::{ #[instruction(more_instructions: Vec)] pub struct AddInstructionsToProposal<'info> { #[account(mut)] - pub proposal_creator: Signer<'info>, + pub creator: Signer<'info>, #[account( mut, realloc = Proposal::extended_size(proposal.to_account_info().data_len(), &more_instructions)?, - realloc::payer = proposal_creator, + realloc::payer = creator, realloc::zero = true, - has_one = proposal_creator @ SettlerError::IncorrectProposalCreator + has_one = creator @ SettlerError::IncorrectProposalCreator )] // Any proposal pub proposal: Box>, diff --git a/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs b/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs index 25a7b9c..75bf93f 100644 --- a/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs @@ -28,7 +28,7 @@ pub fn claim_stale_proposal<'info>( Box::new(Account::::try_from(account_info)?); require_keys_eq!( - proposal.proposal_creator, + proposal.creator, proposal_creator.key(), SettlerError::IncorrectProposalCreator ); diff --git a/packages/svm/programs/settler/src/instructions/create_proposal.rs b/packages/svm/programs/settler/src/instructions/create_proposal.rs index d4eb706..2c85dc7 100644 --- a/packages/svm/programs/settler/src/instructions/create_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/create_proposal.rs @@ -83,7 +83,7 @@ pub fn create_proposal( let proposal = &mut ctx.accounts.proposal; proposal.intent = intent.key(); - proposal.proposal_creator = ctx.accounts.solver.key(); + proposal.creator = ctx.accounts.solver.key(); proposal.deadline = deadline; proposal.is_final = is_final; proposal.is_signed = false; diff --git a/packages/svm/programs/settler/src/instructions/execute_proposal.rs b/packages/svm/programs/settler/src/instructions/execute_proposal.rs index 198759b..ce1dc7d 100644 --- a/packages/svm/programs/settler/src/instructions/execute_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/execute_proposal.rs @@ -26,7 +26,7 @@ pub struct ExecuteProposal<'info> { #[account( mut, has_one = intent @ SettlerError::IncorrectIntentForProposal, - has_one = proposal_creator @ SettlerError::IncorrectProposalCreator, + constraint = proposal.creator == proposal_creator.key() @ SettlerError::IncorrectProposalCreator, constraint = proposal.is_signed @ SettlerError::ProposalIsNotSigned, close = proposal_creator )] diff --git a/packages/svm/programs/settler/src/state/proposal.rs b/packages/svm/programs/settler/src/state/proposal.rs index 5487320..d1f3119 100644 --- a/packages/svm/programs/settler/src/state/proposal.rs +++ b/packages/svm/programs/settler/src/state/proposal.rs @@ -8,7 +8,7 @@ use crate::{ #[account] pub struct Proposal { pub intent: Pubkey, - pub proposal_creator: Pubkey, + pub creator: Pubkey, pub deadline: u64, pub is_final: bool, pub is_signed: bool, @@ -21,7 +21,7 @@ impl Proposal { /// Doesn't take into account size of variable fields pub const BASE_LEN: usize = 32 + // intent - 32 + // proposal_creator + 32 + // creator 8 + // deadline 1 + // is_final 1 + // is_signed diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 7ebb86d..246d99d 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -823,7 +823,7 @@ describe('Settler Program', () => { const proposal = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) expect(proposal.intent.toString()).to.be.eq(sdk.getIntentKey(intentHash).toString()) - expect(proposal.proposalCreator.toString()).to.be.eq(solver.publicKey.toString()) + expect(proposal.creator.toString()).to.be.eq(solver.publicKey.toString()) expect(proposal.deadline.toNumber()).to.be.eq(deadline) expect(proposal.isFinal).to.be.true expect(proposal.instructions.length).to.be.eq(1) @@ -1500,7 +1500,7 @@ describe('Settler Program', () => { it('cannot add instructions if not proposal creator', async () => { const intentHash = await createTestProposal(false) const proposalCreator = (await program.account.proposal.fetch(solverSdk.getProposalKey(intentHash))) - .proposalCreator + .creator const moreInstructions = [ { @@ -1730,14 +1730,14 @@ describe('Settler Program', () => { const proposalBalanceBefore = Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0 - const proposalCreatorBalanceBefore = Number(provider.client.getBalance(proposalBefore.proposalCreator)) || 0 + const proposalCreatorBalanceBefore = Number(provider.client.getBalance(proposalBefore.creator)) || 0 const ix = await solverSdk.claimStaleProposalIx([intentHash]) await makeTxSignAndSend(solverProvider, ix) const proposalBalanceAfter = Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0 - const proposalCreatorBalanceAfter = Number(provider.client.getBalance(proposalBefore.proposalCreator)) || 0 + const proposalCreatorBalanceAfter = Number(provider.client.getBalance(proposalBefore.creator)) || 0 try { await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) From efbfea992bb10debe2683b0b41beb7a0b8d1d39e Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 16:39:57 -0300 Subject: [PATCH 29/39] Fix lint --- packages/svm/tests/settler.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 246d99d..21aa720 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -1499,8 +1499,7 @@ describe('Settler Program', () => { it('cannot add instructions if not proposal creator', async () => { const intentHash = await createTestProposal(false) - const proposalCreator = (await program.account.proposal.fetch(solverSdk.getProposalKey(intentHash))) - .creator + const proposalCreator = (await program.account.proposal.fetch(solverSdk.getProposalKey(intentHash))).creator const moreInstructions = [ { From 1c1c0c903e8191aac298f8d5c9ad491e17fd6322 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 30 Dec 2025 16:52:14 -0300 Subject: [PATCH 30/39] Code review: adapt Settler for changes in Controller --- .../settler/src/instructions/add_axia_sig.rs | 15 +++------- .../src/instructions/add_validator_sig.rs | 28 +++++-------------- .../instructions/change_controller_program.rs | 9 ++++++ .../instructions/change_whitelist_program.rs | 9 ------ .../programs/settler/src/instructions/mod.rs | 4 +-- packages/svm/programs/settler/src/lib.rs | 4 +-- packages/svm/tests/settler.test.ts | 24 ++++++++-------- 7 files changed, 35 insertions(+), 58 deletions(-) create mode 100644 packages/svm/programs/settler/src/instructions/change_controller_program.rs delete mode 100644 packages/svm/programs/settler/src/instructions/change_whitelist_program.rs diff --git a/packages/svm/programs/settler/src/instructions/add_axia_sig.rs b/packages/svm/programs/settler/src/instructions/add_axia_sig.rs index 64d1e1c..64bc3d1 100644 --- a/packages/svm/programs/settler/src/instructions/add_axia_sig.rs +++ b/packages/svm/programs/settler/src/instructions/add_axia_sig.rs @@ -4,13 +4,10 @@ use anchor_lang::{ }; use crate::{ + controller::{accounts::EntityRegistry, types::EntityType}, errors::SettlerError, state::Proposal, utils::{check_ed25519_ix, get_args_from_ed25519_ix_data, Ed25519Args}, - whitelist::{ - accounts::EntityRegistry, - types::{EntityType, WhitelistStatus}, - }, }; #[derive(Accounts)] @@ -21,18 +18,14 @@ pub struct AddAxiaSig<'info> { #[account( seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], bump = solver_registry.bump, - seeds::program = crate::whitelist::ID, - constraint = - solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + seeds::program = crate::controller::ID, )] pub solver_registry: Box>, #[account( seeds = [b"entity-registry", &[EntityType::Axia as u8 + 1], axia_registry.entity_pubkey.as_ref()], bump = axia_registry.bump, - seeds::program = crate::whitelist::ID, - constraint = - axia_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::AxiaNotWhitelisted + seeds::program = crate::controller::ID, )] pub axia_registry: Box>, @@ -74,7 +67,7 @@ pub fn add_axia_sig(ctx: Context) -> Result<()> { // Verify pubkey is whitelisted Axia require!( ed25519_ix_args.pubkey == &ctx.accounts.axia_registry.entity_pubkey.to_bytes(), - SettlerError::AxiaNotWhitelisted + SettlerError::AxiaNotAllowlisted ); // Updates proposal as signed diff --git a/packages/svm/programs/settler/src/instructions/add_validator_sig.rs b/packages/svm/programs/settler/src/instructions/add_validator_sig.rs index f6fd619..79fbef1 100644 --- a/packages/svm/programs/settler/src/instructions/add_validator_sig.rs +++ b/packages/svm/programs/settler/src/instructions/add_validator_sig.rs @@ -4,13 +4,10 @@ use anchor_lang::{ }; use crate::{ + controller::{accounts::EntityRegistry, types::EntityType}, errors::SettlerError, state::Intent, utils::{check_ed25519_ix, get_args_from_ed25519_ix_data, Ed25519Args}, - whitelist::{ - accounts::EntityRegistry, - types::{EntityType, WhitelistStatus}, - }, }; #[derive(Accounts)] @@ -21,9 +18,7 @@ pub struct AddValidatorSig<'info> { #[account( seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], bump = solver_registry.bump, - seeds::program = crate::whitelist::ID, - constraint = - solver_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::OnlySolver + seeds::program = crate::controller::ID, )] pub solver_registry: Box>, @@ -36,17 +31,13 @@ pub struct AddValidatorSig<'info> { pub intent: Box>, #[account( - seeds = [b"fulfilled-intent", intent.intent_hash.as_ref()], + seeds = [b"fulfilled-intent", intent.hash.as_ref()], bump )] /// This PDA must be uninitialized pub fulfilled_intent: SystemAccount<'info>, /// CHECK: other checks in ix body - #[account( - constraint = - validator_registry.status as u8 == WhitelistStatus::Whitelisted as u8 @ SettlerError::ValidatorNotWhitelisted - )] pub validator_registry: Box>, /// CHECK: The address check is needed because otherwise @@ -67,7 +58,7 @@ pub fn add_validator_sig(ctx: Context) -> Result<()> { // Verify correct message was signed require!( - ed25519_ix_args.msg == intent.intent_hash, + ed25519_ix_args.msg == intent.hash, SettlerError::SigVerificationFailed ); @@ -81,10 +72,10 @@ pub fn add_validator_sig(ctx: Context) -> Result<()> { ed25519_ix_args.pubkey, &[ctx.accounts.validator_registry.bump] ], - &crate::whitelist::ID, + &crate::controller::ID, ) - .map_err(|_| SettlerError::ValidatorNotWhitelisted)?, - SettlerError::ValidatorNotWhitelisted, + .map_err(|_| SettlerError::ValidatorNotAllowlisted)?, + SettlerError::ValidatorNotAllowlisted, ); // Updates intent PDA if signature not present and min_validations not met @@ -99,11 +90,6 @@ pub fn add_validator_sig(ctx: Context) -> Result<()> { return Ok(()); } - intent.validations = intent - .validations - .checked_add(1) - .ok_or(SettlerError::MathError)?; - intent.validators.push(ed25519_pubkey); Ok(()) diff --git a/packages/svm/programs/settler/src/instructions/change_controller_program.rs b/packages/svm/programs/settler/src/instructions/change_controller_program.rs new file mode 100644 index 0000000..62fca3b --- /dev/null +++ b/packages/svm/programs/settler/src/instructions/change_controller_program.rs @@ -0,0 +1,9 @@ +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct ChangeControllerProgram {} + +pub fn change_controller_program(ctx: Context) -> Result<()> { + // TODO: check against crate::controller::ID + Ok(()) +} diff --git a/packages/svm/programs/settler/src/instructions/change_whitelist_program.rs b/packages/svm/programs/settler/src/instructions/change_whitelist_program.rs deleted file mode 100644 index ca70398..0000000 --- a/packages/svm/programs/settler/src/instructions/change_whitelist_program.rs +++ /dev/null @@ -1,9 +0,0 @@ -use anchor_lang::prelude::*; - -#[derive(Accounts)] -pub struct ChangeWhitelistProgram {} - -pub fn change_whitelist_program(ctx: Context) -> Result<()> { - // TODO: check against crate::whitelist::ID - Ok(()) -} diff --git a/packages/svm/programs/settler/src/instructions/mod.rs b/packages/svm/programs/settler/src/instructions/mod.rs index 305ad84..b6953ce 100644 --- a/packages/svm/programs/settler/src/instructions/mod.rs +++ b/packages/svm/programs/settler/src/instructions/mod.rs @@ -1,7 +1,7 @@ pub mod add_axia_sig; pub mod add_instructions_to_proposal; pub mod add_validator_sig; -pub mod change_whitelist_program; +pub mod change_controller_program; pub mod claim_stale_intent; pub mod claim_stale_proposals; pub mod create_intent; @@ -13,7 +13,7 @@ pub mod initialize; pub use add_axia_sig::*; pub use add_instructions_to_proposal::*; pub use add_validator_sig::*; -pub use change_whitelist_program::*; +pub use change_controller_program::*; pub use claim_stale_intent::*; pub use claim_stale_proposals::*; pub use create_intent::*; diff --git a/packages/svm/programs/settler/src/lib.rs b/packages/svm/programs/settler/src/lib.rs index 4d89e1d..51f375d 100644 --- a/packages/svm/programs/settler/src/lib.rs +++ b/packages/svm/programs/settler/src/lib.rs @@ -32,8 +32,8 @@ pub mod settler { instructions::add_validator_sig(ctx) } - pub fn change_whitelist_program(ctx: Context) -> Result<()> { - instructions::change_whitelist_program(ctx) + pub fn change_controller_program(ctx: Context) -> Result<()> { + instructions::change_controller_program(ctx) } pub fn claim_stale_intent(ctx: Context) -> Result<()> { diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 8577359..4625e41 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -47,9 +47,10 @@ import { WARP_TIME_SHORT, } from './helpers/constants' import { + addValidatorsToIntent, + createAllowlistedEntity, createAxiaSignature, createFinalizedProposal, - addValidatorsToIntent, createTestIntent, createValidatedIntent, createValidatorSignature, @@ -1876,7 +1877,7 @@ describe('Settler Program', () => { whitelistedValidator = Keypair.generate() const whitelistValidatorIx = await controllerSdk.createEntityRegistryIx( EntityType.Validator, - whitelistedValidator.publicKey, + whitelistedValidator.publicKey ) await makeTxSignAndSend(provider, whitelistValidatorIx) }) @@ -1913,9 +1914,9 @@ describe('Settler Program', () => { }) const intentKey = sdk.getIntentKey(intentHash) - const validator1 = await createWhitelistedEntity(controllerSdk, provider, EntityType.Validator) - const validator2 = await createWhitelistedEntity(controllerSdk, provider, EntityType.Validator) - const validator3 = await createWhitelistedEntity(controllerSdk, provider, EntityType.Validator) + const validator1 = await createAllowlistedEntity(controllerSdk, provider, EntityType.Validator) + const validator2 = await createAllowlistedEntity(controllerSdk, provider, EntityType.Validator) + const validator3 = await createAllowlistedEntity(controllerSdk, provider, EntityType.Validator) const signature1 = await createValidatorSignature(intentHash, validator1) const ixs1 = await solverSdk.addValidatorSigIxs( @@ -1994,8 +1995,8 @@ describe('Settler Program', () => { }) const intentKey = sdk.getIntentKey(intentHash) - const validator1 = await createWhitelistedEntity(controllerSdk, provider, EntityType.Validator) - const validator2 = await createWhitelistedEntity(controllerSdk, provider, EntityType.Validator) + const validator1 = await createAllowlistedEntity(controllerSdk, provider, EntityType.Validator) + const validator2 = await createAllowlistedEntity(controllerSdk, provider, EntityType.Validator) const signature1 = await createValidatorSignature(intentHash, validator1) const ixs1 = await solverSdk.addValidatorSigIxs( @@ -2270,10 +2271,7 @@ describe('Settler Program', () => { before(async () => { whitelistedAxia = Keypair.generate() - const whitelistAxiaIx = await controllerSdk.createEntityRegistryIx( - EntityType.Axia, - whitelistedAxia.publicKey, - ) + const whitelistAxiaIx = await controllerSdk.createEntityRegistryIx(EntityType.Axia, whitelistedAxia.publicKey) await makeTxSignAndSend(provider, whitelistAxiaIx) }) @@ -2490,7 +2488,7 @@ describe('Settler Program', () => { it('cannot add signature with signature from different axia', async () => { const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - const axia2 = await createWhitelistedEntity(controllerSdk, provider, EntityType.Axia) + const axia2 = await createAllowlistedEntity(controllerSdk, provider, EntityType.Axia) const signature = await createAxiaSignature(proposalKey, axia2) // Try to use axia2's signature but claim it's from whitelistedAxia @@ -2505,7 +2503,7 @@ describe('Settler Program', () => { it('cannot add signature with signature from validator instead of axia', async () => { const { proposalKey } = await createFinalizedProposal(solverSdk, solverProvider, client, program) - const validator = await createWhitelistedEntity(controllerSdk, provider, EntityType.Validator) + const validator = await createAllowlistedEntity(controllerSdk, provider, EntityType.Validator) const signature = await createAxiaSignature(proposalKey, validator) const ixs = await solverSdk.addAxiaSigIxs(proposalKey, validator.publicKey, signature) From e1ef9edaec1e4ced8057fde71e90e58146f44c9a Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Mon, 5 Jan 2026 15:17:11 -0300 Subject: [PATCH 31/39] 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 32/39] 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 40d2ad6c5f978c939fe5621326313d834e97e20f Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Mon, 5 Jan 2026 16:00:23 -0300 Subject: [PATCH 33/39] Fix lint and merge errors --- packages/svm/tests/controller.test.ts | 97 +++++++++++++-------------- packages/svm/tests/helpers/helpers.ts | 2 +- packages/svm/tests/settler.test.ts | 4 +- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts index ba7a18f..f38dcaa 100644 --- a/packages/svm/tests/controller.test.ts +++ b/packages/svm/tests/controller.test.ts @@ -88,9 +88,9 @@ describe('Controller Program', () => { const ix = await deployerSdk.initializeIx(admin.publicKey) await makeTxSignAndSend(deployerProvider, ix) - const settings = await program.account.controllerSettings.fetch(deployerSdk.getControllerSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) - }) + const settings = await program.account.controllerSettings.fetch(deployerSdk.getControllerSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) + }) it('cannot call initialize again', async () => { const ix = await deployerSdk.initializeIx(admin.publicKey) @@ -123,8 +123,8 @@ describe('Controller Program', () => { const ix = await adminSdk.setAdmin(otherAdmin.publicKey) await makeTxSignAndSend(adminProvider, ix) - const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) + const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) }) }) @@ -160,61 +160,58 @@ describe('Controller Program', () => { 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()) - }) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Validator, validator) + ) - it('should create entity registry successfully (axia)', async () => { - const ix = await adminSdk.createEntityRegistryIx(EntityType.Axia, axia) - await makeTxSignAndSend(adminProvider, ix) + expect(entityRegistry.entityType).to.deep.include({ validator: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) + }) - const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) - ) + 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({ axia: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - }) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) - it('should create entity registry successfully (solver)', async () => { - const ix = await adminSdk.createEntityRegistryIx(EntityType.Solver, solver) - await makeTxSignAndSend(adminProvider, ix) + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) + }) - const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) - ) + it('should create entity registry successfully (solver)', async () => { + const ix = await adminSdk.setAllowedEntityIx(EntityType.Solver, solver) + await makeTxSignAndSend(adminProvider, ix) - expect(entityRegistry.entityType).to.deep.include({ solver: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) - }) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) - it('should change admin for next tests', async () => { - const ix = await adminSdk.setAdmin(otherAdmin.publicKey) - await makeTxSignAndSend(adminProvider, ix) + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) + }) - const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) - }) + it('should change admin for next tests', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) - it('should close entity registry (validator)', async () => { - const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Validator, validator) - await makeTxSignAndSend(otherAdminProvider, ix) + const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) + expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) + }) - 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 (validator)', async () => { + const ix = await otherAdminSdk.closeEntityRegistryIx(EntityType.Validator, validator) + 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.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 create entity registry successfully (axia)', async () => { @@ -247,7 +244,7 @@ describe('Controller Program', () => { const ix = await adminSdk.setAdmin(otherAdmin.publicKey) await makeTxSignAndSend(adminProvider, ix) - const settings = await program.account.globalSettings.fetch(adminSdk.getGlobalSettingsPubkey()) + const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) }) diff --git a/packages/svm/tests/helpers/helpers.ts b/packages/svm/tests/helpers/helpers.ts index 2d1fe5d..89eb57b 100644 --- a/packages/svm/tests/helpers/helpers.ts +++ b/packages/svm/tests/helpers/helpers.ts @@ -1,6 +1,6 @@ +import { web3 } from '@coral-xyz/anchor' import { Keypair, PublicKey } from '@solana/web3.js' import { LiteSVMProvider } from 'anchor-litesvm' -import { web3 } from '@coral-xyz/anchor' import { expect } from 'chai' import { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm' diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index 1a2cc85..779cdf3 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -40,7 +40,7 @@ import { WARP_TIME_LONG, WARP_TIME_SHORT, } from './helpers/constants' -import { createTestIntent, expectTransactionError, generateIntentHash, generateNonce } from './helpers/settler-helpers' +import { createTestIntent, expectTransactionError, generateIntentHash, generateNonce } from './helpers/helpers' import { makeTxSignAndSend, warpSeconds } from './utils' describe('Settler Program', () => { @@ -88,7 +88,7 @@ describe('Settler Program', () => { // Initialize Controller and add Solver to allowlist controllerSdk = new ControllerSDK(provider) await makeTxSignAndSend(provider, await controllerSdk.initializeIx(admin.publicKey)) - await makeTxSignAndSend(provider, await controllerSdk.createEntityRegistryIx(EntityType.Solver, solver.publicKey)) + await makeTxSignAndSend(provider, await controllerSdk.setAllowedEntityIx(EntityType.Solver, solver.publicKey)) }) beforeEach(() => { From 89ddecc25e4ff9badd4db72eb5acd1afccc1e0c2 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Mon, 5 Jan 2026 16:17:40 -0300 Subject: [PATCH 34/39] Code review: several comments --- packages/svm/package.json | 1 + .../svm/programs/settler/src/constants.rs | 2 +- .../settler/src/instructions/create_intent.rs | 4 +- .../settler/src/instructions/initialize.rs | 4 +- packages/svm/tests/helpers/helpers.ts | 64 ++----------------- yarn.lock | 36 ++++++++++- 6 files changed, 45 insertions(+), 66 deletions(-) diff --git a/packages/svm/package.json b/packages/svm/package.json index 9aeca27..8040005 100644 --- a/packages/svm/package.json +++ b/packages/svm/package.json @@ -18,6 +18,7 @@ "litesvm": "=0.1.0" }, "devDependencies": { + "@mimicprotocol/sdk": "0.0.1-rc.20", "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.20", "@types/mocha": "^10.0.10", diff --git a/packages/svm/programs/settler/src/constants.rs b/packages/svm/programs/settler/src/constants.rs index 756ec67..c2edf8e 100644 --- a/packages/svm/programs/settler/src/constants.rs +++ b/packages/svm/programs/settler/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/programs/settler/src/instructions/create_intent.rs b/packages/svm/programs/settler/src/instructions/create_intent.rs index 7f3c26e..f830b88 100644 --- a/packages/svm/programs/settler/src/instructions/create_intent.rs +++ b/packages/svm/programs/settler/src/instructions/create_intent.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use crate::{ - controller::{accounts::EntityRegistry, types::EntityType}, + controller::{self, accounts::EntityRegistry, types::EntityType}, errors::SettlerError, state::Intent, types::{IntentEvent, OpType, TokenFee}, @@ -17,7 +17,7 @@ pub struct CreateIntent<'info> { #[account( seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], bump = solver_registry.bump, - seeds::program = crate::controller::ID + seeds::program = controller::ID )] pub solver_registry: Box>, diff --git a/packages/svm/programs/settler/src/instructions/initialize.rs b/packages/svm/programs/settler/src/instructions/initialize.rs index b65f334..4f13472 100644 --- a/packages/svm/programs/settler/src/instructions/initialize.rs +++ b/packages/svm/programs/settler/src/instructions/initialize.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use std::str::FromStr; -use crate::{constants::DEPLOYER_KEY, errors::SettlerError, state::SettlerSettings}; +use crate::{constants::DEPLOYER_KEY, controller, errors::SettlerError, state::SettlerSettings}; #[derive(Accounts)] pub struct Initialize<'info> { @@ -29,7 +29,7 @@ pub fn initialize(ctx: Context) -> Result<()> { let settler_settings = &mut ctx.accounts.settler_settings; - settler_settings.controller_program = crate::controller::ID; + settler_settings.controller_program = controller::ID; settler_settings.bump = ctx.bumps.settler_settings; Ok(()) diff --git a/packages/svm/tests/helpers/helpers.ts b/packages/svm/tests/helpers/helpers.ts index 89eb57b..168fbdd 100644 --- a/packages/svm/tests/helpers/helpers.ts +++ b/packages/svm/tests/helpers/helpers.ts @@ -1,10 +1,10 @@ import { web3 } from '@coral-xyz/anchor' +import { randomHex } from '@mimicprotocol/sdk' import { Keypair, PublicKey } from '@solana/web3.js' import { LiteSVMProvider } from 'anchor-litesvm' import { expect } from 'chai' -import { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm' +import { FailedTransactionMetadata, TransactionMetadata } from 'litesvm' -import ControllerSDK, { EntityType } from '../../sdks/controller/Controller' import SettlerSDK from '../../sdks/settler/Settler' import { CreateIntentParams, IntentEvent, OpType, TokenFee } from '../../sdks/settler/types' import { makeTxSignAndSend } from '../utils' @@ -19,18 +19,20 @@ import { NONCE_LENGTH, } from './constants' +export const LAMPORTS_PER_SOL = 1_000_000_000 + /** * Generate a random 32-byte hex string for intent hash */ export function generateIntentHash(): string { - return Buffer.from(Array.from({ length: INTENT_HASH_LENGTH }, () => Math.floor(Math.random() * 256))).toString('hex') + return randomHex(INTENT_HASH_LENGTH).slice(2) } /** * Generate a random 32-byte hex string for nonce */ export function generateNonce(): string { - return Buffer.from(Array.from({ length: NONCE_LENGTH }, () => Math.floor(Math.random() * 256))).toString('hex') + return randomHex(NONCE_LENGTH).slice(2) } /** @@ -88,60 +90,6 @@ export async function createTestIntent( return intentHash } -/** - * Create a validated intent (with validations set to meet min_validations requirement) - */ -export async function createValidatedIntent( - solverSdk: SettlerSDK, - solverProvider: LiteSVMProvider, - client: LiteSVM, - options: { - intentHash?: string - minValidations?: number - isFinal?: boolean - deadline?: number - } = {} -): Promise { - const intentHash = await createTestIntent(solverSdk, solverProvider, { - ...options, - isFinal: options.isFinal ?? true, - }) - - // Set validations to meet min_validations requirement - const intentKey = solverSdk.getIntentKey(intentHash) - const intentAccount = client.getAccount(intentKey) - if (intentAccount) { - const intentData = Buffer.from(intentAccount.data) - // validations is at offset: 8 (disc) + 1 (op) + 32 (user) + 32 (intent_creator) + 32 (intent_hash) + 32 (nonce) + 8 (deadline) + 2 (min_validations) = 147 - // validations is u16, so 2 bytes - const minValidations = options.minValidations ?? DEFAULT_MIN_VALIDATIONS - intentData.writeUInt16LE(minValidations, 147) - client.setAccount(intentKey, { - ...intentAccount, - data: intentData, - }) - } - - return intentHash -} - -/** - * Creates an allowlisted entity (validator, axia, or solver) - */ -export async function createAllowlistedEntity( - controllerSdk: ControllerSDK, - provider: LiteSVMProvider, - entityType: EntityType, - entityKeypair?: Keypair -): Promise { - const entity = entityKeypair || Keypair.generate() - const allowlistIx = await controllerSdk.createEntityRegistryIx(entityType, entity.publicKey) - await makeTxSignAndSend(provider, allowlistIx) - return entity -} - -export const LAMPORTS_PER_SOL = 1_000_000_000 - /** * Helper to expect transaction errors consistently */ diff --git a/yarn.lock b/yarn.lock index 9081310..e3c33a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -526,6 +526,11 @@ dependencies: "@noble/hashes" "1.7.2" +"@noble/ed25519@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-3.0.0.tgz#720d4cdb6b5f632e29164a7e9d5cdfeb82a7ac86" + integrity sha512-QyteqMNm0GLqfa5SoYbSC3+Pvykwpn95Zgth4MFVSMKBB75ELl9tX1LAVsN4c3HXOrakHsF2gL4zWDAYCcsnzg== + "@noble/hashes@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" @@ -4009,7 +4014,16 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4066,7 +4080,14 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -4504,7 +4525,16 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From ba1d1447b53364d35610f8879ee6a7eb6080513f Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Mon, 5 Jan 2026 16:36:14 -0300 Subject: [PATCH 35/39] Code review: change crate::controller to controller --- .../svm/programs/settler/src/instructions/create_proposal.rs | 4 ++-- .../svm/programs/settler/src/instructions/execute_proposal.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/create_proposal.rs b/packages/svm/programs/settler/src/instructions/create_proposal.rs index 2c85dc7..d62a1d8 100644 --- a/packages/svm/programs/settler/src/instructions/create_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/create_proposal.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use crate::{ - controller::{accounts::EntityRegistry, types::EntityType}, + controller::{self, accounts::EntityRegistry, types::EntityType}, errors::SettlerError, state::{Intent, Proposal, ProposalInstruction}, types::TokenFee, @@ -16,7 +16,7 @@ pub struct CreateProposal<'info> { #[account( seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], bump = solver_registry.bump, - seeds::program = crate::controller::ID, + seeds::program = controller::ID, )] pub solver_registry: Box>, diff --git a/packages/svm/programs/settler/src/instructions/execute_proposal.rs b/packages/svm/programs/settler/src/instructions/execute_proposal.rs index ce1dc7d..ac7ab12 100644 --- a/packages/svm/programs/settler/src/instructions/execute_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/execute_proposal.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use crate::{ - controller::{accounts::EntityRegistry, types::EntityType}, + controller::{self, accounts::EntityRegistry, types::EntityType}, errors::SettlerError, state::{FulfilledIntent, Intent, Proposal}, types::IntentEvent, @@ -15,7 +15,7 @@ pub struct ExecuteProposal<'info> { #[account( seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()], bump = solver_registry.bump, - seeds::program = crate::controller::ID, + seeds::program = controller::ID, )] pub solver_registry: Box>, From 0ff652965cd557f51b2da35afdb796e61b232c5d Mon Sep 17 00:00:00 2001 From: Guido Dipietro <48221146+GuidoDipietro@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:31:50 -0300 Subject: [PATCH 36/39] (1) Add Controller program (#45) --- packages/svm/Anchor.toml | 3 +- packages/svm/Cargo.lock | 7 + packages/svm/idls/controller.json | 518 ++++++++++++++++++ packages/svm/package.json | 11 +- packages/svm/programs/controller/Cargo.toml | 27 + .../svm/programs/controller/src/constants.rs | 4 + .../svm/programs/controller/src/errors.rs | 10 + .../src/instructions/close_entity_registry.rs | 52 ++ .../controller/src/instructions/initialize.rs | 36 ++ .../controller/src/instructions/mod.rs | 9 + .../controller/src/instructions/set_admin.rs | 36 ++ .../src/instructions/set_allowed_entity.rs | 46 ++ packages/svm/programs/controller/src/lib.rs | 40 ++ .../controller/src/state/entity_registry.rs | 11 + .../controller/src/state/global_settings.rs | 8 + .../svm/programs/controller/src/state/mod.rs | 5 + .../controller/src/types/entity_type.rs | 13 + .../svm/programs/controller/src/types/mod.rs | 3 + packages/svm/rust-toolchain.toml | 4 + packages/svm/sdks/controller/Controller.ts | 99 ++++ packages/svm/tests/controller.test.ts | 334 +++++++++++ packages/svm/tests/helpers/helpers.ts | 33 ++ packages/svm/tests/settler.test.ts | 46 -- packages/svm/tests/utils.ts | 39 +- 24 files changed, 1338 insertions(+), 56 deletions(-) create mode 100644 packages/svm/idls/controller.json create mode 100644 packages/svm/programs/controller/Cargo.toml create mode 100644 packages/svm/programs/controller/src/constants.rs create mode 100644 packages/svm/programs/controller/src/errors.rs create mode 100644 packages/svm/programs/controller/src/instructions/close_entity_registry.rs create mode 100644 packages/svm/programs/controller/src/instructions/initialize.rs create mode 100644 packages/svm/programs/controller/src/instructions/mod.rs create mode 100644 packages/svm/programs/controller/src/instructions/set_admin.rs create mode 100644 packages/svm/programs/controller/src/instructions/set_allowed_entity.rs create mode 100644 packages/svm/programs/controller/src/lib.rs create mode 100644 packages/svm/programs/controller/src/state/entity_registry.rs create mode 100644 packages/svm/programs/controller/src/state/global_settings.rs create mode 100644 packages/svm/programs/controller/src/state/mod.rs create mode 100644 packages/svm/programs/controller/src/types/entity_type.rs create mode 100644 packages/svm/programs/controller/src/types/mod.rs create mode 100644 packages/svm/rust-toolchain.toml create mode 100644 packages/svm/sdks/controller/Controller.ts create mode 100644 packages/svm/tests/controller.test.ts create mode 100644 packages/svm/tests/helpers/helpers.ts delete mode 100644 packages/svm/tests/settler.test.ts diff --git a/packages/svm/Anchor.toml b/packages/svm/Anchor.toml index 1bb4fa9..e1ff45d 100644 --- a/packages/svm/Anchor.toml +++ b/packages/svm/Anchor.toml @@ -7,6 +7,7 @@ skip-lint = false [programs.localnet] settler = "HbNt35Ng8aM4NUy39evpCQqXEC4Nmaq16ewY8dyNF6NF" +controller = "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..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" diff --git a/packages/svm/idls/controller.json b/packages/svm/idls/controller.json new file mode 100644 index 0000000..71f8265 --- /dev/null +++ b/packages/svm/idls/controller.json @@ -0,0 +1,518 @@ +{ + "address": "7PwVkjnnapxytWFW69WFDLhfVZZgKhBE9m3zwcDZmncr", + "metadata": { + "name": "controller", + "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_allowlist_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": "AllowlistStatus" + } + } + } + ] + }, + { + "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": "SetEntityAllowlistStatusEvent", + "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": "AllowlistStatus" + } + } + }, + { + "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": "SetEntityAllowlistStatusEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "entity_type", + "type": { + "defined": { + "name": "EntityType" + } + } + }, + { + "name": "entity_pubkey", + "type": "pubkey" + }, + { + "name": "status", + "type": { + "defined": { + "name": "AllowlistStatus" + } + } + }, + { + "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": "AllowlistStatus", + "repr": { + "kind": "rust" + }, + "type": { + "kind": "enum", + "variants": [ + { + "name": "Allowlisted" + }, + { + "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/controller/Cargo.toml b/packages/svm/programs/controller/Cargo.toml new file mode 100644 index 0000000..29e05b6 --- /dev/null +++ b/packages/svm/programs/controller/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "controller" +version = "0.1.0" +description = "Manages allowlist for Mimic Protocol entities" +edition = "2021" +license = "GPL-3.0" + +[lib] +crate-type = ["cdylib", "lib"] +name = "controller" + +[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/controller/src/constants.rs b/packages/svm/programs/controller/src/constants.rs new file mode 100644 index 0000000..c2edf8e --- /dev/null +++ b/packages/svm/programs/controller/src/constants.rs @@ -0,0 +1,4 @@ +pub const DEPLOYER_KEY: &str = env!( + "DEPLOYER_KEY", + "Please set the DEPLOYER_KEY env variable before compiling." +); diff --git a/packages/svm/programs/controller/src/errors.rs b/packages/svm/programs/controller/src/errors.rs new file mode 100644 index 0000000..3c6c177 --- /dev/null +++ b/packages/svm/programs/controller/src/errors.rs @@ -0,0 +1,10 @@ +use anchor_lang::prelude::*; + +#[error_code] +pub enum ControllerError { + #[msg("Only deployer can call this instruction")] + OnlyDeployer, + + #[msg("Only admin can call this instruction")] + OnlyAdmin, +} 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/initialize.rs b/packages/svm/programs/controller/src/instructions/initialize.rs new file mode 100644 index 0000000..094cd0a --- /dev/null +++ b/packages/svm/programs/controller/src/instructions/initialize.rs @@ -0,0 +1,36 @@ +use anchor_lang::prelude::*; +use std::str::FromStr; + +use crate::{constants::DEPLOYER_KEY, errors::ControllerError, 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) -> Result<()> { + require_keys_eq!( + ctx.accounts.deployer.key(), + Pubkey::from_str(DEPLOYER_KEY).unwrap(), + ControllerError::OnlyDeployer, + ); + + let global_settings = &mut ctx.accounts.global_settings; + + global_settings.admin = admin; + global_settings.bump = ctx.bumps.global_settings; + + Ok(()) +} diff --git a/packages/svm/programs/controller/src/instructions/mod.rs b/packages/svm/programs/controller/src/instructions/mod.rs new file mode 100644 index 0000000..3028dc9 --- /dev/null +++ b/packages/svm/programs/controller/src/instructions/mod.rs @@ -0,0 +1,9 @@ +pub mod close_entity_registry; +pub mod initialize; +pub mod set_admin; +pub mod set_allowed_entity; + +pub use close_entity_registry::*; +pub use initialize::*; +pub use set_admin::*; +pub use set_allowed_entity::*; diff --git a/packages/svm/programs/controller/src/instructions/set_admin.rs b/packages/svm/programs/controller/src/instructions/set_admin.rs new file mode 100644 index 0000000..dc59043 --- /dev/null +++ b/packages/svm/programs/controller/src/instructions/set_admin.rs @@ -0,0 +1,36 @@ +use anchor_lang::prelude::*; + +use crate::{errors::ControllerError, 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 @ ControllerError::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; + + emit!(SetAdminEvent { + new_admin, + timestamp: Clock::get()?.unix_timestamp as u64, + }); + + Ok(()) +} + +#[event] +pub struct SetAdminEvent { + pub new_admin: Pubkey, + pub timestamp: u64, +} diff --git a/packages/svm/programs/controller/src/instructions/set_allowed_entity.rs b/packages/svm/programs/controller/src/instructions/set_allowed_entity.rs new file mode 100644 index 0000000..f264eea --- /dev/null +++ b/packages/svm/programs/controller/src/instructions/set_allowed_entity.rs @@ -0,0 +1,46 @@ +use anchor_lang::prelude::*; + +use crate::{ + errors::ControllerError, + state::{EntityRegistry, GlobalSettings}, + types::EntityType, +}; + +#[derive(Accounts)] +#[instruction(entity_type: EntityType, entity_pubkey: Pubkey)] +pub struct SetAllowedEntity<'info> { + #[account(mut)] + pub admin: Signer<'info>, + + #[account( + init, + 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 @ ControllerError::OnlyAdmin + )] + pub global_settings: Box>, + + pub system_program: Program<'info, System>, +} + +pub fn set_allowed_entity( + ctx: Context, + entity_type: EntityType, + entity_pubkey: Pubkey, +) -> Result<()> { + let entity_registry = &mut ctx.accounts.entity_registry; + + entity_registry.entity_type = entity_type; + entity_registry.entity_pubkey = entity_pubkey; + entity_registry.bump = ctx.bumps.entity_registry; + + Ok(()) +} diff --git a/packages/svm/programs/controller/src/lib.rs b/packages/svm/programs/controller/src/lib.rs new file mode 100644 index 0000000..a52a231 --- /dev/null +++ b/packages/svm/programs/controller/src/lib.rs @@ -0,0 +1,40 @@ +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 controller { + use super::*; + + pub fn initialize(ctx: Context, admin: Pubkey) -> Result<()> { + instructions::initialize(ctx, admin) + } + + pub fn set_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { + instructions::set_admin(ctx, new_admin) + } + + pub fn set_allowed_entity( + ctx: Context, + entity_type: EntityType, + entity_pubkey: Pubkey, + ) -> Result<()> { + instructions::set_allowed_entity(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 new file mode 100644 index 0000000..dbf917c --- /dev/null +++ b/packages/svm/programs/controller/src/state/entity_registry.rs @@ -0,0 +1,11 @@ +use anchor_lang::prelude::*; + +use crate::types::EntityType; + +#[account] +#[derive(InitSpace)] +pub struct EntityRegistry { + pub entity_type: EntityType, + pub entity_pubkey: Pubkey, + pub bump: u8, +} diff --git a/packages/svm/programs/controller/src/state/global_settings.rs b/packages/svm/programs/controller/src/state/global_settings.rs new file mode 100644 index 0000000..6027636 --- /dev/null +++ b/packages/svm/programs/controller/src/state/global_settings.rs @@ -0,0 +1,8 @@ +use anchor_lang::prelude::*; + +#[account] +#[derive(InitSpace)] +pub struct GlobalSettings { + pub admin: Pubkey, + pub bump: u8, +} diff --git a/packages/svm/programs/controller/src/state/mod.rs b/packages/svm/programs/controller/src/state/mod.rs new file mode 100644 index 0000000..e0bef0c --- /dev/null +++ b/packages/svm/programs/controller/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/controller/src/types/entity_type.rs b/packages/svm/programs/controller/src/types/entity_type.rs new file mode 100644 index 0000000..1d798cc --- /dev/null +++ b/packages/svm/programs/controller/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/controller/src/types/mod.rs b/packages/svm/programs/controller/src/types/mod.rs new file mode 100644 index 0000000..5e95bd3 --- /dev/null +++ b/packages/svm/programs/controller/src/types/mod.rs @@ -0,0 +1,3 @@ +pub mod entity_type; + +pub use entity_type::*; 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/controller/Controller.ts b/packages/svm/sdks/controller/Controller.ts new file mode 100644 index 0000000..e2e7b72 --- /dev/null +++ b/packages/svm/sdks/controller/Controller.ts @@ -0,0 +1,99 @@ +import { IdlTypes, Program, Provider, web3 } from '@coral-xyz/anchor' + +import * as ControllerIDL from '../../target/idl/controller.json' +import { Controller } from '../../target/types/controller' + +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 + + constructor(provider: Provider) { + this.program = new Program(ControllerIDL, provider) + } + + async initializeIx(admin: web3.PublicKey): Promise { + const globalSettings = this.getGlobalSettingsPubkey() + const ix = await this.program.methods + .initialize(admin) + .accountsPartial({ + deployer: this.getSignerKey(), + globalSettings, + }) + .instruction() + return ix + } + + async setAdmin(newAdmin: web3.PublicKey): Promise { + const globalSettings = this.getGlobalSettingsPubkey() + const ix = await this.program.methods + .setAdmin(newAdmin) + .accountsPartial({ + admin: this.getSignerKey(), + globalSettings, + }) + .instruction() + return ix + } + + async setAllowedEntityIx(entityType: EntityType, entityPubkey: web3.PublicKey): Promise { + const entityRegistry = this.getEntityRegistryPubkey(entityType, entityPubkey) + const globalSettings = this.getGlobalSettingsPubkey() + const ix = await this.program.methods + .setAllowedEntity(this.entityTypeToAnchorEnum(entityType), 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(), + entityRegistry, + 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}`) + } +} diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts new file mode 100644 index 0000000..bf070a1 --- /dev/null +++ b/packages/svm/tests/controller.test.ts @@ -0,0 +1,334 @@ +/* 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 ControllerSDK, { EntityType } from '../sdks/controller/Controller' +import * as ControllerIDL from '../target/idl/controller.json' +import { Controller } from '../target/types/controller' +import { expectTransactionError, randomKeypair, randomPubkey, toLamports } from './helpers/helpers' +import { makeTxSignAndSend, warpSeconds } from './utils' + +describe('Controller', () => { + let client: LiteSVM + + let deployer: web3.Keypair + let admin: web3.Keypair + let otherAdmin: web3.Keypair + let malicious: web3.Keypair + + let deployerProvider: LiteSVMProvider + let adminProvider: LiteSVMProvider + let otherAdminProvider: LiteSVMProvider + let maliciousProvider: LiteSVMProvider + + let program: Program + + let deployerSdk: ControllerSDK + let adminSdk: ControllerSDK + let otherAdminSdk: ControllerSDK + let maliciousSdk: ControllerSDK + + before(async () => { + deployer = web3.Keypair.fromSecretKey( + Uint8Array.from(JSON.parse(fs.readFileSync(path.join(os.homedir(), '.config', 'solana', 'id.json'), 'utf8'))) + ) + admin = randomKeypair() + otherAdmin = randomKeypair() + malicious = randomKeypair() + + client = fromWorkspace(path.join(__dirname, '../')).withBuiltins() + + deployerProvider = new LiteSVMProvider(client, new Wallet(deployer)) + adminProvider = new LiteSVMProvider(client, new Wallet(admin)) + otherAdminProvider = new LiteSVMProvider(client, new Wallet(otherAdmin)) + maliciousProvider = new LiteSVMProvider(client, new Wallet(malicious)) + + program = new Program(ControllerIDL as any, deployerProvider) + + deployerSdk = new ControllerSDK(deployerProvider) + adminSdk = new ControllerSDK(adminProvider) + otherAdminSdk = new ControllerSDK(otherAdminProvider) + maliciousSdk = new ControllerSDK(maliciousProvider) + + 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) + }) + + beforeEach(() => { + client.expireBlockhash() + }) + + 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) + + 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) + + 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) + + expectTransactionError(res, 'already in use') + }) + }) + }) + + 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) + + 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) + }) + + 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') + }) + }) + + 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()) + }) + }) + + 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()) + }) + }) + }) + }) +}) diff --git a/packages/svm/tests/helpers/helpers.ts b/packages/svm/tests/helpers/helpers.ts new file mode 100644 index 0000000..717d553 --- /dev/null +++ b/packages/svm/tests/helpers/helpers.ts @@ -0,0 +1,33 @@ +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 + */ +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) + } +} + +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 +} 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 - }) - }) -}) 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) + ) + ) } From 1ac650365b2c7526112309fd5bc5075108493ed2 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 6 Jan 2026 14:58:51 -0300 Subject: [PATCH 37/39] Revert "Merge branch 'solana/settler' into solana/3-settler-proposal-lifecycle" This reverts commit e690d3ced7844a3c2ed1653dd8c51923ee243fc5, reversing changes made to ba1d1447b53364d35610f8879ee6a7eb6080513f. --- README.md | 2 +- packages/evm/hardhat.config.ts | 6 - .../Create3Controller#Controller.dbg.json | 4 - .../Create3Controller#Controller.json | 392 ----- .../Create3Controller#ICreateX.dbg.json | 4 - .../artifacts/Create3Controller#ICreateX.json | 51 - .../Create3Settler#ICreateX.dbg.json | 4 - .../artifacts/Create3Settler#ICreateX.json | 51 - .../artifacts/Create3Settler#Settler.dbg.json | 4 - .../artifacts/Create3Settler#Settler.json | 1488 ----------------- ...22028aefba6db9743fbac66c2677f7feca3ad.json | 266 --- .../chain-1/deployed_addresses.json | 6 - .../deployments/chain-1/journal.jsonl | 20 - packages/svm/idls/controller.json | 168 +- packages/svm/programs/controller/Cargo.toml | 2 +- 15 files changed, 78 insertions(+), 2390 deletions(-) delete mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.dbg.json delete mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.json delete mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.dbg.json delete mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.json delete mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.dbg.json delete mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.json delete mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.dbg.json delete mode 100644 packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.json delete mode 100644 packages/evm/ignition/deployments/chain-1/build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json delete mode 100644 packages/evm/ignition/deployments/chain-1/deployed_addresses.json delete mode 100644 packages/evm/ignition/deployments/chain-1/journal.jsonl diff --git a/README.md b/README.md index 49de975..a474ea6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Mimic Protocol -

Blockchain execution protocol

+

Blockchain automation protocol

diff --git a/packages/evm/hardhat.config.ts b/packages/evm/hardhat.config.ts index b7317e5..28aea2c 100644 --- a/packages/evm/hardhat.config.ts +++ b/packages/evm/hardhat.config.ts @@ -21,12 +21,6 @@ const config: HardhatUserConfig = { }, }, networks: { - ethereum: { - type: 'http', - chainId: 1, - url: process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com', - accounts: process.env.DEPLOYER_PRIVATE_KEY ? [process.env.DEPLOYER_PRIVATE_KEY] : [], - }, optimism: { type: 'http', chainId: 10, diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.dbg.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.dbg.json deleted file mode 100644 index e872235..0000000 --- a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.dbg.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "_format": "hh-sol-dbg-1", - "buildInfo": "../build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.json deleted file mode 100644 index 91966d3..0000000 --- a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#Controller.json +++ /dev/null @@ -1,392 +0,0 @@ -{ - "_format": "hh3-artifact-1", - "contractName": "Controller", - "sourceName": "contracts/Controller.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address[]", - "name": "solvers", - "type": "address[]" - }, - { - "internalType": "address[]", - "name": "executors", - "type": "address[]" - }, - { - "internalType": "address[]", - "name": "proposalSigners", - "type": "address[]" - }, - { - "internalType": "address[]", - "name": "validators", - "type": "address[]" - }, - { - "internalType": "uint8", - "name": "_minValidations", - "type": "uint8" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ControllerInputInvalidLength", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "OwnableInvalidOwner", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "OwnableUnauthorizedAccount", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "executor", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "allowed", - "type": "bool" - } - ], - "name": "ExecutorAllowedSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint8", - "name": "newMinValidation", - "type": "uint8" - } - ], - "name": "MinValidationSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "proposalSigner", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "allowed", - "type": "bool" - } - ], - "name": "ProposalSignerAllowedSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "solver", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "allowed", - "type": "bool" - } - ], - "name": "SolverAllowedSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "validator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "allowed", - "type": "bool" - } - ], - "name": "ValidatorAllowedSet", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isExecutorAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isProposalSignerAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isSolverAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isValidatorAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "minValidations", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "executors", - "type": "address[]" - }, - { - "internalType": "bool[]", - "name": "alloweds", - "type": "bool[]" - } - ], - "name": "setAllowedExecutors", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "signers", - "type": "address[]" - }, - { - "internalType": "bool[]", - "name": "alloweds", - "type": "bool[]" - } - ], - "name": "setAllowedProposalSigners", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "solvers", - "type": "address[]" - }, - { - "internalType": "bool[]", - "name": "alloweds", - "type": "bool[]" - } - ], - "name": "setAllowedSolvers", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "validators", - "type": "address[]" - }, - { - "internalType": "bool[]", - "name": "alloweds", - "type": "bool[]" - } - ], - "name": "setAllowedValidators", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "newMinValidations", - "type": "uint8" - } - ], - "name": "setMinValidations", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x608060405234801561000f575f5ffd5b50604051610e6c380380610e6c83398101604081905261002e91610444565b856001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b61006581610172565b505f5b85518110156100a35761009b8682815181106100865761008661051f565b602002602001015160016101c160201b60201c565b600101610068565b505f5b84518110156100e1576100d98582815181106100c4576100c461051f565b6020026020010151600161022060201b60201c565b6001016100a6565b505f5b835181101561011f576101178482815181106101025761010261051f565b6020026020010151600161027760201b60201c565b6001016100e4565b505f5b825181101561015d576101558382815181106101405761014061051f565b602002602001015160016102ce60201b60201c565b600101610122565b5061016781610325565b505050505050610533565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f15091015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610214565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c39101610214565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610214565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b80516001600160a01b0381168114610378575f5ffd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126103a0575f5ffd5b81516001600160401b038111156103b9576103b961037d565b604051600582901b90603f8201601f191681016001600160401b03811182821017156103e7576103e761037d565b604052918252602081850181019290810186841115610404575f5ffd5b6020860192505b8383101561042a5761041c83610362565b81526020928301920161040b565b5095945050505050565b805160ff81168114610378575f5ffd5b5f5f5f5f5f5f60c08789031215610459575f5ffd5b61046287610362565b60208801519096506001600160401b0381111561047d575f5ffd5b61048989828a01610391565b604089015190965090506001600160401b038111156104a6575f5ffd5b6104b289828a01610391565b606089015190955090506001600160401b038111156104cf575f5ffd5b6104db89828a01610391565b608089015190945090506001600160401b038111156104f8575f5ffd5b61050489828a01610391565b92505061051360a08801610434565b90509295509295509295565b634e487b7160e01b5f52603260045260245ffd5b61092c806105405f395ff3fe608060405234801561000f575f5ffd5b50600436106100da575f3560e01c80637d701102116100885780638da5cb5b116100635780638da5cb5b146101ce5780638f72c136146101e8578063d9d8edb9146101fb578063f2fde38b1461021d575f5ffd5b80637d701102146101865780637e8d9637146101a857806384aaa224146101bb575f5ffd5b806361655670116100b8578063616556701461013d578063680038c71461015f578063715018a61461017e575f5ffd5b8063090de7df146100de578063163eed9e146100f35780633d41132f1461012a575b5f5ffd5b6100f16100ec3660046107df565b610230565b005b6101156101013660046108a2565b60036020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100f16101383660046107df565b6102ae565b61011561014b3660046108a2565b60046020525f908152604090205460ff1681565b60055461016c9060ff1681565b60405160ff9091168152602001610121565b6100f1610327565b6101156101943660046108a2565b60026020525f908152604090205460ff1681565b6100f16101b63660046108c2565b61033a565b6100f16101c93660046107df565b61034e565b5f546040516001600160a01b039091168152602001610121565b6100f16101f63660046107df565b6103c7565b6101156102093660046108a2565b60016020525f908152604090205460ff1681565b6100f161022b3660046108a2565b610440565b610238610498565b805182511461025a57604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576102a183828151811061027a5761027a6108e2565b6020026020010151838381518110610294576102946108e2565b60200260200101516104dd565b60010161025c565b505050565b6102b6610498565b80518251146102d857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a95761031f8382815181106102f8576102f86108e2565b6020026020010151838381518110610312576103126108e2565b602002602001015161053c565b6001016102da565b61032f610498565b6103385f610593565b565b610342610498565b61034b816105fa565b50565b610356610498565b805182511461037857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576103bf838281518110610398576103986108e2565b60200260200101518383815181106103b2576103b26108e2565b6020026020010151610637565b60010161037a565b6103cf610498565b80518251146103f157604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a957610438838281518110610411576104116108e2565b602002602001015183838151811061042b5761042b6108e2565b602002602001015161068e565b6001016103f3565b610448610498565b6001600160a01b03811661048f576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61034b81610593565b5f546001600160a01b03163314610338576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610486565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c391015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610530565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610530565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f1509101610530565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610722576107226106e5565b604052919050565b5f67ffffffffffffffff821115610743576107436106e5565b5060051b60200190565b80356001600160a01b0381168114610763575f5ffd5b919050565b5f82601f830112610777575f5ffd5b813561078a6107858261072a565b6106f9565b8082825260208201915060208360051b8601019250858311156107ab575f5ffd5b602085015b838110156107d557803580151581146107c7575f5ffd5b8352602092830192016107b0565b5095945050505050565b5f5f604083850312156107f0575f5ffd5b823567ffffffffffffffff811115610806575f5ffd5b8301601f81018513610816575f5ffd5b80356108246107858261072a565b8082825260208201915060208360051b850101925087831115610845575f5ffd5b6020840193505b8284101561086e5761085d8461074d565b82526020938401939091019061084c565b9450505050602083013567ffffffffffffffff81111561088c575f5ffd5b61089885828601610768565b9150509250929050565b5f602082840312156108b2575f5ffd5b6108bb8261074d565b9392505050565b5f602082840312156108d2575f5ffd5b813560ff811681146108bb575f5ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212204e1a03491d67aedff2296b499cf5fee36529062002d67b286fb08859eac0e13664736f6c634300081c0033", - "deployedBytecode": "0x608060405234801561000f575f5ffd5b50600436106100da575f3560e01c80637d701102116100885780638da5cb5b116100635780638da5cb5b146101ce5780638f72c136146101e8578063d9d8edb9146101fb578063f2fde38b1461021d575f5ffd5b80637d701102146101865780637e8d9637146101a857806384aaa224146101bb575f5ffd5b806361655670116100b8578063616556701461013d578063680038c71461015f578063715018a61461017e575f5ffd5b8063090de7df146100de578063163eed9e146100f35780633d41132f1461012a575b5f5ffd5b6100f16100ec3660046107df565b610230565b005b6101156101013660046108a2565b60036020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100f16101383660046107df565b6102ae565b61011561014b3660046108a2565b60046020525f908152604090205460ff1681565b60055461016c9060ff1681565b60405160ff9091168152602001610121565b6100f1610327565b6101156101943660046108a2565b60026020525f908152604090205460ff1681565b6100f16101b63660046108c2565b61033a565b6100f16101c93660046107df565b61034e565b5f546040516001600160a01b039091168152602001610121565b6100f16101f63660046107df565b6103c7565b6101156102093660046108a2565b60016020525f908152604090205460ff1681565b6100f161022b3660046108a2565b610440565b610238610498565b805182511461025a57604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576102a183828151811061027a5761027a6108e2565b6020026020010151838381518110610294576102946108e2565b60200260200101516104dd565b60010161025c565b505050565b6102b6610498565b80518251146102d857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a95761031f8382815181106102f8576102f86108e2565b6020026020010151838381518110610312576103126108e2565b602002602001015161053c565b6001016102da565b61032f610498565b6103385f610593565b565b610342610498565b61034b816105fa565b50565b610356610498565b805182511461037857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576103bf838281518110610398576103986108e2565b60200260200101518383815181106103b2576103b26108e2565b6020026020010151610637565b60010161037a565b6103cf610498565b80518251146103f157604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a957610438838281518110610411576104116108e2565b602002602001015183838151811061042b5761042b6108e2565b602002602001015161068e565b6001016103f3565b610448610498565b6001600160a01b03811661048f576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61034b81610593565b5f546001600160a01b03163314610338576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610486565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c391015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610530565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610530565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f1509101610530565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610722576107226106e5565b604052919050565b5f67ffffffffffffffff821115610743576107436106e5565b5060051b60200190565b80356001600160a01b0381168114610763575f5ffd5b919050565b5f82601f830112610777575f5ffd5b813561078a6107858261072a565b6106f9565b8082825260208201915060208360051b8601019250858311156107ab575f5ffd5b602085015b838110156107d557803580151581146107c7575f5ffd5b8352602092830192016107b0565b5095945050505050565b5f5f604083850312156107f0575f5ffd5b823567ffffffffffffffff811115610806575f5ffd5b8301601f81018513610816575f5ffd5b80356108246107858261072a565b8082825260208201915060208360051b850101925087831115610845575f5ffd5b6020840193505b8284101561086e5761085d8461074d565b82526020938401939091019061084c565b9450505050602083013567ffffffffffffffff81111561088c575f5ffd5b61089885828601610768565b9150509250929050565b5f602082840312156108b2575f5ffd5b6108bb8261074d565b9392505050565b5f602082840312156108d2575f5ffd5b813560ff811681146108bb575f5ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212204e1a03491d67aedff2296b499cf5fee36529062002d67b286fb08859eac0e13664736f6c634300081c0033", - "linkReferences": {}, - "deployedLinkReferences": {}, - "immutableReferences": {}, - "inputSourceName": "project/contracts/Controller.sol", - "buildInfoId": "27822028aefba6db9743fbac66c2677f7feca3ad" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.dbg.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.dbg.json deleted file mode 100644 index e872235..0000000 --- a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.dbg.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "_format": "hh-sol-dbg-1", - "buildInfo": "../build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.json deleted file mode 100644 index 0b1d6cc..0000000 --- a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Controller#ICreateX.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "_format": "hh3-artifact-1", - "contractName": "ICreateX", - "sourceName": "contracts/interfaces/ICreateX.sol", - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newContract", - "type": "address" - } - ], - "name": "ContractCreation", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "salt", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "initCode", - "type": "bytes" - } - ], - "name": "deployCreate3", - "outputs": [ - { - "internalType": "address", - "name": "newContract", - "type": "address" - } - ], - "stateMutability": "payable", - "type": "function" - } - ], - "bytecode": "0x", - "deployedBytecode": "0x", - "linkReferences": {}, - "deployedLinkReferences": {}, - "immutableReferences": {}, - "inputSourceName": "project/contracts/interfaces/ICreateX.sol", - "buildInfoId": "27822028aefba6db9743fbac66c2677f7feca3ad" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.dbg.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.dbg.json deleted file mode 100644 index e872235..0000000 --- a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.dbg.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "_format": "hh-sol-dbg-1", - "buildInfo": "../build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.json deleted file mode 100644 index 0b1d6cc..0000000 --- a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#ICreateX.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "_format": "hh3-artifact-1", - "contractName": "ICreateX", - "sourceName": "contracts/interfaces/ICreateX.sol", - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newContract", - "type": "address" - } - ], - "name": "ContractCreation", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "salt", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "initCode", - "type": "bytes" - } - ], - "name": "deployCreate3", - "outputs": [ - { - "internalType": "address", - "name": "newContract", - "type": "address" - } - ], - "stateMutability": "payable", - "type": "function" - } - ], - "bytecode": "0x", - "deployedBytecode": "0x", - "linkReferences": {}, - "deployedLinkReferences": {}, - "immutableReferences": {}, - "inputSourceName": "project/contracts/interfaces/ICreateX.sol", - "buildInfoId": "27822028aefba6db9743fbac66c2677f7feca3ad" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.dbg.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.dbg.json deleted file mode 100644 index e872235..0000000 --- a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.dbg.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "_format": "hh-sol-dbg-1", - "buildInfo": "../build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.json b/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.json deleted file mode 100644 index d6affec..0000000 --- a/packages/evm/ignition/deployments/chain-1/artifacts/Create3Settler#Settler.json +++ /dev/null @@ -1,1488 +0,0 @@ -{ - "_format": "hh3-artifact-1", - "contractName": "Settler", - "sourceName": "contracts/Settler.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "_controller", - "type": "address" - }, - { - "internalType": "address", - "name": "_owner", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], - "name": "AddressEmptyCode", - "type": "error" - }, - { - "inputs": [], - "name": "ECDSAInvalidSignature", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "length", - "type": "uint256" - } - ], - "name": "ECDSAInvalidSignatureLength", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "ECDSAInvalidSignatureS", - "type": "error" - }, - { - "inputs": [], - "name": "FailedCall", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } - ], - "name": "InsufficientBalance", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidShortString", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "OwnableInvalidOwner", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "OwnableUnauthorizedAccount", - "type": "error" - }, - { - "inputs": [], - "name": "ReentrancyGuardReentrantCall", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "SafeERC20FailedOperation", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amountOut", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "proposed", - "type": "uint256" - } - ], - "name": "SettlerAmountOutLtProposed", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "executor", - "type": "address" - } - ], - "name": "SettlerExecutorNotAllowed", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "name": "SettlerIntentPastDeadline", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "min", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "current", - "type": "uint256" - } - ], - "name": "SettlerIntentValidationsNotEnough", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - } - ], - "name": "SettlerInvalidChain", - "type": "error" - }, - { - "inputs": [], - "name": "SettlerInvalidProposedAmounts", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - } - ], - "name": "SettlerInvalidRecipient", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "settler", - "type": "address" - } - ], - "name": "SettlerInvalidSettler", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - } - ], - "name": "SettlerNonceAlreadyUsed", - "type": "error" - }, - { - "inputs": [], - "name": "SettlerNonceZero", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "post", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "pre", - "type": "uint256" - } - ], - "name": "SettlerPostBalanceOutLtPre", - "type": "error" - }, - { - "inputs": [], - "name": "SettlerProposalDataNotEmpty", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "name": "SettlerProposalPastDeadline", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "signer", - "type": "address" - } - ], - "name": "SettlerProposalSignerNotAllowed", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "proposed", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minAmount", - "type": "uint256" - } - ], - "name": "SettlerProposedAmountLtMinAmount", - "type": "error" - }, - { - "inputs": [], - "name": "SettlerRescueFundsRecipientZero", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "gasUsed", - "type": "uint256" - } - ], - "name": "SettlerSimulationSuccess", - "type": "error" - }, - { - "inputs": [], - "name": "SettlerSolverFeeInvalidLength", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "max", - "type": "uint256" - } - ], - "name": "SettlerSolverFeeTooHigh", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "solver", - "type": "address" - } - ], - "name": "SettlerSolverNotAllowed", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "lengthRequested", - "type": "uint256" - } - ], - "name": "SettlerTooManySafeguards", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "op", - "type": "uint8" - } - ], - "name": "SettlerUnknownIntentType", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "user", - "type": "address" - } - ], - "name": "SettlerUserNotSmartAccount", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "previous", - "type": "address" - }, - { - "internalType": "address", - "name": "current", - "type": "address" - } - ], - "name": "SettlerValidatorDuplicatedOrUnsorted", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "validator", - "type": "address" - } - ], - "name": "SettlerValidatorNotAllowed", - "type": "error" - }, - { - "inputs": [], - "name": "SmartAccountsHandlerZero", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "str", - "type": "string" - } - ], - "name": "StringTooLong", - "type": "error" - }, - { - "anonymous": false, - "inputs": [], - "name": "EIP712DomainChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "FundsRescued", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "topic", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "uint8", - "name": "op", - "type": "uint8" - }, - { - "components": [ - { - "internalType": "uint8", - "name": "op", - "type": "uint8" - }, - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "address", - "name": "settler", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "internalType": "struct MaxFee[]", - "name": "maxFees", - "type": "tuple[]" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "topic", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IntentEvent[]", - "name": "events", - "type": "tuple[]" - }, - { - "internalType": "bytes", - "name": "configSig", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "minValidations", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "validations", - "type": "bytes[]" - } - ], - "indexed": false, - "internalType": "struct Intent", - "name": "intent", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "uint256[]", - "name": "fees", - "type": "uint256[]" - } - ], - "indexed": false, - "internalType": "struct Proposal", - "name": "proposal", - "type": "tuple" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "output", - "type": "bytes" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "IntentExecuted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "intentsValidator", - "type": "address" - } - ], - "name": "IntentsValidatorSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "proposal", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "index", - "type": "uint256" - } - ], - "name": "ProposalExecuted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - } - ], - "name": "SafeguardSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "smartAccountsHandler", - "type": "address" - } - ], - "name": "SmartAccountsHandlerSet", - "type": "event" - }, - { - "inputs": [], - "name": "controller", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "eip712Domain", - "outputs": [ - { - "internalType": "bytes1", - "name": "fields", - "type": "bytes1" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "string", - "name": "version", - "type": "string" - }, - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "verifyingContract", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "salt", - "type": "bytes32" - }, - { - "internalType": "uint256[]", - "name": "extensions", - "type": "uint256[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint8", - "name": "op", - "type": "uint8" - }, - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "address", - "name": "settler", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "internalType": "struct MaxFee[]", - "name": "maxFees", - "type": "tuple[]" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "topic", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IntentEvent[]", - "name": "events", - "type": "tuple[]" - }, - { - "internalType": "bytes", - "name": "configSig", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "minValidations", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "validations", - "type": "bytes[]" - } - ], - "internalType": "struct Intent", - "name": "intent", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "uint256[]", - "name": "fees", - "type": "uint256[]" - } - ], - "internalType": "struct Proposal", - "name": "proposal", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "internalType": "struct Execution[]", - "name": "executions", - "type": "tuple[]" - } - ], - "name": "execute", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint8", - "name": "op", - "type": "uint8" - }, - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "address", - "name": "settler", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "internalType": "struct MaxFee[]", - "name": "maxFees", - "type": "tuple[]" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "topic", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IntentEvent[]", - "name": "events", - "type": "tuple[]" - }, - { - "internalType": "bytes", - "name": "configSig", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "minValidations", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "validations", - "type": "bytes[]" - } - ], - "internalType": "struct Intent", - "name": "intent", - "type": "tuple" - } - ], - "name": "getIntentHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "getNonceBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "uint256[]", - "name": "fees", - "type": "uint256[]" - } - ], - "internalType": "struct Proposal", - "name": "proposal", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint8", - "name": "op", - "type": "uint8" - }, - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "address", - "name": "settler", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "internalType": "struct MaxFee[]", - "name": "maxFees", - "type": "tuple[]" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "topic", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IntentEvent[]", - "name": "events", - "type": "tuple[]" - }, - { - "internalType": "bytes", - "name": "configSig", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "minValidations", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "validations", - "type": "bytes[]" - } - ], - "internalType": "struct Intent", - "name": "intent", - "type": "tuple" - }, - { - "internalType": "address", - "name": "solver", - "type": "address" - } - ], - "name": "getProposalHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "user", - "type": "address" - } - ], - "name": "getUserSafeguard", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "intentsValidator", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "rescueFunds", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newIntentsValidator", - "type": "address" - } - ], - "name": "setIntentsValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "safeguard", - "type": "bytes" - } - ], - "name": "setSafeguard", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newSmartAccountsHandler", - "type": "address" - } - ], - "name": "setSmartAccountsHandler", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint8", - "name": "op", - "type": "uint8" - }, - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "address", - "name": "settler", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "internalType": "struct MaxFee[]", - "name": "maxFees", - "type": "tuple[]" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "topic", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IntentEvent[]", - "name": "events", - "type": "tuple[]" - }, - { - "internalType": "bytes", - "name": "configSig", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "minValidations", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "validations", - "type": "bytes[]" - } - ], - "internalType": "struct Intent", - "name": "intent", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "uint256[]", - "name": "fees", - "type": "uint256[]" - } - ], - "internalType": "struct Proposal", - "name": "proposal", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "internalType": "struct Execution[]", - "name": "executions", - "type": "tuple[]" - } - ], - "name": "simulate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "smartAccountsHandler", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "stateMutability": "payable", - "type": "receive" - } - ], - "bytecode": "0x610180604052348015610010575f5ffd5b506040516150e63803806150e683398101604081905261002f916102aa565b604080518082018252601681527f4d696d69632050726f746f636f6c20536574746c657200000000000000000000602080830191909152825180840190935260018352603160f81b9083015290826001600160a01b0381166100ab57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6100b4816101c4565b50600180556100c4826002610213565b610120526100d3816003610213565b61014052815160208084019190912060e052815190820120610100524660a05261015f60e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60805250503060c0526001600160a01b0382166101605260405161018290610282565b604051809103905ff08015801561019b573d5f5f3e3d5ffd5b50600480546001600160a01b0319166001600160a01b0392909216919091179055506104859050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f60208351101561022e5761022783610245565b905061023f565b816102398482610373565b5060ff90505b92915050565b5f5f829050601f8151111561026f578260405163305a27a960e01b81526004016100a2919061042d565b805161027a82610462565b179392505050565b6109c58061472183390190565b80516001600160a01b03811681146102a5575f5ffd5b919050565b5f5f604083850312156102bb575f5ffd5b6102c48361028f565b91506102d26020840161028f565b90509250929050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061030357607f821691505b60208210810361032157634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561036e57805f5260205f20601f840160051c8101602085101561034c5750805b601f840160051c820191505b8181101561036b575f8155600101610358565b50505b505050565b81516001600160401b0381111561038c5761038c6102db565b6103a08161039a84546102ef565b84610327565b6020601f8211600181146103d2575f83156103bb5750848201515b5f19600385901b1c1916600184901b17845561036b565b5f84815260208120601f198516915b8281101561040157878501518255602094850194600190920191016103e1565b508482101561041e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80516020808301519190811015610321575f1960209190910360031b1b16919050565b60805160a05160c05160e0516101005161012051610140516101605161421761050a5f395f81816103440152818161049c015281816106cd015281816112770152818161147401528181611585015261223b01525f610bd601525f610ba401525f61294301525f61291b01525f61287601525f6128a001525f6128ca01526142175ff3fe60806040526004361061010c575f3560e01c80638da5cb5b116100a1578063cbc9104511610071578063f2fde38b11610057578063f2fde38b14610314578063f77c479114610333578063fee2b7a714610366575f5ffd5b8063cbc91045146102d6578063f0801868146102f5575f5ffd5b80638da5cb5b14610239578063911929df14610255578063c422661f14610281578063c4bb6af5146102a0575f5ffd5b8063715018a6116100dc578063715018a6146101c057806377c8fb6b146101d45780637f411c79146101f357806384b0196e14610212575f5ffd5b80630fc27cf6146101175780632e5a2458146101385780634987ed2f1461016a5780636ccae054146101a1575f5ffd5b3661011357005b5f5ffd5b348015610122575f5ffd5b50610136610131366004612dc9565b610385565b005b348015610143575f5ffd5b50610157610152366004613366565b610399565b6040519081526020015b60405180910390f35b348015610175575f5ffd5b50600454610189906001600160a01b031681565b6040516001600160a01b039091168152602001610161565b3480156101ac575f5ffd5b506101366101bb3660046133de565b6103af565b3480156101cb575f5ffd5b50610136610465565b3480156101df575f5ffd5b50600554610189906001600160a01b031681565b3480156101fe575f5ffd5b5061013661020d36600461341c565b610478565b34801561021d575f5ffd5b50610226610587565b60405161016197969594939291906135ca565b348015610244575f5ffd5b505f546001600160a01b0316610189565b348015610260575f5ffd5b5061027461026f366004612dc9565b6105e5565b6040516101619190613653565b34801561028c575f5ffd5b5061013661029b366004612dc9565b61068e565b3480156102ab575f5ffd5b506101576102ba366004613665565b600660209081525f928352604080842090915290825290205481565b3480156102e1575f5ffd5b506101366102f036600461368f565b61069f565b348015610300575f5ffd5b5061013661030f36600461341c565b6106a9565b34801561031f575f5ffd5b5061013661032e366004612dc9565b61076e565b34801561033e575f5ffd5b506101897f000000000000000000000000000000000000000000000000000000000000000081565b348015610371575f5ffd5b506101576103803660046136c9565b6107c1565b61038d6107d1565b61039681610816565b50565b5f6103a58484846108ac565b90505b9392505050565b6103b76107d1565b6103bf610948565b6001600160a01b0382166103ff576040517f54328a0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61040a83838361098b565b816001600160a01b0316836001600160a01b03167fed2837b80b3489773c5f1ed30dd2884b4c90dbf7b87428ea03c49b24ef59a8058360405161044f91815260200190565b60405180910390a361046060018055565b505050565b61046d6107d1565b6104765f6109c4565b565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa1580156104e3573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050791906136fb565b6105345760405163a7ac57a960e01b81526001600160a01b03821660048201526024015b60405180910390fd5b5f5a9050610543836001610a20565b5f5a61054f908361371a565b9050806040517fa523ba4900000000000000000000000000000000000000000000000000000000815260040161052b91815260200190565b5f6060805f5f5f6060610598610b9d565b6105a0610bcf565b604080515f808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009b939a50919850469750309650945092509050565b6001600160a01b0381165f90815260076020526040902080546060919061060b90613739565b80601f016020809104026020016040519081016040528092919081815260200182805461063790613739565b80156106825780601f1061065957610100808354040283529160200191610682565b820191905f5260205f20905b81548152906001019060200180831161066557829003601f168201915b50505050509050919050565b6106966107d1565b61039681610bfc565b6103963382610c52565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa158015610714573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061073891906136fb565b6107605760405163a7ac57a960e01b81526001600160a01b038216600482015260240161052b565b61076a825f610a20565b5050565b6107766107d1565b6001600160a01b0381166107b8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f600482015260240161052b565b610396816109c4565b5f6107cb82610ccb565b92915050565b5f546001600160a01b03163314610476576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052b565b6001600160a01b038116610856576040517f42af048a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f93fa00498c991ad852fbb3361afb0e3507720aa9ff69a8386009b76134a6e654905f90a250565b5f7fabd98b1a5498f7ce6ce5fd671f1a1c2ad437b155c4c12281f925612823c72e886108d784610ccb565b83865f01518760200151805190602001206108f58960400151610d6e565b6040805160208101979097528601949094526001600160a01b039092166060850152608084015260a083015260c082015260e0016040516020818303038152906040528051906020012090509392505050565b600260015403610984576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036109b9576104608282610d80565b610460838383610e2b565b5f80546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610a28610948565b5f5b8251811015610b93575f838281518110610a4657610a4661376b565b60200260200101515f015190505f848381518110610a6657610a6661376b565b60200260200101516020015190505f858481518110610a8757610a8761376b565b6020026020010151604001519050610aa183838388610e9f565b6020838101516001600160a01b03165f9081526006825260408082206060870151835290925220439055825160ff16610ae357610ade838361164c565b610b4a565b825160ff165f1901610af957610ade83836119d0565b825160ff1660011901610b1057610ade8383611a90565b82516040517f7cdbcbf100000000000000000000000000000000000000000000000000000000815260ff909116600482015260240161052b565b610b558284336108ac565b6040518581527f120985525cfb78f6c03f96986f9687f49caf81e29350d21fa052e8c0df211ebb9060200160405180910390a2505050600101610a2a565b5061076a60018055565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006002611bb6565b905090565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006003611bb6565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f493e783f4ed464086f434b9d42b1b8fb97375d1fdc91692c73551281526d22be905f90a250565b6001600160a01b0382165f908152600760205260408120610c7291612d60565b6001600160a01b0382165f908152600760205260409020610c9382826137d7565b506040516001600160a01b038316907f5ac98b81e10e07689d7de8e9ec09900564d65647de8d039328d4cbdf575b28f4905f90a25050565b5f7fbd51147cb801db5aa8dc80920aabcbe45ea95c1767e31bc3339f62d8f0d046f9825f015183602001518460400151856060015186608001518760a0015180519060200120610d1e8960c00151611c60565b610d2b8a60e00151611da6565b8a61010001518b6101200151604051602001610d519b9a99989796959493929190613892565b604051602081830303815290604052805190602001209050919050565b5f81604051602001610d51919061390c565b80471015610dc3576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810182905260440161052b565b5f5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610e0d576040519150601f19603f3d011682016040523d82523d5f602084013e610e12565b606091505b509150915081610e2557610e2581611eba565b50505050565b6040516001600160a01b0383811660248301526044820183905261046091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611efc565b60408401516001600160a01b03163014610ef65760408085015190517f30b078bf0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6060840151610f31576040517f58e8d18100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808501516001600160a01b03165f90815260068252604080822060608801518352909252205415610fab57602084015160608501516040517f9756f94d0000000000000000000000000000000000000000000000000000000081526001600160a01b039092166004830152602482015260440161052b565b6005546001600160a01b0316156110e4576020808501516001600160a01b03165f9081526007909152604081208054610fe390613739565b80601f016020809104026020016040519081016040528092919081815260200182805461100f90613739565b801561105a5780601f106110315761010080835404028352916020019161105a565b820191905f5260205f20905b81548152906001019060200180831161103d57829003601f168201915b505050505090505f815111156110e2576005546040517fdbd902180000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063dbd90218906110b59088908590600401613b29565b5f6040518083038186803b1580156110cb575f5ffd5b505afa1580156110dd573d5f5f3e3d5ffd5b505050505b505b5f6110ee85611f81565b9050801561118d57428560800151116111425760808501516040517f95d3dc22000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b8351421080159061118b5784516040517f2c39717b000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b505b8360400151518560c0015151146111d0576040517f9dca144800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8560c0015151811015611273575f8660c0015182815181106111f6576111f661376b565b60200260200101516020015190505f8660400151838151811061121b5761121b61376b565b6020026020010151905081811115611269576040517f8ffa61fa000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161052b565b50506001016111d2565b505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663680038c76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112d1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190613b4d565b90505f8160ff1687610120015111611310578160ff16611317565b8661012001515b90508087610140015151101561136b57610140870151516040517fcd0ce69900000000000000000000000000000000000000000000000000000000815261052b918391600401918252602082015260400190565b5f5f90505f60405180602001604052806113848b610ccb565b905290505f61139a61139583611fd0565b61200d565b90505f5b8a61014001515181101561152f575f6113d5838d610140015184815181106113c8576113c861376b565b6020026020010151612054565b9050846001600160a01b0316816001600160a01b031611611435576040517fce98854c0000000000000000000000000000000000000000000000000000000081526001600160a01b0380871660048301528216602482015260440161052b565b6040517f616556700000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015291955085915f917f000000000000000000000000000000000000000000000000000000000000000090911690636165567090602401602060405180830381865afa1580156114bb573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114df91906136fb565b1590508015611525576040517ff24706660000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505060010161139e565b505f6115486115426113958c8e336108ac565b8a612054565b6040517f163eed9e0000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301529192505f917f0000000000000000000000000000000000000000000000000000000000000000169063163eed9e90602401602060405180830381865afa1580156115ca573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ee91906136fb565b1580156115f9575088155b9050801561163e576040517f100158a80000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505050505050505050505050565b5f8260a001518060200190518101906116659190613c16565b90505f82602001518060200190518101906116809190613d93565b905061168c828261207c565b60208401516004545f916116a9916001600160a01b0316906122ed565b905046835f01510361170b575f5b836040015151811015611709575f846040015182815181106116db576116db61376b565b60200260200101519050611700815f01518860200151865f0151846020015187612371565b506001016116b7565b505b5f611715846123b1565b83516040517f7b4da5de0000000000000000000000000000000000000000000000000000000081529192506001600160a01b031690637b4da5de906117609089908990600401613efe565b5f604051808303815f87803b158015611777575f5ffd5b505af1158015611789573d5f5f3e3d5ffd5b50505050468460200151036119c8575f84606001515167ffffffffffffffff8111156117b7576117b7612de4565b6040519080825280602002602001820160405280156117e0578160200160208202803683370190505b5090505f5b856060015151811015611990575f866060015182815181106118095761180961376b565b602002602001015190505f611821825f01513061246d565b90505f8584815181106118365761183661376b565b602002602001015190508082101561188b576040517ff5ad5ab700000000000000000000000000000000000000000000000000000000815260048101859052602481018390526044810182905260640161052b565b611895818361371a565b8585815181106118a7576118a761376b565b6020026020010181815250505f886040015185815181106118ca576118ca61376b565b60200260200101519050808686815181106118e7576118e761376b565b6020026020010151101561195557848686815181106119085761190861376b565b60209081029190910101516040517ff0a43aa7000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044810182905260640161052b565b611980845f015185604001518888815181106119735761197361376b565b602002602001015161098b565b5050600190920191506117e59050565b506119bb8787836040516020016119a79190613f22565b604051602081830303815290604052612524565b6119c68787856125b7565b505b505050505050565b5f8260a001518060200190518101906119e99190613f34565b90506119f5818361263b565b60208301516004545f91611a12916001600160a01b0316906122ed565b90505f5b826020015151811015611a69575f83602001518281518110611a3a57611a3a61376b565b60200260200101519050611a60815f015187602001518360400151846020015187612371565b50600101611a16565b50604080515f815260208101909152611a859085908590612524565b610e258484836125b7565b5f8260a00151806020019051810190611aa99190613fc0565b9050611aba818385602001516126fc565b5f81602001515167ffffffffffffffff811115611ad957611ad9612de4565b604051908082528060200260200182016040528015611b0c57816020015b6060815260200190600190039081611af75790505b5090505f5b826020015151811015611b93575f83602001518281518110611b3557611b3561376b565b602090810291909101810151878201518151928201516040830151600454939550611b6d946001600160a01b0390941693919061279a565b838381518110611b7f57611b7f61376b565b602090810291909101015250600101611b11565b50611baa8484836040516020016119a7919061410a565b610e25848460016125b7565b606060ff8314611bd057611bc98361282d565b90506107cb565b818054611bdc90613739565b80601f0160208091040260200160405190810160405280929190818152602001828054611c0890613739565b8015611c535780601f10611c2a57610100808354040283529160200191611c53565b820191905f5260205f20905b815481529060010190602001808311611c3657829003601f168201915b5050505050905092915050565b5f5f825167ffffffffffffffff811115611c7c57611c7c612de4565b604051908082528060200260200182016040528015611ca5578160200160208202803683370190505b5090505f5b8351811015611d76577fe44101eb81f50b64d4005e31312ea7bafd6f81357f9dce4fe51d5f2dcb939da4848281518110611ce657611ce661376b565b60200260200101515f0151858381518110611d0357611d0361376b565b602002602001015160200151604051602001611d3b939291909283526001600160a01b03919091166020830152604082015260600190565b60405160208183030381529060405280519060200120828281518110611d6357611d6361376b565b6020908102919091010152600101611caa565b5080604051602001611d88919061390c565b60405160208183030381529060405280519060200120915050919050565b5f5f825167ffffffffffffffff811115611dc257611dc2612de4565b604051908082528060200260200182016040528015611deb578160200160208202803683370190505b5090505f5b8351811015611d76577ff7a396f96f3810db1cfde06de38d32fc29d56b63cf9d9aee2c10c91ef7273bb1848281518110611e2c57611e2c61376b565b60200260200101515f0151858381518110611e4957611e4961376b565b60200260200101516020015180519060200120604051602001611e7f939291909283526020830191909152604082015260600190565b60405160208183030381529060405280519060200120828281518110611ea757611ea761376b565b6020908102919091010152600101611df0565b805115611eca5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f60205f8451602086015f885af180611f1b576040513d5f823e3d81fd5b50505f513d91508115611f32578060011415611f3f565b6001600160a01b0384163b155b15610e25576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b80515f9060ff1615611f9557506001919050565b5f8260a00151806020019051810190611fae9190613c16565b90508060200151815f015103611fc75750600192915050565b51461492915050565b8051604080517f07094367b2db06e3dbb414cf947644f8e08067ea238c5f41cb2fa5fea5be628e6020820152908101919091525f90606001610d51565b5f6107cb61201961286a565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b5f5f5f5f6120628686612993565b92509250925061207282826129dc565b5090949350505050565b81515f904614801590612093575046836020015114155b905080156120b65760405163308a43dd60e11b815246600482015260240161052b565b826060015151826040015151146120f9576040517f1816c20200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8360600151518110156121f2575f8460600151828151811061211f5761211f61376b565b602002602001015190505f81604001519050306001600160a01b0316816001600160a01b03160361216e57604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b5f826020015190505f8660400151858151811061218d5761218d61376b565b60200260200101519050818110156121e2576040517f1fef5f3300000000000000000000000000000000000000000000000000000000815260048101869052602481018290526044810183905260640161052b565b5050600190920191506120fb9050565b5060208301518351146104605781516040517f7d7011020000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201525f917f00000000000000000000000000000000000000000000000000000000000000001690637d70110290602401602060405180830381865afa158015612280573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122a491906136fb565b1590508015610e255782516040517fdb489af40000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6040517f5e07a6e60000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301525f9190841690635e07a6e690602401602060405180830381865afa15801561234d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a891906136fb565b801561239557600454612390906001600160a01b031685878686612adf565b6123aa565b6123aa6001600160a01b038616858585612b38565b5050505050565b606081606001515167ffffffffffffffff8111156123d1576123d1612de4565b6040519080825280602002602001820160405280156123fa578160200160208202803683370190505b50905046826020015103612468575f5b82606001515181101561246657612441836060015182815181106124305761243061376b565b60200260200101515f01513061246d565b8282815181106124535761245361376b565b602090810291909101015260010161240a565b505b919050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036124a357506001600160a01b038116316107cb565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015612500573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc9919061411c565b5f5b8360e0015151811015610e25575f8460e00151828151811061254a5761254a61376b565b60200260200101519050845f015160ff16815f015186602001516001600160a01b03167fee2c98c99b683f71058b8744fea294a411482f07d55c98b392917e9286f22f1388888887602001516040516125a69493929190614133565b60405180910390a450600101612526565b6020830151335f5b8560c00151518110156119c8575f8660c0015182815181106125e3576125e361376b565b60200260200101515f01519050612604816001600160a01b03166103481490565b61263257612632818585896040015186815181106126245761262461376b565b602002602001015189612371565b506001016125bf565b8151461461265e5760405163308a43dd60e11b815246600482015260240161052b565b60208101515115612682576040516393c58e1160e01b815260040160405180910390fd5b5f5b826020015151811015610460575f836020015182815181106126a8576126a861376b565b6020026020010151604001519050306001600160a01b0316816001600160a01b0316036126f357604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b50600101612684565b8251461461271f5760405163308a43dd60e11b815246600482015260240161052b565b60208201515115612743576040516393c58e1160e01b815260040160405180910390fd5b600454612759906001600160a01b0316826122ed565b610460576040517fcaec9bac0000000000000000000000000000000000000000000000000000000081526001600160a01b038216600482015260240161052b565b60606128238663ff883fcf60e01b878787876040516024016127bf949392919061418a565b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612b71565b9695505050505050565b60605f61283983612be3565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156128c257507f000000000000000000000000000000000000000000000000000000000000000046145b156128ec57507f000000000000000000000000000000000000000000000000000000000000000090565b610bca604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f5f83516041036129ca576020840151604085015160608601515f1a6129bc88828585612c23565b9550955095505050506129d5565b505081515f91506002905b9250925092565b5f8260038111156129ef576129ef61377f565b036129f8575050565b6001826003811115612a0c57612a0c61377f565b03612a43576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826003811115612a5757612a5761377f565b03612a91576040517ffce698f70000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6003826003811115612aa557612aa561377f565b0361076a576040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6040516001600160a01b038086166024830152808516604483015283166064820152608481018290526119c89086907ff18d03cc000000000000000000000000000000000000000000000000000000009060a4016127bf565b6040516001600160a01b038481166024830152838116604483015260648201839052610e259186918216906323b872dd90608401610e58565b60605f5f846001600160a01b031684604051612b8d91906141cb565b5f60405180830381855af49150503d805f8114612bc5576040519150601f19603f3d011682016040523d82523d5f602084013e612bca565b606091505b5091509150612bda858383612ceb565b95945050505050565b5f60ff8216601f8111156107cb576040517fb3512b0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115612c5c57505f91506003905082612ce1565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015612cad573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116612cd857505f925060019150829050612ce1565b92505f91508190505b9450945094915050565b606082612d0057612cfb82611eba565b6103a8565b8151158015612d1757506001600160a01b0384163b155b15612d59576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b50806103a8565b508054612d6c90613739565b5f825580601f10612d7b575050565b601f0160209004905f5260205f209081019061039691905b80821115612da6575f8155600101612d93565b5090565b6001600160a01b0381168114610396575f5ffd5b803561246881612daa565b5f60208284031215612dd9575f5ffd5b81356103a881612daa565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b60405290565b6040805190810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051610160810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b6040516080810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612eb457612eb4612de4565b604052919050565b5f67ffffffffffffffff821115612ed557612ed5612de4565b50601f01601f191660200190565b5f82601f830112612ef2575f5ffd5b8135612f05612f0082612ebc565b612e8b565b818152846020838601011115612f19575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f67ffffffffffffffff821115612f4e57612f4e612de4565b5060051b60200190565b5f60608284031215612f68575f5ffd5b612f70612df8565b823581529050602082013567ffffffffffffffff811115612f8f575f5ffd5b612f9b84828501612ee3565b602083015250604082013567ffffffffffffffff811115612fba575f5ffd5b8201601f81018413612fca575f5ffd5b8035612fd8612f0082612f35565b8082825260208201915060208360051b850101925086831115612ff9575f5ffd5b6020840193505b8284101561301b578335825260209384019390910190613000565b60408501525091949350505050565b60ff81168114610396575f5ffd5b80356124688161302a565b5f82601f830112613052575f5ffd5b8135613060612f0082612f35565b8082825260208201915060208360061b860101925085831115613081575f5ffd5b602085015b838110156130cc576040818803121561309d575f5ffd5b6130a5612e21565b81356130b081612daa565b8152602082810135818301529084529290920191604001613086565b5095945050505050565b5f82601f8301126130e5575f5ffd5b81356130f3612f0082612f35565b8082825260208201915060208360051b860101925085831115613114575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613137575f5ffd5b86016040818903601f1901121561314c575f5ffd5b613154612e21565b60208201358152604082013567ffffffffffffffff811115613174575f5ffd5b6131838a602083860101612ee3565b6020830152508085525050602083019250602081019050613119565b5f82601f8301126131ae575f5ffd5b81356131bc612f0082612f35565b8082825260208201915060208360051b8601019250858311156131dd575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613200575f5ffd5b61320f886020838a0101612ee3565b845250602092830192016131e2565b5f610160828403121561322f575f5ffd5b613237612e44565b905061324282613038565b815261325060208301612dbe565b602082015261326160408301612dbe565b6040820152606082810135908201526080808301359082015260a082013567ffffffffffffffff811115613293575f5ffd5b61329f84828501612ee3565b60a08301525060c082013567ffffffffffffffff8111156132be575f5ffd5b6132ca84828501613043565b60c08301525060e082013567ffffffffffffffff8111156132e9575f5ffd5b6132f5848285016130d6565b60e08301525061010082013567ffffffffffffffff811115613315575f5ffd5b61332184828501612ee3565b61010083015250610120828101359082015261014082013567ffffffffffffffff81111561334d575f5ffd5b6133598482850161319f565b6101408301525092915050565b5f5f5f60608486031215613378575f5ffd5b833567ffffffffffffffff81111561338e575f5ffd5b61339a86828701612f58565b935050602084013567ffffffffffffffff8111156133b6575f5ffd5b6133c28682870161321e565b92505060408401356133d381612daa565b809150509250925092565b5f5f5f606084860312156133f0575f5ffd5b83356133fb81612daa565b9250602084013561340b81612daa565b929592945050506040919091013590565b5f6020828403121561342c575f5ffd5b813567ffffffffffffffff811115613442575f5ffd5b8201601f81018413613452575f5ffd5b8035613460612f0082612f35565b8082825260208201915060208360051b850101925086831115613481575f5ffd5b602084015b8381101561355757803567ffffffffffffffff8111156134a4575f5ffd5b85016060818a03601f190112156134b9575f5ffd5b6134c1612df8565b602082013567ffffffffffffffff8111156134da575f5ffd5b6134e98b60208386010161321e565b825250604082013567ffffffffffffffff811115613505575f5ffd5b6135148b602083860101612f58565b602083015250606082013567ffffffffffffffff811115613533575f5ffd5b6135428b602083860101612ee3565b60408301525084525060209283019201613486565b509695505050505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f8151808452602084019350602083015f5b828110156135c05781518652602095860195909101906001016135a2565b5093949350505050565b7fff000000000000000000000000000000000000000000000000000000000000008816815260e060208201525f61360460e0830189613562565b82810360408401526136168189613562565b90508660608401526001600160a01b03861660808401528460a084015282810360c08401526136458185613590565b9a9950505050505050505050565b602081525f6103a86020830184613562565b5f5f60408385031215613676575f5ffd5b823561368181612daa565b946020939093013593505050565b5f6020828403121561369f575f5ffd5b813567ffffffffffffffff8111156136b5575f5ffd5b6136c184828501612ee3565b949350505050565b5f602082840312156136d9575f5ffd5b813567ffffffffffffffff8111156136ef575f5ffd5b6136c18482850161321e565b5f6020828403121561370b575f5ffd5b815180151581146103a8575f5ffd5b818103818111156107cb57634e487b7160e01b5f52601160045260245ffd5b600181811c9082168061374d57607f821691505b60208210810361246657634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b601f82111561046057805f5260205f20601f840160051c810160208510156137b85750805b601f840160051c820191505b818110156123aa575f81556001016137c4565b815167ffffffffffffffff8111156137f1576137f1612de4565b613805816137ff8454613739565b84613793565b6020601f821160018114613837575f83156138205750848201515b5f19600385901b1c1916600184901b1784556123aa565b5f84815260208120601f198516915b828110156138665787850151825560209485019460019092019101613846565b508482101561388357868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8b815260ff8b1660208201526001600160a01b038a1660408201526001600160a01b03891660608201528760808201528660a08201528560c08201528460e0820152836101008201526101606101208201525f6138f3610160830185613562565b9050826101408301529c9b505050505050505050505050565b81515f90829060208501835b82811015613936578151845260209384019390910190600101613918565b509195945050505050565b5f8151808452602084019350602083015f5b828110156135c057815180516001600160a01b031687526020908101518188015260409096019590910190600101613953565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f1985840301885281518051845260208101519050604060208501526139d46040850182613562565b6020998a01999094509290920191506001016139a2565b50909695505050505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f19858403018852613a2f838351613562565b6020988901989093509190910190600101613a13565b805160ff1682525f6020820151613a6760208501826001600160a01b03169052565b506040820151613a8260408501826001600160a01b03169052565b50606082015160608401526080820151608084015260a082015161016060a0850152613ab2610160850182613562565b905060c083015184820360c0860152613acb8282613941565b91505060e083015184820360e0860152613ae58282613986565b915050610100830151848203610100860152613b018282613562565b915050610120830151610120850152610140830151848203610140860152612bda82826139f7565b604081525f613b3b6040830185613a45565b8281036020840152612bda8185613562565b5f60208284031215613b5d575f5ffd5b81516103a88161302a565b5f613b75612f0084612f35565b83815290506020810160608402830185811115613b90575f5ffd5b835b81811015613bee575f60608289031215613baa575f5ffd5b613bb2612df8565b90508151613bbf81612daa565b8152602082810151908201526040820151613bd981612daa565b60408201528352602090920191606001613b92565b5050509392505050565b5f82601f830112613c07575f5ffd5b6103a883835160208501613b68565b5f60208284031215613c26575f5ffd5b815167ffffffffffffffff811115613c3c575f5ffd5b820160808185031215613c4d575f5ffd5b613c55612e68565b8151815260208083015190820152604082015167ffffffffffffffff811115613c7c575f5ffd5b8201601f81018613613c8c575f5ffd5b8051613c9a612f0082612f35565b8082825260208201915060208360061b850101925088831115613cbb575f5ffd5b6020840193505b82841015613d0b576040848a031215613cd9575f5ffd5b613ce1612e21565b8451613cec81612daa565b8152602085810151818301529083526040909401939190910190613cc2565b6040850152505050606082015167ffffffffffffffff811115613d2c575f5ffd5b613d3886828501613bf8565b606083015250949350505050565b5f82601f830112613d55575f5ffd5b8151613d63612f0082612ebc565b818152846020838601011115613d77575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613da3575f5ffd5b815167ffffffffffffffff811115613db9575f5ffd5b820160608185031215613dca575f5ffd5b613dd2612df8565b8151613ddd81612daa565b8152602082015167ffffffffffffffff811115613df8575f5ffd5b613e0486828501613d46565b602083015250604082015167ffffffffffffffff811115613e23575f5ffd5b80830192505084601f830112613e37575f5ffd5b8151613e45612f0082612f35565b8082825260208201915060208360051b860101925087831115613e66575f5ffd5b6020850194505b82851015613e88578451825260209485019490910190613e6d565b6040840152509095945050505050565b805182525f602082015160606020850152613eb66060850182613562565b9050604083015184820360408601528181518084526020840191506020830193505f92505b808310156130cc5783518252602082019150602084019350600183019250613edb565b604081525f613f106040830185613a45565b8281036020840152612bda8185613e98565b602081525f6103a86020830184613590565b5f60208284031215613f44575f5ffd5b815167ffffffffffffffff811115613f5a575f5ffd5b820160408185031215613f6b575f5ffd5b613f73612e21565b81518152602082015167ffffffffffffffff811115613f90575f5ffd5b80830192505084601f830112613fa4575f5ffd5b613fb385835160208501613b68565b6020820152949350505050565b5f60208284031215613fd0575f5ffd5b815167ffffffffffffffff811115613fe6575f5ffd5b820160408185031215613ff7575f5ffd5b613fff612e21565b81518152602082015167ffffffffffffffff81111561401c575f5ffd5b80830192505084601f830112614030575f5ffd5b815161403e612f0082612f35565b8082825260208201915060208360051b86010192508783111561405f575f5ffd5b602085015b838110156140f957805167ffffffffffffffff811115614082575f5ffd5b86016060818b03601f19011215614097575f5ffd5b61409f612df8565b60208201516140ad81612daa565b8152604082015167ffffffffffffffff8111156140c8575f5ffd5b6140d78c602083860101613d46565b6020838101919091526060939093015160408301525084529283019201614064565b506020840152509095945050505050565b602081525f6103a860208301846139f7565b5f6020828403121561412c575f5ffd5b5051919050565b608081525f6141456080830187613a45565b82810360208401526141578187613e98565b9050828103604084015261416b8186613562565b9050828103606084015261417f8185613562565b979650505050505050565b6001600160a01b03851681526001600160a01b0384166020820152608060408201525f6141ba6080830185613562565b905082606083015295945050505050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220fae3247dd7a33c3cd300206de44d56570a43a7690ab25372a739d3c2704b371e64736f6c634300081c00336080604052348015600e575f5ffd5b506109a98061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80635e07a6e614610043578063f18d03cc1461006b578063ff883fcf14610080575b5f5ffd5b610056610051366004610636565b6100a0565b60405190151581526020015b60405180910390f35b61007e61007936600461064f565b6100ec565b005b61009361008e366004610703565b6101cb565b60405161006291906107d3565b5f816001600160a01b03163b5f036100b957505f919050565b6100c2826102b3565b156100cf57506001919050565b6100d882610364565b156100e557506001919050565b505f919050565b6100f5846102b3565b1561017d576040517fbeabacc80000000000000000000000000000000000000000000000000000000081526001600160a01b03848116600483015283811660248301526044820183905285169063beabacc8906064015f604051808303815f87803b158015610162575f5ffd5b505af1158015610174573d5f5f3e3d5ffd5b505050506101c5565b61018684610364565b1561019c57610197848484846103d3565b6101c5565b604051636b94637360e11b81526001600160a01b03851660048201526024015b60405180910390fd5b50505050565b60606101d6856102b3565b1561026d576040517f4ae000410000000000000000000000000000000000000000000000000000000081526001600160a01b03861690634ae0004190610224908790879087906004016107e5565b5f604051808303815f875af115801561023f573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526102669190810190610863565b90506102ab565b61027685610364565b1561028757610266858585856104a2565b604051636b94637360e11b81526001600160a01b03861660048201526024016101bc565b949350505050565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527ff44bac890000000000000000000000000000000000000000000000000000000060048201525f906001600160a01b038316906301ffc9a790602401602060405180830381865afa92505050801561034e575060408051601f3d908101601f1916820190925261034b918101906108a4565b60015b61035957505f919050565b92915050565b919050565b5f816001600160a01b031663e75235b86040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156103bf575060408051601f3d908101601f191682019092526103bc918101906108bd565b60015b6103ca57505f919050565b50600192915050565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b0384161461047f57604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261047a90859085905f6104a2565b61049b565b604080515f81526020810190915261049b9085908490846104a2565b5050505050565b60605f5f866001600160a01b0316635229073f8786885f6040518563ffffffff1660e01b81526004016104d894939291906108d4565b5f604051808303815f875af11580156104f3573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261051a9190810190610928565b9150915084515f146105365761053186838361054b565b610540565b61054082826105c3565b979650505050505050565b6060826105605761055b826105de565b6105bc565b815115801561057757506001600160a01b0384163b155b156105b9576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016101bc565b50805b9392505050565b6060826105d8576105d3826105de565b610359565b50919050565b8051156105ee5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461035f575f5ffd5b5f60208284031215610646575f5ffd5b6105bc82610620565b5f5f5f5f60808587031215610662575f5ffd5b61066b85610620565b935061067960208601610620565b925061068760408601610620565b9396929550929360600135925050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff811182821017156106d4576106d4610697565b604052919050565b5f67ffffffffffffffff8211156106f5576106f5610697565b50601f01601f191660200190565b5f5f5f5f60808587031215610716575f5ffd5b61071f85610620565b935061072d60208601610620565b9250604085013567ffffffffffffffff811115610748575f5ffd5b8501601f81018713610758575f5ffd5b803561076b610766826106dc565b6106ab565b81815288602083850101111561077f575f5ffd5b816020840160208301375f91810160200191909152949793965093946060013593505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6105bc60208301846107a5565b6001600160a01b0384168152606060208201525f61080660608301856107a5565b9050826040830152949350505050565b5f82601f830112610825575f5ffd5b8151610833610766826106dc565b818152846020838601011115610847575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215610873575f5ffd5b815167ffffffffffffffff811115610889575f5ffd5b6102ab84828501610816565b8051801515811461035f575f5ffd5b5f602082840312156108b4575f5ffd5b6105bc82610895565b5f602082840312156108cd575f5ffd5b5051919050565b6001600160a01b0385168152836020820152608060408201525f6108fb60808301856107a5565b90506002831061091957634e487b7160e01b5f52602160045260245ffd5b82606083015295945050505050565b5f5f60408385031215610939575f5ffd5b61094283610895565b9150602083015167ffffffffffffffff81111561095d575f5ffd5b61096985828601610816565b915050925092905056fea264697066735822122063c7f17d2676ae9340ccf0078bc40725749c3c4dd9198f82832bc411d2d1333764736f6c634300081c0033", - "deployedBytecode": "0x60806040526004361061010c575f3560e01c80638da5cb5b116100a1578063cbc9104511610071578063f2fde38b11610057578063f2fde38b14610314578063f77c479114610333578063fee2b7a714610366575f5ffd5b8063cbc91045146102d6578063f0801868146102f5575f5ffd5b80638da5cb5b14610239578063911929df14610255578063c422661f14610281578063c4bb6af5146102a0575f5ffd5b8063715018a6116100dc578063715018a6146101c057806377c8fb6b146101d45780637f411c79146101f357806384b0196e14610212575f5ffd5b80630fc27cf6146101175780632e5a2458146101385780634987ed2f1461016a5780636ccae054146101a1575f5ffd5b3661011357005b5f5ffd5b348015610122575f5ffd5b50610136610131366004612dc9565b610385565b005b348015610143575f5ffd5b50610157610152366004613366565b610399565b6040519081526020015b60405180910390f35b348015610175575f5ffd5b50600454610189906001600160a01b031681565b6040516001600160a01b039091168152602001610161565b3480156101ac575f5ffd5b506101366101bb3660046133de565b6103af565b3480156101cb575f5ffd5b50610136610465565b3480156101df575f5ffd5b50600554610189906001600160a01b031681565b3480156101fe575f5ffd5b5061013661020d36600461341c565b610478565b34801561021d575f5ffd5b50610226610587565b60405161016197969594939291906135ca565b348015610244575f5ffd5b505f546001600160a01b0316610189565b348015610260575f5ffd5b5061027461026f366004612dc9565b6105e5565b6040516101619190613653565b34801561028c575f5ffd5b5061013661029b366004612dc9565b61068e565b3480156102ab575f5ffd5b506101576102ba366004613665565b600660209081525f928352604080842090915290825290205481565b3480156102e1575f5ffd5b506101366102f036600461368f565b61069f565b348015610300575f5ffd5b5061013661030f36600461341c565b6106a9565b34801561031f575f5ffd5b5061013661032e366004612dc9565b61076e565b34801561033e575f5ffd5b506101897f000000000000000000000000000000000000000000000000000000000000000081565b348015610371575f5ffd5b506101576103803660046136c9565b6107c1565b61038d6107d1565b61039681610816565b50565b5f6103a58484846108ac565b90505b9392505050565b6103b76107d1565b6103bf610948565b6001600160a01b0382166103ff576040517f54328a0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61040a83838361098b565b816001600160a01b0316836001600160a01b03167fed2837b80b3489773c5f1ed30dd2884b4c90dbf7b87428ea03c49b24ef59a8058360405161044f91815260200190565b60405180910390a361046060018055565b505050565b61046d6107d1565b6104765f6109c4565b565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa1580156104e3573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050791906136fb565b6105345760405163a7ac57a960e01b81526001600160a01b03821660048201526024015b60405180910390fd5b5f5a9050610543836001610a20565b5f5a61054f908361371a565b9050806040517fa523ba4900000000000000000000000000000000000000000000000000000000815260040161052b91815260200190565b5f6060805f5f5f6060610598610b9d565b6105a0610bcf565b604080515f808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009b939a50919850469750309650945092509050565b6001600160a01b0381165f90815260076020526040902080546060919061060b90613739565b80601f016020809104026020016040519081016040528092919081815260200182805461063790613739565b80156106825780601f1061065957610100808354040283529160200191610682565b820191905f5260205f20905b81548152906001019060200180831161066557829003601f168201915b50505050509050919050565b6106966107d1565b61039681610bfc565b6103963382610c52565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa158015610714573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061073891906136fb565b6107605760405163a7ac57a960e01b81526001600160a01b038216600482015260240161052b565b61076a825f610a20565b5050565b6107766107d1565b6001600160a01b0381166107b8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f600482015260240161052b565b610396816109c4565b5f6107cb82610ccb565b92915050565b5f546001600160a01b03163314610476576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052b565b6001600160a01b038116610856576040517f42af048a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f93fa00498c991ad852fbb3361afb0e3507720aa9ff69a8386009b76134a6e654905f90a250565b5f7fabd98b1a5498f7ce6ce5fd671f1a1c2ad437b155c4c12281f925612823c72e886108d784610ccb565b83865f01518760200151805190602001206108f58960400151610d6e565b6040805160208101979097528601949094526001600160a01b039092166060850152608084015260a083015260c082015260e0016040516020818303038152906040528051906020012090509392505050565b600260015403610984576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036109b9576104608282610d80565b610460838383610e2b565b5f80546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610a28610948565b5f5b8251811015610b93575f838281518110610a4657610a4661376b565b60200260200101515f015190505f848381518110610a6657610a6661376b565b60200260200101516020015190505f858481518110610a8757610a8761376b565b6020026020010151604001519050610aa183838388610e9f565b6020838101516001600160a01b03165f9081526006825260408082206060870151835290925220439055825160ff16610ae357610ade838361164c565b610b4a565b825160ff165f1901610af957610ade83836119d0565b825160ff1660011901610b1057610ade8383611a90565b82516040517f7cdbcbf100000000000000000000000000000000000000000000000000000000815260ff909116600482015260240161052b565b610b558284336108ac565b6040518581527f120985525cfb78f6c03f96986f9687f49caf81e29350d21fa052e8c0df211ebb9060200160405180910390a2505050600101610a2a565b5061076a60018055565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006002611bb6565b905090565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006003611bb6565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f493e783f4ed464086f434b9d42b1b8fb97375d1fdc91692c73551281526d22be905f90a250565b6001600160a01b0382165f908152600760205260408120610c7291612d60565b6001600160a01b0382165f908152600760205260409020610c9382826137d7565b506040516001600160a01b038316907f5ac98b81e10e07689d7de8e9ec09900564d65647de8d039328d4cbdf575b28f4905f90a25050565b5f7fbd51147cb801db5aa8dc80920aabcbe45ea95c1767e31bc3339f62d8f0d046f9825f015183602001518460400151856060015186608001518760a0015180519060200120610d1e8960c00151611c60565b610d2b8a60e00151611da6565b8a61010001518b6101200151604051602001610d519b9a99989796959493929190613892565b604051602081830303815290604052805190602001209050919050565b5f81604051602001610d51919061390c565b80471015610dc3576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810182905260440161052b565b5f5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610e0d576040519150601f19603f3d011682016040523d82523d5f602084013e610e12565b606091505b509150915081610e2557610e2581611eba565b50505050565b6040516001600160a01b0383811660248301526044820183905261046091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611efc565b60408401516001600160a01b03163014610ef65760408085015190517f30b078bf0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6060840151610f31576040517f58e8d18100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808501516001600160a01b03165f90815260068252604080822060608801518352909252205415610fab57602084015160608501516040517f9756f94d0000000000000000000000000000000000000000000000000000000081526001600160a01b039092166004830152602482015260440161052b565b6005546001600160a01b0316156110e4576020808501516001600160a01b03165f9081526007909152604081208054610fe390613739565b80601f016020809104026020016040519081016040528092919081815260200182805461100f90613739565b801561105a5780601f106110315761010080835404028352916020019161105a565b820191905f5260205f20905b81548152906001019060200180831161103d57829003601f168201915b505050505090505f815111156110e2576005546040517fdbd902180000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063dbd90218906110b59088908590600401613b29565b5f6040518083038186803b1580156110cb575f5ffd5b505afa1580156110dd573d5f5f3e3d5ffd5b505050505b505b5f6110ee85611f81565b9050801561118d57428560800151116111425760808501516040517f95d3dc22000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b8351421080159061118b5784516040517f2c39717b000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b505b8360400151518560c0015151146111d0576040517f9dca144800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8560c0015151811015611273575f8660c0015182815181106111f6576111f661376b565b60200260200101516020015190505f8660400151838151811061121b5761121b61376b565b6020026020010151905081811115611269576040517f8ffa61fa000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161052b565b50506001016111d2565b505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663680038c76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112d1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190613b4d565b90505f8160ff1687610120015111611310578160ff16611317565b8661012001515b90508087610140015151101561136b57610140870151516040517fcd0ce69900000000000000000000000000000000000000000000000000000000815261052b918391600401918252602082015260400190565b5f5f90505f60405180602001604052806113848b610ccb565b905290505f61139a61139583611fd0565b61200d565b90505f5b8a61014001515181101561152f575f6113d5838d610140015184815181106113c8576113c861376b565b6020026020010151612054565b9050846001600160a01b0316816001600160a01b031611611435576040517fce98854c0000000000000000000000000000000000000000000000000000000081526001600160a01b0380871660048301528216602482015260440161052b565b6040517f616556700000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015291955085915f917f000000000000000000000000000000000000000000000000000000000000000090911690636165567090602401602060405180830381865afa1580156114bb573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114df91906136fb565b1590508015611525576040517ff24706660000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505060010161139e565b505f6115486115426113958c8e336108ac565b8a612054565b6040517f163eed9e0000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301529192505f917f0000000000000000000000000000000000000000000000000000000000000000169063163eed9e90602401602060405180830381865afa1580156115ca573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ee91906136fb565b1580156115f9575088155b9050801561163e576040517f100158a80000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505050505050505050505050565b5f8260a001518060200190518101906116659190613c16565b90505f82602001518060200190518101906116809190613d93565b905061168c828261207c565b60208401516004545f916116a9916001600160a01b0316906122ed565b905046835f01510361170b575f5b836040015151811015611709575f846040015182815181106116db576116db61376b565b60200260200101519050611700815f01518860200151865f0151846020015187612371565b506001016116b7565b505b5f611715846123b1565b83516040517f7b4da5de0000000000000000000000000000000000000000000000000000000081529192506001600160a01b031690637b4da5de906117609089908990600401613efe565b5f604051808303815f87803b158015611777575f5ffd5b505af1158015611789573d5f5f3e3d5ffd5b50505050468460200151036119c8575f84606001515167ffffffffffffffff8111156117b7576117b7612de4565b6040519080825280602002602001820160405280156117e0578160200160208202803683370190505b5090505f5b856060015151811015611990575f866060015182815181106118095761180961376b565b602002602001015190505f611821825f01513061246d565b90505f8584815181106118365761183661376b565b602002602001015190508082101561188b576040517ff5ad5ab700000000000000000000000000000000000000000000000000000000815260048101859052602481018390526044810182905260640161052b565b611895818361371a565b8585815181106118a7576118a761376b565b6020026020010181815250505f886040015185815181106118ca576118ca61376b565b60200260200101519050808686815181106118e7576118e761376b565b6020026020010151101561195557848686815181106119085761190861376b565b60209081029190910101516040517ff0a43aa7000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044810182905260640161052b565b611980845f015185604001518888815181106119735761197361376b565b602002602001015161098b565b5050600190920191506117e59050565b506119bb8787836040516020016119a79190613f22565b604051602081830303815290604052612524565b6119c68787856125b7565b505b505050505050565b5f8260a001518060200190518101906119e99190613f34565b90506119f5818361263b565b60208301516004545f91611a12916001600160a01b0316906122ed565b90505f5b826020015151811015611a69575f83602001518281518110611a3a57611a3a61376b565b60200260200101519050611a60815f015187602001518360400151846020015187612371565b50600101611a16565b50604080515f815260208101909152611a859085908590612524565b610e258484836125b7565b5f8260a00151806020019051810190611aa99190613fc0565b9050611aba818385602001516126fc565b5f81602001515167ffffffffffffffff811115611ad957611ad9612de4565b604051908082528060200260200182016040528015611b0c57816020015b6060815260200190600190039081611af75790505b5090505f5b826020015151811015611b93575f83602001518281518110611b3557611b3561376b565b602090810291909101810151878201518151928201516040830151600454939550611b6d946001600160a01b0390941693919061279a565b838381518110611b7f57611b7f61376b565b602090810291909101015250600101611b11565b50611baa8484836040516020016119a7919061410a565b610e25848460016125b7565b606060ff8314611bd057611bc98361282d565b90506107cb565b818054611bdc90613739565b80601f0160208091040260200160405190810160405280929190818152602001828054611c0890613739565b8015611c535780601f10611c2a57610100808354040283529160200191611c53565b820191905f5260205f20905b815481529060010190602001808311611c3657829003601f168201915b5050505050905092915050565b5f5f825167ffffffffffffffff811115611c7c57611c7c612de4565b604051908082528060200260200182016040528015611ca5578160200160208202803683370190505b5090505f5b8351811015611d76577fe44101eb81f50b64d4005e31312ea7bafd6f81357f9dce4fe51d5f2dcb939da4848281518110611ce657611ce661376b565b60200260200101515f0151858381518110611d0357611d0361376b565b602002602001015160200151604051602001611d3b939291909283526001600160a01b03919091166020830152604082015260600190565b60405160208183030381529060405280519060200120828281518110611d6357611d6361376b565b6020908102919091010152600101611caa565b5080604051602001611d88919061390c565b60405160208183030381529060405280519060200120915050919050565b5f5f825167ffffffffffffffff811115611dc257611dc2612de4565b604051908082528060200260200182016040528015611deb578160200160208202803683370190505b5090505f5b8351811015611d76577ff7a396f96f3810db1cfde06de38d32fc29d56b63cf9d9aee2c10c91ef7273bb1848281518110611e2c57611e2c61376b565b60200260200101515f0151858381518110611e4957611e4961376b565b60200260200101516020015180519060200120604051602001611e7f939291909283526020830191909152604082015260600190565b60405160208183030381529060405280519060200120828281518110611ea757611ea761376b565b6020908102919091010152600101611df0565b805115611eca5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f60205f8451602086015f885af180611f1b576040513d5f823e3d81fd5b50505f513d91508115611f32578060011415611f3f565b6001600160a01b0384163b155b15610e25576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b80515f9060ff1615611f9557506001919050565b5f8260a00151806020019051810190611fae9190613c16565b90508060200151815f015103611fc75750600192915050565b51461492915050565b8051604080517f07094367b2db06e3dbb414cf947644f8e08067ea238c5f41cb2fa5fea5be628e6020820152908101919091525f90606001610d51565b5f6107cb61201961286a565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b5f5f5f5f6120628686612993565b92509250925061207282826129dc565b5090949350505050565b81515f904614801590612093575046836020015114155b905080156120b65760405163308a43dd60e11b815246600482015260240161052b565b826060015151826040015151146120f9576040517f1816c20200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8360600151518110156121f2575f8460600151828151811061211f5761211f61376b565b602002602001015190505f81604001519050306001600160a01b0316816001600160a01b03160361216e57604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b5f826020015190505f8660400151858151811061218d5761218d61376b565b60200260200101519050818110156121e2576040517f1fef5f3300000000000000000000000000000000000000000000000000000000815260048101869052602481018290526044810183905260640161052b565b5050600190920191506120fb9050565b5060208301518351146104605781516040517f7d7011020000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201525f917f00000000000000000000000000000000000000000000000000000000000000001690637d70110290602401602060405180830381865afa158015612280573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122a491906136fb565b1590508015610e255782516040517fdb489af40000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6040517f5e07a6e60000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301525f9190841690635e07a6e690602401602060405180830381865afa15801561234d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a891906136fb565b801561239557600454612390906001600160a01b031685878686612adf565b6123aa565b6123aa6001600160a01b038616858585612b38565b5050505050565b606081606001515167ffffffffffffffff8111156123d1576123d1612de4565b6040519080825280602002602001820160405280156123fa578160200160208202803683370190505b50905046826020015103612468575f5b82606001515181101561246657612441836060015182815181106124305761243061376b565b60200260200101515f01513061246d565b8282815181106124535761245361376b565b602090810291909101015260010161240a565b505b919050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036124a357506001600160a01b038116316107cb565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015612500573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc9919061411c565b5f5b8360e0015151811015610e25575f8460e00151828151811061254a5761254a61376b565b60200260200101519050845f015160ff16815f015186602001516001600160a01b03167fee2c98c99b683f71058b8744fea294a411482f07d55c98b392917e9286f22f1388888887602001516040516125a69493929190614133565b60405180910390a450600101612526565b6020830151335f5b8560c00151518110156119c8575f8660c0015182815181106125e3576125e361376b565b60200260200101515f01519050612604816001600160a01b03166103481490565b61263257612632818585896040015186815181106126245761262461376b565b602002602001015189612371565b506001016125bf565b8151461461265e5760405163308a43dd60e11b815246600482015260240161052b565b60208101515115612682576040516393c58e1160e01b815260040160405180910390fd5b5f5b826020015151811015610460575f836020015182815181106126a8576126a861376b565b6020026020010151604001519050306001600160a01b0316816001600160a01b0316036126f357604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b50600101612684565b8251461461271f5760405163308a43dd60e11b815246600482015260240161052b565b60208201515115612743576040516393c58e1160e01b815260040160405180910390fd5b600454612759906001600160a01b0316826122ed565b610460576040517fcaec9bac0000000000000000000000000000000000000000000000000000000081526001600160a01b038216600482015260240161052b565b60606128238663ff883fcf60e01b878787876040516024016127bf949392919061418a565b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612b71565b9695505050505050565b60605f61283983612be3565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156128c257507f000000000000000000000000000000000000000000000000000000000000000046145b156128ec57507f000000000000000000000000000000000000000000000000000000000000000090565b610bca604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f5f83516041036129ca576020840151604085015160608601515f1a6129bc88828585612c23565b9550955095505050506129d5565b505081515f91506002905b9250925092565b5f8260038111156129ef576129ef61377f565b036129f8575050565b6001826003811115612a0c57612a0c61377f565b03612a43576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826003811115612a5757612a5761377f565b03612a91576040517ffce698f70000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6003826003811115612aa557612aa561377f565b0361076a576040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6040516001600160a01b038086166024830152808516604483015283166064820152608481018290526119c89086907ff18d03cc000000000000000000000000000000000000000000000000000000009060a4016127bf565b6040516001600160a01b038481166024830152838116604483015260648201839052610e259186918216906323b872dd90608401610e58565b60605f5f846001600160a01b031684604051612b8d91906141cb565b5f60405180830381855af49150503d805f8114612bc5576040519150601f19603f3d011682016040523d82523d5f602084013e612bca565b606091505b5091509150612bda858383612ceb565b95945050505050565b5f60ff8216601f8111156107cb576040517fb3512b0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115612c5c57505f91506003905082612ce1565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015612cad573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116612cd857505f925060019150829050612ce1565b92505f91508190505b9450945094915050565b606082612d0057612cfb82611eba565b6103a8565b8151158015612d1757506001600160a01b0384163b155b15612d59576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b50806103a8565b508054612d6c90613739565b5f825580601f10612d7b575050565b601f0160209004905f5260205f209081019061039691905b80821115612da6575f8155600101612d93565b5090565b6001600160a01b0381168114610396575f5ffd5b803561246881612daa565b5f60208284031215612dd9575f5ffd5b81356103a881612daa565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b60405290565b6040805190810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051610160810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b6040516080810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612eb457612eb4612de4565b604052919050565b5f67ffffffffffffffff821115612ed557612ed5612de4565b50601f01601f191660200190565b5f82601f830112612ef2575f5ffd5b8135612f05612f0082612ebc565b612e8b565b818152846020838601011115612f19575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f67ffffffffffffffff821115612f4e57612f4e612de4565b5060051b60200190565b5f60608284031215612f68575f5ffd5b612f70612df8565b823581529050602082013567ffffffffffffffff811115612f8f575f5ffd5b612f9b84828501612ee3565b602083015250604082013567ffffffffffffffff811115612fba575f5ffd5b8201601f81018413612fca575f5ffd5b8035612fd8612f0082612f35565b8082825260208201915060208360051b850101925086831115612ff9575f5ffd5b6020840193505b8284101561301b578335825260209384019390910190613000565b60408501525091949350505050565b60ff81168114610396575f5ffd5b80356124688161302a565b5f82601f830112613052575f5ffd5b8135613060612f0082612f35565b8082825260208201915060208360061b860101925085831115613081575f5ffd5b602085015b838110156130cc576040818803121561309d575f5ffd5b6130a5612e21565b81356130b081612daa565b8152602082810135818301529084529290920191604001613086565b5095945050505050565b5f82601f8301126130e5575f5ffd5b81356130f3612f0082612f35565b8082825260208201915060208360051b860101925085831115613114575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613137575f5ffd5b86016040818903601f1901121561314c575f5ffd5b613154612e21565b60208201358152604082013567ffffffffffffffff811115613174575f5ffd5b6131838a602083860101612ee3565b6020830152508085525050602083019250602081019050613119565b5f82601f8301126131ae575f5ffd5b81356131bc612f0082612f35565b8082825260208201915060208360051b8601019250858311156131dd575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613200575f5ffd5b61320f886020838a0101612ee3565b845250602092830192016131e2565b5f610160828403121561322f575f5ffd5b613237612e44565b905061324282613038565b815261325060208301612dbe565b602082015261326160408301612dbe565b6040820152606082810135908201526080808301359082015260a082013567ffffffffffffffff811115613293575f5ffd5b61329f84828501612ee3565b60a08301525060c082013567ffffffffffffffff8111156132be575f5ffd5b6132ca84828501613043565b60c08301525060e082013567ffffffffffffffff8111156132e9575f5ffd5b6132f5848285016130d6565b60e08301525061010082013567ffffffffffffffff811115613315575f5ffd5b61332184828501612ee3565b61010083015250610120828101359082015261014082013567ffffffffffffffff81111561334d575f5ffd5b6133598482850161319f565b6101408301525092915050565b5f5f5f60608486031215613378575f5ffd5b833567ffffffffffffffff81111561338e575f5ffd5b61339a86828701612f58565b935050602084013567ffffffffffffffff8111156133b6575f5ffd5b6133c28682870161321e565b92505060408401356133d381612daa565b809150509250925092565b5f5f5f606084860312156133f0575f5ffd5b83356133fb81612daa565b9250602084013561340b81612daa565b929592945050506040919091013590565b5f6020828403121561342c575f5ffd5b813567ffffffffffffffff811115613442575f5ffd5b8201601f81018413613452575f5ffd5b8035613460612f0082612f35565b8082825260208201915060208360051b850101925086831115613481575f5ffd5b602084015b8381101561355757803567ffffffffffffffff8111156134a4575f5ffd5b85016060818a03601f190112156134b9575f5ffd5b6134c1612df8565b602082013567ffffffffffffffff8111156134da575f5ffd5b6134e98b60208386010161321e565b825250604082013567ffffffffffffffff811115613505575f5ffd5b6135148b602083860101612f58565b602083015250606082013567ffffffffffffffff811115613533575f5ffd5b6135428b602083860101612ee3565b60408301525084525060209283019201613486565b509695505050505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f8151808452602084019350602083015f5b828110156135c05781518652602095860195909101906001016135a2565b5093949350505050565b7fff000000000000000000000000000000000000000000000000000000000000008816815260e060208201525f61360460e0830189613562565b82810360408401526136168189613562565b90508660608401526001600160a01b03861660808401528460a084015282810360c08401526136458185613590565b9a9950505050505050505050565b602081525f6103a86020830184613562565b5f5f60408385031215613676575f5ffd5b823561368181612daa565b946020939093013593505050565b5f6020828403121561369f575f5ffd5b813567ffffffffffffffff8111156136b5575f5ffd5b6136c184828501612ee3565b949350505050565b5f602082840312156136d9575f5ffd5b813567ffffffffffffffff8111156136ef575f5ffd5b6136c18482850161321e565b5f6020828403121561370b575f5ffd5b815180151581146103a8575f5ffd5b818103818111156107cb57634e487b7160e01b5f52601160045260245ffd5b600181811c9082168061374d57607f821691505b60208210810361246657634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b601f82111561046057805f5260205f20601f840160051c810160208510156137b85750805b601f840160051c820191505b818110156123aa575f81556001016137c4565b815167ffffffffffffffff8111156137f1576137f1612de4565b613805816137ff8454613739565b84613793565b6020601f821160018114613837575f83156138205750848201515b5f19600385901b1c1916600184901b1784556123aa565b5f84815260208120601f198516915b828110156138665787850151825560209485019460019092019101613846565b508482101561388357868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8b815260ff8b1660208201526001600160a01b038a1660408201526001600160a01b03891660608201528760808201528660a08201528560c08201528460e0820152836101008201526101606101208201525f6138f3610160830185613562565b9050826101408301529c9b505050505050505050505050565b81515f90829060208501835b82811015613936578151845260209384019390910190600101613918565b509195945050505050565b5f8151808452602084019350602083015f5b828110156135c057815180516001600160a01b031687526020908101518188015260409096019590910190600101613953565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f1985840301885281518051845260208101519050604060208501526139d46040850182613562565b6020998a01999094509290920191506001016139a2565b50909695505050505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f19858403018852613a2f838351613562565b6020988901989093509190910190600101613a13565b805160ff1682525f6020820151613a6760208501826001600160a01b03169052565b506040820151613a8260408501826001600160a01b03169052565b50606082015160608401526080820151608084015260a082015161016060a0850152613ab2610160850182613562565b905060c083015184820360c0860152613acb8282613941565b91505060e083015184820360e0860152613ae58282613986565b915050610100830151848203610100860152613b018282613562565b915050610120830151610120850152610140830151848203610140860152612bda82826139f7565b604081525f613b3b6040830185613a45565b8281036020840152612bda8185613562565b5f60208284031215613b5d575f5ffd5b81516103a88161302a565b5f613b75612f0084612f35565b83815290506020810160608402830185811115613b90575f5ffd5b835b81811015613bee575f60608289031215613baa575f5ffd5b613bb2612df8565b90508151613bbf81612daa565b8152602082810151908201526040820151613bd981612daa565b60408201528352602090920191606001613b92565b5050509392505050565b5f82601f830112613c07575f5ffd5b6103a883835160208501613b68565b5f60208284031215613c26575f5ffd5b815167ffffffffffffffff811115613c3c575f5ffd5b820160808185031215613c4d575f5ffd5b613c55612e68565b8151815260208083015190820152604082015167ffffffffffffffff811115613c7c575f5ffd5b8201601f81018613613c8c575f5ffd5b8051613c9a612f0082612f35565b8082825260208201915060208360061b850101925088831115613cbb575f5ffd5b6020840193505b82841015613d0b576040848a031215613cd9575f5ffd5b613ce1612e21565b8451613cec81612daa565b8152602085810151818301529083526040909401939190910190613cc2565b6040850152505050606082015167ffffffffffffffff811115613d2c575f5ffd5b613d3886828501613bf8565b606083015250949350505050565b5f82601f830112613d55575f5ffd5b8151613d63612f0082612ebc565b818152846020838601011115613d77575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613da3575f5ffd5b815167ffffffffffffffff811115613db9575f5ffd5b820160608185031215613dca575f5ffd5b613dd2612df8565b8151613ddd81612daa565b8152602082015167ffffffffffffffff811115613df8575f5ffd5b613e0486828501613d46565b602083015250604082015167ffffffffffffffff811115613e23575f5ffd5b80830192505084601f830112613e37575f5ffd5b8151613e45612f0082612f35565b8082825260208201915060208360051b860101925087831115613e66575f5ffd5b6020850194505b82851015613e88578451825260209485019490910190613e6d565b6040840152509095945050505050565b805182525f602082015160606020850152613eb66060850182613562565b9050604083015184820360408601528181518084526020840191506020830193505f92505b808310156130cc5783518252602082019150602084019350600183019250613edb565b604081525f613f106040830185613a45565b8281036020840152612bda8185613e98565b602081525f6103a86020830184613590565b5f60208284031215613f44575f5ffd5b815167ffffffffffffffff811115613f5a575f5ffd5b820160408185031215613f6b575f5ffd5b613f73612e21565b81518152602082015167ffffffffffffffff811115613f90575f5ffd5b80830192505084601f830112613fa4575f5ffd5b613fb385835160208501613b68565b6020820152949350505050565b5f60208284031215613fd0575f5ffd5b815167ffffffffffffffff811115613fe6575f5ffd5b820160408185031215613ff7575f5ffd5b613fff612e21565b81518152602082015167ffffffffffffffff81111561401c575f5ffd5b80830192505084601f830112614030575f5ffd5b815161403e612f0082612f35565b8082825260208201915060208360051b86010192508783111561405f575f5ffd5b602085015b838110156140f957805167ffffffffffffffff811115614082575f5ffd5b86016060818b03601f19011215614097575f5ffd5b61409f612df8565b60208201516140ad81612daa565b8152604082015167ffffffffffffffff8111156140c8575f5ffd5b6140d78c602083860101613d46565b6020838101919091526060939093015160408301525084529283019201614064565b506020840152509095945050505050565b602081525f6103a860208301846139f7565b5f6020828403121561412c575f5ffd5b5051919050565b608081525f6141456080830187613a45565b82810360208401526141578187613e98565b9050828103604084015261416b8186613562565b9050828103606084015261417f8185613562565b979650505050505050565b6001600160a01b03851681526001600160a01b0384166020820152608060408201525f6141ba6080830185613562565b905082606083015295945050505050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220fae3247dd7a33c3cd300206de44d56570a43a7690ab25372a739d3c2704b371e64736f6c634300081c0033", - "linkReferences": {}, - "deployedLinkReferences": {}, - "immutableReferences": { - "4041": [ - { - "length": 32, - "start": 10442 - } - ], - "4043": [ - { - "length": 32, - "start": 10400 - } - ], - "4045": [ - { - "length": 32, - "start": 10358 - } - ], - "4047": [ - { - "length": 32, - "start": 10523 - } - ], - "4049": [ - { - "length": 32, - "start": 10563 - } - ], - "4052": [ - { - "length": 32, - "start": 2980 - } - ], - "4055": [ - { - "length": 32, - "start": 3030 - } - ], - "8966": [ - { - "length": 32, - "start": 836 - }, - { - "length": 32, - "start": 1180 - }, - { - "length": 32, - "start": 1741 - }, - { - "length": 32, - "start": 4727 - }, - { - "length": 32, - "start": 5236 - }, - { - "length": 32, - "start": 5509 - }, - { - "length": 32, - "start": 8763 - } - ] - }, - "inputSourceName": "project/contracts/Settler.sol", - "buildInfoId": "27822028aefba6db9743fbac66c2677f7feca3ad" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json b/packages/evm/ignition/deployments/chain-1/build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json deleted file mode 100644 index df0f28b..0000000 --- a/packages/evm/ignition/deployments/chain-1/build-info/27822028aefba6db9743fbac66c2677f7feca3ad.json +++ /dev/null @@ -1,266 +0,0 @@ -{ - "_format": "hh3-sol-build-info-1", - "id": "27822028aefba6db9743fbac66c2677f7feca3ad", - "solcVersion": "0.8.28", - "solcLongVersion": "0.8.28+commit.7893614a", - "userSourceNameMap": { - "contracts/Controller.sol": "project/contracts/Controller.sol", - "contracts/Intents.sol": "project/contracts/Intents.sol", - "contracts/Settler.sol": "project/contracts/Settler.sol", - "contracts/interfaces/IController.sol": "project/contracts/interfaces/IController.sol", - "contracts/interfaces/ICreateX.sol": "project/contracts/interfaces/ICreateX.sol", - "contracts/interfaces/IExecutor.sol": "project/contracts/interfaces/IExecutor.sol", - "contracts/interfaces/IIntentsValidator.sol": "project/contracts/interfaces/IIntentsValidator.sol", - "contracts/interfaces/ISafe.sol": "project/contracts/interfaces/ISafe.sol", - "contracts/interfaces/ISettler.sol": "project/contracts/interfaces/ISettler.sol", - "contracts/interfaces/ISmartAccount.sol": "project/contracts/interfaces/ISmartAccount.sol", - "contracts/interfaces/ISmartAccountsHandler.sol": "project/contracts/interfaces/ISmartAccountsHandler.sol", - "contracts/safeguards/BaseIntentsValidator.sol": "project/contracts/safeguards/BaseIntentsValidator.sol", - "contracts/safeguards/CallIntentsValidator.sol": "project/contracts/safeguards/CallIntentsValidator.sol", - "contracts/safeguards/IntentsValidator.sol": "project/contracts/safeguards/IntentsValidator.sol", - "contracts/safeguards/Safeguards.sol": "project/contracts/safeguards/Safeguards.sol", - "contracts/safeguards/SwapIntentsValidator.sol": "project/contracts/safeguards/SwapIntentsValidator.sol", - "contracts/safeguards/TransferIntentsValidator.sol": "project/contracts/safeguards/TransferIntentsValidator.sol", - "contracts/smart-accounts/SmartAccount.sol": "project/contracts/smart-accounts/SmartAccount.sol", - "contracts/smart-accounts/SmartAccountsHandler.sol": "project/contracts/smart-accounts/SmartAccountsHandler.sol", - "contracts/smart-accounts/SmartAccountsHandlerHelpers.sol": "project/contracts/smart-accounts/SmartAccountsHandlerHelpers.sol", - "contracts/test/CallMock.sol": "project/contracts/test/CallMock.sol", - "contracts/test/TokenMock.sol": "project/contracts/test/TokenMock.sol", - "contracts/test/executors/EmptyExecutorMock.sol": "project/contracts/test/executors/EmptyExecutorMock.sol", - "contracts/test/executors/MintExecutorMock.sol": "project/contracts/test/executors/MintExecutorMock.sol", - "contracts/test/executors/ReentrantExecutorMock.sol": "project/contracts/test/executors/ReentrantExecutorMock.sol", - "contracts/test/executors/TransferExecutorMock.sol": "project/contracts/test/executors/TransferExecutorMock.sol", - "contracts/test/smart-accounts/SafeMock.sol": "project/contracts/test/smart-accounts/SafeMock.sol", - "contracts/test/utils/DenominationsMock.sol": "project/contracts/test/utils/DenominationsMock.sol", - "contracts/test/utils/ERC20HelpersMock.sol": "project/contracts/test/utils/ERC20HelpersMock.sol", - "contracts/utils/Denominations.sol": "project/contracts/utils/Denominations.sol", - "contracts/utils/ERC20Helpers.sol": "project/contracts/utils/ERC20Helpers.sol" - }, - "input": { - "language": "Solidity", - "settings": { - "optimizer": { - "enabled": true, - "runs": 1000 - }, - "evmVersion": "cancun", - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "remappings": [ - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/", - "project/:@openzeppelin/contracts/=npm/@openzeppelin/contracts@5.3.0/" - ] - }, - "sources": { - "npm/@openzeppelin/contracts@5.3.0/access/Ownable.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/interfaces/draft-IERC6093.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol)\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard ERC-20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\n */\ninterface IERC20Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC20InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC20InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC20InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC-721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\n */\ninterface IERC721Errors {\n /**\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.\n * Used in balance queries.\n * @param owner Address of the current owner of a token.\n */\n error ERC721InvalidOwner(address owner);\n\n /**\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\n * @param tokenId Identifier number of a token.\n */\n error ERC721NonexistentToken(uint256 tokenId);\n\n /**\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param tokenId Identifier number of a token.\n * @param owner Address of the current owner of a token.\n */\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC721InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC721InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param tokenId Identifier number of a token.\n */\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC721InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC-1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\n */\ninterface IERC1155Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n * @param tokenId Identifier number of a token.\n */\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC1155InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC1155InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param owner Address of the current owner of a token.\n */\n error ERC1155MissingApprovalForAll(address operator, address owner);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC1155InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC1155InvalidOperator(address operator);\n\n /**\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n * Used in batch transfers.\n * @param idsLength Length of the array of token identifiers\n * @param valuesLength Length of the array of token amounts\n */\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC1271.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC1271.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-1271 standard signature validation method for\n * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].\n */\ninterface IERC1271 {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param hash Hash of the data to be signed\n * @param signature Signature byte array associated with `hash`\n */\n function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC1363.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @title IERC1363\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\n *\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\n */\ninterface IERC1363 is IERC20, IERC165 {\n /*\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\n * 0xb0202a11 ===\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\n */\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @param data Additional data with no specified format, sent in call to `spender`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC165.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n" - }, - "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC20.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../token/ERC20/IERC20.sol\";\n" - }, - "npm/@openzeppelin/contracts@5.3.0/interfaces/IERC5267.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)\n\npragma solidity ^0.8.20;\n\ninterface IERC5267 {\n /**\n * @dev MAY be emitted to signal that the domain could have changed.\n */\n event EIP712DomainChanged();\n\n /**\n * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712\n * signature.\n */\n function eip712Domain()\n external\n view\n returns (\n bytes1 fields,\n string memory name,\n string memory version,\n uint256 chainId,\n address verifyingContract,\n bytes32 salt,\n uint256[] memory extensions\n );\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/token/ERC20/ERC20.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n mapping(address account => uint256) private _balances;\n\n mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * Both values are immutable: they can only be set once during construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Skips emitting an {Approval} event indicating an allowance update. This is not\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n _totalSupply += value;\n } else {\n uint256 fromBalance = _balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n _balances[from] = fromBalance - value;\n }\n }\n\n if (to == address(0)) {\n unchecked {\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n _totalSupply -= value;\n }\n } else {\n unchecked {\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n _balances[to] += value;\n }\n }\n\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n * Relies on the `_update` mechanism\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _mint(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(address(0), account, value);\n }\n\n /**\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n * Relies on the `_update` mechanism.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead\n */\n function _burn(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n _update(account, address(0), value);\n }\n\n /**\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n *\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n _approve(owner, spender, value, true);\n }\n\n /**\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n *\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any\n * `Approval` event during `transferFrom` operations.\n *\n * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to\n * true using the following override:\n *\n * ```solidity\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n * super._approve(owner, spender, value, true);\n * }\n * ```\n *\n * Requirements are the same as {_approve}.\n */\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n if (owner == address(0)) {\n revert ERC20InvalidApprover(address(0));\n }\n if (spender == address(0)) {\n revert ERC20InvalidSpender(address(0));\n }\n _allowances[owner][spender] = value;\n if (emitEvent) {\n emit Approval(owner, spender, value);\n }\n }\n\n /**\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\n *\n * Does not update the allowance value in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Does not emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance < type(uint256).max) {\n if (currentAllowance < value) {\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n }\n unchecked {\n _approve(owner, spender, currentAllowance - value, false);\n }\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/token/ERC20/extensions/IERC20Metadata.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/token/ERC20/IERC20.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/token/ERC20/utils/SafeERC20.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC1363} from \"../../../interfaces/IERC1363.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n /**\n * @dev An operation with an ERC-20 token failed.\n */\n error SafeERC20FailedOperation(address token);\n\n /**\n * @dev Indicates a failed `decreaseAllowance` request.\n */\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\n\n /**\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));\n }\n\n /**\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n */\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n }\n\n /**\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\n return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));\n }\n\n /**\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\n return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n }\n\n /**\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n uint256 oldAllowance = token.allowance(address(this), spender);\n forceApprove(token, spender, oldAllowance + value);\n }\n\n /**\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\n * value, non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\n unchecked {\n uint256 currentAllowance = token.allowance(address(this), spender);\n if (currentAllowance < requestedDecrease) {\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\n }\n forceApprove(token, spender, currentAllowance - requestedDecrease);\n }\n }\n\n /**\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n * to be set to zero before setting it to a non-zero value, such as USDT.\n *\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\n * only sets the \"standard\" allowance. Any temporary allowance will remain active, in addition to the value being\n * set here.\n */\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\n bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));\n\n if (!_callOptionalReturnBool(token, approvalCall)) {\n _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));\n _callOptionalReturn(token, approvalCall);\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n safeTransfer(token, to, value);\n } else if (!token.transferAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\n * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferFromAndCallRelaxed(\n IERC1363 token,\n address from,\n address to,\n uint256 value,\n bytes memory data\n ) internal {\n if (to.code.length == 0) {\n safeTransferFrom(token, from, to, value);\n } else if (!token.transferFromAndCall(from, to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\n * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\n * once without retrying, and relies on the returned value to be true.\n *\n * Reverts if the returned value is other than `true`.\n */\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n forceApprove(token, to, value);\n } else if (!token.approveAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n // bubble errors\n if iszero(success) {\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize())\n revert(ptr, returndatasize())\n }\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n\n if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.\n */\n function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {\n bool success;\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/Address.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)\n\npragma solidity ^0.8.20;\n\nimport {Errors} from \"./Errors.sol\";\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev There's no code at `target` (it is not a contract).\n */\n error AddressEmptyCode(address target);\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n if (address(this).balance < amount) {\n revert Errors.InsufficientBalance(address(this).balance, amount);\n }\n\n (bool success, bytes memory returndata) = recipient.call{value: amount}(\"\");\n if (!success) {\n _revert(returndata);\n }\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason or custom error, it is bubbled\n * up by this function (like regular Solidity function calls). However, if\n * the call reverted with no returned reason, this function reverts with a\n * {Errors.FailedCall} error.\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n if (address(this).balance < value) {\n revert Errors.InsufficientBalance(address(this).balance, value);\n }\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target\n * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case\n * of an unsuccessful call.\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata\n ) internal view returns (bytes memory) {\n if (!success) {\n _revert(returndata);\n } else {\n // only check if target is a contract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n if (returndata.length == 0 && target.code.length == 0) {\n revert AddressEmptyCode(target);\n }\n return returndata;\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the\n * revert reason or with a default {Errors.FailedCall} error.\n */\n function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {\n if (!success) {\n _revert(returndata);\n } else {\n return returndata;\n }\n }\n\n /**\n * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.\n */\n function _revert(bytes memory returndata) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n assembly (\"memory-safe\") {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert Errors.FailedCall();\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/Context.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/cryptography/ECDSA.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS\n }\n\n /**\n * @dev The signature derives the `address(0)`.\n */\n error ECDSAInvalidSignature();\n\n /**\n * @dev The signature has an invalid length.\n */\n error ECDSAInvalidSignatureLength(uint256 length);\n\n /**\n * @dev The signature has an S value that is in the upper half order.\n */\n error ECDSAInvalidSignatureS(bytes32 s);\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not\n * return address(0) without also returning an error description. Errors are documented using an enum (error type)\n * and a bytes32 providing additional information about the error.\n *\n * If no error is returned, then the address can be used for verification purposes.\n *\n * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n */\n function tryRecover(\n bytes32 hash,\n bytes memory signature\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly (\"memory-safe\") {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n unchecked {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n // We do not check for an overflow here since the shift operation results in 0 or 1.\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n */\n function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS, s);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature, bytes32(0));\n }\n\n return (signer, RecoverError.NoError, bytes32(0));\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.\n */\n function _throwError(RecoverError error, bytes32 errorArg) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert ECDSAInvalidSignature();\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert ECDSAInvalidSignatureLength(uint256(errorArg));\n } else if (error == RecoverError.InvalidSignatureS) {\n revert ECDSAInvalidSignatureS(errorArg);\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/cryptography/EIP712.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/EIP712.sol)\n\npragma solidity ^0.8.20;\n\nimport {MessageHashUtils} from \"./MessageHashUtils.sol\";\nimport {ShortStrings, ShortString} from \"../ShortStrings.sol\";\nimport {IERC5267} from \"../../interfaces/IERC5267.sol\";\n\n/**\n * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.\n *\n * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose\n * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract\n * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to\n * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.\n *\n * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding\n * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA\n * ({_hashTypedDataV4}).\n *\n * The implementation of the domain separator was designed to be as efficient as possible while still properly updating\n * the chain id to protect against replay attacks on an eventual fork of the chain.\n *\n * NOTE: This contract implements the version of the encoding known as \"v4\", as implemented by the JSON RPC method\n * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].\n *\n * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain\n * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the\n * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.\n *\n * @custom:oz-upgrades-unsafe-allow state-variable-immutable\n */\nabstract contract EIP712 is IERC5267 {\n using ShortStrings for *;\n\n bytes32 private constant TYPE_HASH =\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\");\n\n // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to\n // invalidate the cached domain separator if the chain id changes.\n bytes32 private immutable _cachedDomainSeparator;\n uint256 private immutable _cachedChainId;\n address private immutable _cachedThis;\n\n bytes32 private immutable _hashedName;\n bytes32 private immutable _hashedVersion;\n\n ShortString private immutable _name;\n ShortString private immutable _version;\n // slither-disable-next-line constable-states\n string private _nameFallback;\n // slither-disable-next-line constable-states\n string private _versionFallback;\n\n /**\n * @dev Initializes the domain separator and parameter caches.\n *\n * The meaning of `name` and `version` is specified in\n * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:\n *\n * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.\n * - `version`: the current major version of the signing domain.\n *\n * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart\n * contract upgrade].\n */\n constructor(string memory name, string memory version) {\n _name = name.toShortStringWithFallback(_nameFallback);\n _version = version.toShortStringWithFallback(_versionFallback);\n _hashedName = keccak256(bytes(name));\n _hashedVersion = keccak256(bytes(version));\n\n _cachedChainId = block.chainid;\n _cachedDomainSeparator = _buildDomainSeparator();\n _cachedThis = address(this);\n }\n\n /**\n * @dev Returns the domain separator for the current chain.\n */\n function _domainSeparatorV4() internal view returns (bytes32) {\n if (address(this) == _cachedThis && block.chainid == _cachedChainId) {\n return _cachedDomainSeparator;\n } else {\n return _buildDomainSeparator();\n }\n }\n\n function _buildDomainSeparator() private view returns (bytes32) {\n return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));\n }\n\n /**\n * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this\n * function returns the hash of the fully encoded EIP712 message for this domain.\n *\n * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:\n *\n * ```solidity\n * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(\n * keccak256(\"Mail(address to,string contents)\"),\n * mailTo,\n * keccak256(bytes(mailContents))\n * )));\n * address signer = ECDSA.recover(digest, signature);\n * ```\n */\n function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {\n return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);\n }\n\n /**\n * @inheritdoc IERC5267\n */\n function eip712Domain()\n public\n view\n virtual\n returns (\n bytes1 fields,\n string memory name,\n string memory version,\n uint256 chainId,\n address verifyingContract,\n bytes32 salt,\n uint256[] memory extensions\n )\n {\n return (\n hex\"0f\", // 01111\n _EIP712Name(),\n _EIP712Version(),\n block.chainid,\n address(this),\n bytes32(0),\n new uint256[](0)\n );\n }\n\n /**\n * @dev The name parameter for the EIP712 domain.\n *\n * NOTE: By default this function reads _name which is an immutable value.\n * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).\n */\n // solhint-disable-next-line func-name-mixedcase\n function _EIP712Name() internal view returns (string memory) {\n return _name.toStringWithFallback(_nameFallback);\n }\n\n /**\n * @dev The version parameter for the EIP712 domain.\n *\n * NOTE: By default this function reads _version which is an immutable value.\n * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).\n */\n // solhint-disable-next-line func-name-mixedcase\n function _EIP712Version() internal view returns (string memory) {\n return _version.toStringWithFallback(_versionFallback);\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/cryptography/MessageHashUtils.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)\n\npragma solidity ^0.8.20;\n\nimport {Strings} from \"../Strings.sol\";\n\n/**\n * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.\n *\n * The library provides methods for generating a hash of a message that conforms to the\n * https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]\n * specifications.\n */\nlibrary MessageHashUtils {\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x45` (`personal_sign` messages).\n *\n * The digest is calculated by prefixing a bytes32 `messageHash` with\n * `\"\\x19Ethereum Signed Message:\\n32\"` and hashing the result. It corresponds with the\n * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.\n *\n * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with\n * keccak256, although any bytes32 value can be safely used because the final digest will\n * be re-hashed.\n *\n * See {ECDSA-recover}.\n */\n function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n mstore(0x00, \"\\x19Ethereum Signed Message:\\n32\") // 32 is the bytes-length of messageHash\n mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix\n digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)\n }\n }\n\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x45` (`personal_sign` messages).\n *\n * The digest is calculated by prefixing an arbitrary `message` with\n * `\"\\x19Ethereum Signed Message:\\n\" + len(message)` and hashing the result. It corresponds with the\n * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.\n *\n * See {ECDSA-recover}.\n */\n function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {\n return\n keccak256(bytes.concat(\"\\x19Ethereum Signed Message:\\n\", bytes(Strings.toString(message.length)), message));\n }\n\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x00` (data with intended validator).\n *\n * The digest is calculated by prefixing an arbitrary `data` with `\"\\x19\\x00\"` and the intended\n * `validator` address. Then hashing the result.\n *\n * See {ECDSA-recover}.\n */\n function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(hex\"19_00\", validator, data));\n }\n\n /**\n * @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.\n */\n function toDataWithIntendedValidatorHash(\n address validator,\n bytes32 messageHash\n ) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n mstore(0x00, hex\"19_00\")\n mstore(0x02, shl(96, validator))\n mstore(0x16, messageHash)\n digest := keccak256(0x00, 0x36)\n }\n }\n\n /**\n * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).\n *\n * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with\n * `\\x19\\x01` and hashing the result. It corresponds to the hash signed by the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.\n *\n * See {ECDSA-recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n mstore(ptr, hex\"19_01\")\n mstore(add(ptr, 0x02), domainSeparator)\n mstore(add(ptr, 0x22), structHash)\n digest := keccak256(ptr, 0x42)\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/Errors.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Collection of common custom errors used in multiple contracts\n *\n * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.\n * It is recommended to avoid relying on the error API for critical functionality.\n *\n * _Available since v5.1._\n */\nlibrary Errors {\n /**\n * @dev The ETH balance of the account is not enough to perform the operation.\n */\n error InsufficientBalance(uint256 balance, uint256 needed);\n\n /**\n * @dev A call to an address target failed. The target may have reverted.\n */\n error FailedCall();\n\n /**\n * @dev The deployment failed.\n */\n error FailedDeployment();\n\n /**\n * @dev A necessary precompile is missing.\n */\n error MissingPrecompile(address);\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/introspection/ERC165.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/introspection/ERC165Checker.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165Checker.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @dev Library used to query support of an interface declared via {IERC165}.\n *\n * Note that these functions return the actual result of the query: they do not\n * `revert` if an interface is not supported. It is up to the caller to decide\n * what to do in these cases.\n */\nlibrary ERC165Checker {\n // As per the ERC-165 spec, no interface should ever match 0xffffffff\n bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff;\n\n /**\n * @dev Returns true if `account` supports the {IERC165} interface.\n */\n function supportsERC165(address account) internal view returns (bool) {\n // Any contract that implements ERC-165 must explicitly indicate support of\n // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid\n return\n supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&\n !supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);\n }\n\n /**\n * @dev Returns true if `account` supports the interface defined by\n * `interfaceId`. Support for {IERC165} itself is queried automatically.\n *\n * See {IERC165-supportsInterface}.\n */\n function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {\n // query support of both ERC-165 as per the spec and support of _interfaceId\n return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);\n }\n\n /**\n * @dev Returns a boolean array where each value corresponds to the\n * interfaces passed in and whether they're supported or not. This allows\n * you to batch check interfaces for a contract where your expectation\n * is that some interfaces may not be supported.\n *\n * See {IERC165-supportsInterface}.\n */\n function getSupportedInterfaces(\n address account,\n bytes4[] memory interfaceIds\n ) internal view returns (bool[] memory) {\n // an array of booleans corresponding to interfaceIds and whether they're supported or not\n bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);\n\n // query support of ERC-165 itself\n if (supportsERC165(account)) {\n // query support of each interface in interfaceIds\n for (uint256 i = 0; i < interfaceIds.length; i++) {\n interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);\n }\n }\n\n return interfaceIdsSupported;\n }\n\n /**\n * @dev Returns true if `account` supports all the interfaces defined in\n * `interfaceIds`. Support for {IERC165} itself is queried automatically.\n *\n * Batch-querying can lead to gas savings by skipping repeated checks for\n * {IERC165} support.\n *\n * See {IERC165-supportsInterface}.\n */\n function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {\n // query support of ERC-165 itself\n if (!supportsERC165(account)) {\n return false;\n }\n\n // query support of each interface in interfaceIds\n for (uint256 i = 0; i < interfaceIds.length; i++) {\n if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {\n return false;\n }\n }\n\n // all interfaces supported\n return true;\n }\n\n /**\n * @notice Query if a contract implements an interface, does not check ERC-165 support\n * @param account The address of the contract to query for support of an interface\n * @param interfaceId The interface identifier, as specified in ERC-165\n * @return true if the contract at account indicates support of the interface with\n * identifier interfaceId, false otherwise\n * @dev Assumes that account contains a contract that supports ERC-165, otherwise\n * the behavior of this method is undefined. This precondition can be checked\n * with {supportsERC165}.\n *\n * Some precompiled contracts will falsely indicate support for a given interface, so caution\n * should be exercised when using this function.\n *\n * Interface identification is specified in ERC-165.\n */\n function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {\n // prepare call\n bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));\n\n // perform static call\n bool success;\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)\n returnSize := returndatasize()\n returnValue := mload(0x00)\n }\n\n return success && returnSize >= 0x20 && returnValue > 0;\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/introspection/IERC165.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/math/Math.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.20;\n\nimport {Panic} from \"../Panic.sol\";\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Floor, // Toward negative infinity\n Ceil, // Toward positive infinity\n Trunc, // Toward zero\n Expand // Away from zero\n }\n\n /**\n * @dev Return the 512-bit addition of two uint256.\n *\n * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.\n */\n function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n assembly (\"memory-safe\") {\n low := add(a, b)\n high := lt(low, a)\n }\n }\n\n /**\n * @dev Return the 512-bit multiplication of two uint256.\n *\n * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.\n */\n function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use\n // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = high * 2²⁵⁶ + low.\n assembly (\"memory-safe\") {\n let mm := mulmod(a, b, not(0))\n low := mul(a, b)\n high := sub(sub(mm, low), lt(mm, low))\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a + b;\n success = c >= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a - b;\n success = c <= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a * b;\n assembly (\"memory-safe\") {\n // Only true when the multiplication doesn't overflow\n // (c / a == b) || (a == 0)\n success := or(eq(div(c, a), b), iszero(a))\n }\n // equivalent to: success ? c : 0\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `DIV` opcode returns zero when the denominator is 0.\n result := div(a, b)\n }\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `MOD` opcode returns zero when the denominator is 0.\n result := mod(a, b)\n }\n }\n }\n\n /**\n * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryAdd(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.\n */\n function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {\n (, uint256 result) = trySub(a, b);\n return result;\n }\n\n /**\n * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryMul(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * SafeCast.toUint(condition));\n }\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds towards infinity instead\n * of rounding towards zero.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n if (b == 0) {\n // Guarantee the same behavior as in a regular Solidity division.\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n\n // The following calculation ensures accurate ceiling division without overflow.\n // Since a is non-zero, (a - 1) / b will not overflow.\n // The largest possible result occurs when (a - 1) / b is type(uint256).max,\n // but the largest value we can obtain is type(uint256).max - 1, which happens\n // when a = type(uint256).max and b = 1.\n unchecked {\n return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);\n }\n }\n\n /**\n * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or\n * denominator == 0.\n *\n * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by\n * Uniswap Labs also under MIT license.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n\n // Handle non-overflow cases, 256 by 256 division.\n if (high == 0) {\n // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n // The surrounding unchecked block does not change this fact.\n // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n return low / denominator;\n }\n\n // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.\n if (denominator <= high) {\n Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));\n }\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [high low].\n uint256 remainder;\n assembly (\"memory-safe\") {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n high := sub(high, gt(remainder, low))\n low := sub(low, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator.\n // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.\n\n uint256 twos = denominator & (0 - denominator);\n assembly (\"memory-safe\") {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [high low] by twos.\n low := div(low, twos)\n\n // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from high into low.\n low |= high * twos;\n\n // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such\n // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv ≡ 1 mod 2⁴.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also\n // works in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶\n inverse *= 2 - denominator * inverse; // inverse mod 2³²\n inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴\n inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is\n // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high\n // is no longer required.\n result = low * inverse;\n return result;\n }\n }\n\n /**\n * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);\n }\n\n /**\n * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.\n */\n function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n if (high >= 1 << n) {\n Panic.panic(Panic.UNDER_OVERFLOW);\n }\n return (high << (256 - n)) | (low >> n);\n }\n }\n\n /**\n * @dev Calculates x * y >> n with full precision, following the selected rounding direction.\n */\n function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {\n return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);\n }\n\n /**\n * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.\n *\n * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.\n * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.\n *\n * If the input value is not inversible, 0 is returned.\n *\n * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the\n * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.\n */\n function invMod(uint256 a, uint256 n) internal pure returns (uint256) {\n unchecked {\n if (n == 0) return 0;\n\n // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)\n // Used to compute integers x and y such that: ax + ny = gcd(a, n).\n // When the gcd is 1, then the inverse of a modulo n exists and it's x.\n // ax + ny = 1\n // ax = 1 + (-y)n\n // ax ≡ 1 (mod n) # x is the inverse of a modulo n\n\n // If the remainder is 0 the gcd is n right away.\n uint256 remainder = a % n;\n uint256 gcd = n;\n\n // Therefore the initial coefficients are:\n // ax + ny = gcd(a, n) = n\n // 0a + 1n = n\n int256 x = 0;\n int256 y = 1;\n\n while (remainder != 0) {\n uint256 quotient = gcd / remainder;\n\n (gcd, remainder) = (\n // The old remainder is the next gcd to try.\n remainder,\n // Compute the next remainder.\n // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd\n // where gcd is at most n (capped to type(uint256).max)\n gcd - remainder * quotient\n );\n\n (x, y) = (\n // Increment the coefficient of a.\n y,\n // Decrement the coefficient of n.\n // Can overflow, but the result is casted to uint256 so that the\n // next value of y is \"wrapped around\" to a value between 0 and n - 1.\n x - y * int256(quotient)\n );\n }\n\n if (gcd != 1) return 0; // No inverse exists.\n return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.\n }\n }\n\n /**\n * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.\n *\n * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is\n * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that\n * `a**(p-2)` is the modular multiplicative inverse of a in Fp.\n *\n * NOTE: this function does NOT check that `p` is a prime greater than `2`.\n */\n function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {\n unchecked {\n return Math.modExp(a, p - 2, p);\n }\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)\n *\n * Requirements:\n * - modulus can't be zero\n * - underlying staticcall to precompile must succeed\n *\n * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make\n * sure the chain you're using it on supports the precompiled contract for modular exponentiation\n * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,\n * the underlying function will succeed given the lack of a revert, but the result may be incorrectly\n * interpreted as 0.\n */\n function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {\n (bool success, uint256 result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).\n * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying\n * to operate modulo 0 or if the underlying precompile reverted.\n *\n * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain\n * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in\n * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack\n * of a revert, but the result may be incorrectly interpreted as 0.\n */\n function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {\n if (m == 0) return (false, 0);\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n // | Offset | Content | Content (Hex) |\n // |-----------|------------|--------------------------------------------------------------------|\n // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x60:0x7f | value of b | 0x<.............................................................b> |\n // | 0x80:0x9f | value of e | 0x<.............................................................e> |\n // | 0xa0:0xbf | value of m | 0x<.............................................................m> |\n mstore(ptr, 0x20)\n mstore(add(ptr, 0x20), 0x20)\n mstore(add(ptr, 0x40), 0x20)\n mstore(add(ptr, 0x60), b)\n mstore(add(ptr, 0x80), e)\n mstore(add(ptr, 0xa0), m)\n\n // Given the result < m, it's guaranteed to fit in 32 bytes,\n // so we can use the memory scratch space located at offset 0.\n success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)\n result := mload(0x00)\n }\n }\n\n /**\n * @dev Variant of {modExp} that supports inputs of arbitrary length.\n */\n function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {\n (bool success, bytes memory result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Variant of {tryModExp} that supports inputs of arbitrary length.\n */\n function tryModExp(\n bytes memory b,\n bytes memory e,\n bytes memory m\n ) internal view returns (bool success, bytes memory result) {\n if (_zeroBytes(m)) return (false, new bytes(0));\n\n uint256 mLen = m.length;\n\n // Encode call args in result and move the free memory pointer\n result = abi.encodePacked(b.length, e.length, mLen, b, e, m);\n\n assembly (\"memory-safe\") {\n let dataPtr := add(result, 0x20)\n // Write result on top of args to avoid allocating extra memory.\n success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)\n // Overwrite the length.\n // result.length > returndatasize() is guaranteed because returndatasize() == m.length\n mstore(result, mLen)\n // Set the memory pointer after the returned data.\n mstore(0x40, add(dataPtr, mLen))\n }\n }\n\n /**\n * @dev Returns whether the provided byte array is zero.\n */\n function _zeroBytes(bytes memory byteArray) private pure returns (bool) {\n for (uint256 i = 0; i < byteArray.length; ++i) {\n if (byteArray[i] != 0) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded\n * towards zero.\n *\n * This method is based on Newton's method for computing square roots; the algorithm is restricted to only\n * using integer operations.\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n unchecked {\n // Take care of easy edge cases when a == 0 or a == 1\n if (a <= 1) {\n return a;\n }\n\n // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a\n // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between\n // the current value as `ε_n = | x_n - sqrt(a) |`.\n //\n // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root\n // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is\n // bigger than any uint256.\n //\n // By noticing that\n // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`\n // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar\n // to the msb function.\n uint256 aa = a;\n uint256 xn = 1;\n\n if (aa >= (1 << 128)) {\n aa >>= 128;\n xn <<= 64;\n }\n if (aa >= (1 << 64)) {\n aa >>= 64;\n xn <<= 32;\n }\n if (aa >= (1 << 32)) {\n aa >>= 32;\n xn <<= 16;\n }\n if (aa >= (1 << 16)) {\n aa >>= 16;\n xn <<= 8;\n }\n if (aa >= (1 << 8)) {\n aa >>= 8;\n xn <<= 4;\n }\n if (aa >= (1 << 4)) {\n aa >>= 4;\n xn <<= 2;\n }\n if (aa >= (1 << 2)) {\n xn <<= 1;\n }\n\n // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).\n //\n // We can refine our estimation by noticing that the middle of that interval minimizes the error.\n // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).\n // This is going to be our x_0 (and ε_0)\n xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)\n\n // From here, Newton's method give us:\n // x_{n+1} = (x_n + a / x_n) / 2\n //\n // One should note that:\n // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a\n // = ((x_n² + a) / (2 * x_n))² - a\n // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a\n // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)\n // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)\n // = (x_n² - a)² / (2 * x_n)²\n // = ((x_n² - a) / (2 * x_n))²\n // ≥ 0\n // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n\n //\n // This gives us the proof of quadratic convergence of the sequence:\n // ε_{n+1} = | x_{n+1} - sqrt(a) |\n // = | (x_n + a / x_n) / 2 - sqrt(a) |\n // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |\n // = | (x_n - sqrt(a))² / (2 * x_n) |\n // = | ε_n² / (2 * x_n) |\n // = ε_n² / | (2 * x_n) |\n //\n // For the first iteration, we have a special case where x_0 is known:\n // ε_1 = ε_0² / | (2 * x_0) |\n // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))\n // ≤ 2**(2*e-4) / (3 * 2**(e-1))\n // ≤ 2**(e-3) / 3\n // ≤ 2**(e-3-log2(3))\n // ≤ 2**(e-4.5)\n //\n // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:\n // ε_{n+1} = ε_n² / | (2 * x_n) |\n // ≤ (2**(e-k))² / (2 * 2**(e-1))\n // ≤ 2**(2*e-2*k) / 2**e\n // ≤ 2**(e-2*k)\n xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above\n xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5\n xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9\n xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18\n xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36\n xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72\n\n // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision\n // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either\n // sqrt(a) or sqrt(a) + 1.\n return xn - SafeCast.toUint(xn > a / xn);\n }\n }\n\n /**\n * @dev Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);\n }\n }\n\n /**\n * @dev Return the log in base 2 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log2(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // If upper 8 bits of 16-bit half set, add 8 to result\n r |= SafeCast.toUint((x >> r) > 0xff) << 3;\n // If upper 4 bits of 8-bit half set, add 4 to result\n r |= SafeCast.toUint((x >> r) > 0xf) << 2;\n\n // Shifts value right by the current result and use it as an index into this lookup table:\n //\n // | x (4 bits) | index | table[index] = MSB position |\n // |------------|---------|-----------------------------|\n // | 0000 | 0 | table[0] = 0 |\n // | 0001 | 1 | table[1] = 0 |\n // | 0010 | 2 | table[2] = 1 |\n // | 0011 | 3 | table[3] = 1 |\n // | 0100 | 4 | table[4] = 2 |\n // | 0101 | 5 | table[5] = 2 |\n // | 0110 | 6 | table[6] = 2 |\n // | 0111 | 7 | table[7] = 2 |\n // | 1000 | 8 | table[8] = 3 |\n // | 1001 | 9 | table[9] = 3 |\n // | 1010 | 10 | table[10] = 3 |\n // | 1011 | 11 | table[11] = 3 |\n // | 1100 | 12 | table[12] = 3 |\n // | 1101 | 13 | table[13] = 3 |\n // | 1110 | 14 | table[14] = 3 |\n // | 1111 | 15 | table[15] = 3 |\n //\n // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.\n assembly (\"memory-safe\") {\n r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))\n }\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);\n }\n }\n\n /**\n * @dev Return the log in base 10 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10 ** 64) {\n value /= 10 ** 64;\n result += 64;\n }\n if (value >= 10 ** 32) {\n value /= 10 ** 32;\n result += 32;\n }\n if (value >= 10 ** 16) {\n value /= 10 ** 16;\n result += 16;\n }\n if (value >= 10 ** 8) {\n value /= 10 ** 8;\n result += 8;\n }\n if (value >= 10 ** 4) {\n value /= 10 ** 4;\n result += 4;\n }\n if (value >= 10 ** 2) {\n value /= 10 ** 2;\n result += 2;\n }\n if (value >= 10 ** 1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);\n }\n }\n\n /**\n * @dev Return the log in base 256 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8\n return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);\n }\n\n /**\n * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);\n }\n }\n\n /**\n * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.\n */\n function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {\n return uint8(rounding) % 2 == 1;\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/math/SafeCast.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)\n// This file was procedurally generated from scripts/generate/templates/SafeCast.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeCast {\n /**\n * @dev Value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);\n\n /**\n * @dev An int value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedIntToUint(int256 value);\n\n /**\n * @dev Value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);\n\n /**\n * @dev An uint value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedUintToInt(uint256 value);\n\n /**\n * @dev Returns the downcasted uint248 from uint256, reverting on\n * overflow (when the input is greater than largest uint248).\n *\n * Counterpart to Solidity's `uint248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toUint248(uint256 value) internal pure returns (uint248) {\n if (value > type(uint248).max) {\n revert SafeCastOverflowedUintDowncast(248, value);\n }\n return uint248(value);\n }\n\n /**\n * @dev Returns the downcasted uint240 from uint256, reverting on\n * overflow (when the input is greater than largest uint240).\n *\n * Counterpart to Solidity's `uint240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toUint240(uint256 value) internal pure returns (uint240) {\n if (value > type(uint240).max) {\n revert SafeCastOverflowedUintDowncast(240, value);\n }\n return uint240(value);\n }\n\n /**\n * @dev Returns the downcasted uint232 from uint256, reverting on\n * overflow (when the input is greater than largest uint232).\n *\n * Counterpart to Solidity's `uint232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toUint232(uint256 value) internal pure returns (uint232) {\n if (value > type(uint232).max) {\n revert SafeCastOverflowedUintDowncast(232, value);\n }\n return uint232(value);\n }\n\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n if (value > type(uint224).max) {\n revert SafeCastOverflowedUintDowncast(224, value);\n }\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint216 from uint256, reverting on\n * overflow (when the input is greater than largest uint216).\n *\n * Counterpart to Solidity's `uint216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toUint216(uint256 value) internal pure returns (uint216) {\n if (value > type(uint216).max) {\n revert SafeCastOverflowedUintDowncast(216, value);\n }\n return uint216(value);\n }\n\n /**\n * @dev Returns the downcasted uint208 from uint256, reverting on\n * overflow (when the input is greater than largest uint208).\n *\n * Counterpart to Solidity's `uint208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toUint208(uint256 value) internal pure returns (uint208) {\n if (value > type(uint208).max) {\n revert SafeCastOverflowedUintDowncast(208, value);\n }\n return uint208(value);\n }\n\n /**\n * @dev Returns the downcasted uint200 from uint256, reverting on\n * overflow (when the input is greater than largest uint200).\n *\n * Counterpart to Solidity's `uint200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toUint200(uint256 value) internal pure returns (uint200) {\n if (value > type(uint200).max) {\n revert SafeCastOverflowedUintDowncast(200, value);\n }\n return uint200(value);\n }\n\n /**\n * @dev Returns the downcasted uint192 from uint256, reverting on\n * overflow (when the input is greater than largest uint192).\n *\n * Counterpart to Solidity's `uint192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toUint192(uint256 value) internal pure returns (uint192) {\n if (value > type(uint192).max) {\n revert SafeCastOverflowedUintDowncast(192, value);\n }\n return uint192(value);\n }\n\n /**\n * @dev Returns the downcasted uint184 from uint256, reverting on\n * overflow (when the input is greater than largest uint184).\n *\n * Counterpart to Solidity's `uint184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toUint184(uint256 value) internal pure returns (uint184) {\n if (value > type(uint184).max) {\n revert SafeCastOverflowedUintDowncast(184, value);\n }\n return uint184(value);\n }\n\n /**\n * @dev Returns the downcasted uint176 from uint256, reverting on\n * overflow (when the input is greater than largest uint176).\n *\n * Counterpart to Solidity's `uint176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toUint176(uint256 value) internal pure returns (uint176) {\n if (value > type(uint176).max) {\n revert SafeCastOverflowedUintDowncast(176, value);\n }\n return uint176(value);\n }\n\n /**\n * @dev Returns the downcasted uint168 from uint256, reverting on\n * overflow (when the input is greater than largest uint168).\n *\n * Counterpart to Solidity's `uint168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toUint168(uint256 value) internal pure returns (uint168) {\n if (value > type(uint168).max) {\n revert SafeCastOverflowedUintDowncast(168, value);\n }\n return uint168(value);\n }\n\n /**\n * @dev Returns the downcasted uint160 from uint256, reverting on\n * overflow (when the input is greater than largest uint160).\n *\n * Counterpart to Solidity's `uint160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toUint160(uint256 value) internal pure returns (uint160) {\n if (value > type(uint160).max) {\n revert SafeCastOverflowedUintDowncast(160, value);\n }\n return uint160(value);\n }\n\n /**\n * @dev Returns the downcasted uint152 from uint256, reverting on\n * overflow (when the input is greater than largest uint152).\n *\n * Counterpart to Solidity's `uint152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toUint152(uint256 value) internal pure returns (uint152) {\n if (value > type(uint152).max) {\n revert SafeCastOverflowedUintDowncast(152, value);\n }\n return uint152(value);\n }\n\n /**\n * @dev Returns the downcasted uint144 from uint256, reverting on\n * overflow (when the input is greater than largest uint144).\n *\n * Counterpart to Solidity's `uint144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toUint144(uint256 value) internal pure returns (uint144) {\n if (value > type(uint144).max) {\n revert SafeCastOverflowedUintDowncast(144, value);\n }\n return uint144(value);\n }\n\n /**\n * @dev Returns the downcasted uint136 from uint256, reverting on\n * overflow (when the input is greater than largest uint136).\n *\n * Counterpart to Solidity's `uint136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toUint136(uint256 value) internal pure returns (uint136) {\n if (value > type(uint136).max) {\n revert SafeCastOverflowedUintDowncast(136, value);\n }\n return uint136(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) {\n revert SafeCastOverflowedUintDowncast(128, value);\n }\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint120 from uint256, reverting on\n * overflow (when the input is greater than largest uint120).\n *\n * Counterpart to Solidity's `uint120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toUint120(uint256 value) internal pure returns (uint120) {\n if (value > type(uint120).max) {\n revert SafeCastOverflowedUintDowncast(120, value);\n }\n return uint120(value);\n }\n\n /**\n * @dev Returns the downcasted uint112 from uint256, reverting on\n * overflow (when the input is greater than largest uint112).\n *\n * Counterpart to Solidity's `uint112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toUint112(uint256 value) internal pure returns (uint112) {\n if (value > type(uint112).max) {\n revert SafeCastOverflowedUintDowncast(112, value);\n }\n return uint112(value);\n }\n\n /**\n * @dev Returns the downcasted uint104 from uint256, reverting on\n * overflow (when the input is greater than largest uint104).\n *\n * Counterpart to Solidity's `uint104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toUint104(uint256 value) internal pure returns (uint104) {\n if (value > type(uint104).max) {\n revert SafeCastOverflowedUintDowncast(104, value);\n }\n return uint104(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n if (value > type(uint96).max) {\n revert SafeCastOverflowedUintDowncast(96, value);\n }\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint88 from uint256, reverting on\n * overflow (when the input is greater than largest uint88).\n *\n * Counterpart to Solidity's `uint88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toUint88(uint256 value) internal pure returns (uint88) {\n if (value > type(uint88).max) {\n revert SafeCastOverflowedUintDowncast(88, value);\n }\n return uint88(value);\n }\n\n /**\n * @dev Returns the downcasted uint80 from uint256, reverting on\n * overflow (when the input is greater than largest uint80).\n *\n * Counterpart to Solidity's `uint80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toUint80(uint256 value) internal pure returns (uint80) {\n if (value > type(uint80).max) {\n revert SafeCastOverflowedUintDowncast(80, value);\n }\n return uint80(value);\n }\n\n /**\n * @dev Returns the downcasted uint72 from uint256, reverting on\n * overflow (when the input is greater than largest uint72).\n *\n * Counterpart to Solidity's `uint72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toUint72(uint256 value) internal pure returns (uint72) {\n if (value > type(uint72).max) {\n revert SafeCastOverflowedUintDowncast(72, value);\n }\n return uint72(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n if (value > type(uint64).max) {\n revert SafeCastOverflowedUintDowncast(64, value);\n }\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint56 from uint256, reverting on\n * overflow (when the input is greater than largest uint56).\n *\n * Counterpart to Solidity's `uint56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toUint56(uint256 value) internal pure returns (uint56) {\n if (value > type(uint56).max) {\n revert SafeCastOverflowedUintDowncast(56, value);\n }\n return uint56(value);\n }\n\n /**\n * @dev Returns the downcasted uint48 from uint256, reverting on\n * overflow (when the input is greater than largest uint48).\n *\n * Counterpart to Solidity's `uint48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toUint48(uint256 value) internal pure returns (uint48) {\n if (value > type(uint48).max) {\n revert SafeCastOverflowedUintDowncast(48, value);\n }\n return uint48(value);\n }\n\n /**\n * @dev Returns the downcasted uint40 from uint256, reverting on\n * overflow (when the input is greater than largest uint40).\n *\n * Counterpart to Solidity's `uint40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toUint40(uint256 value) internal pure returns (uint40) {\n if (value > type(uint40).max) {\n revert SafeCastOverflowedUintDowncast(40, value);\n }\n return uint40(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n if (value > type(uint32).max) {\n revert SafeCastOverflowedUintDowncast(32, value);\n }\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint24 from uint256, reverting on\n * overflow (when the input is greater than largest uint24).\n *\n * Counterpart to Solidity's `uint24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toUint24(uint256 value) internal pure returns (uint24) {\n if (value > type(uint24).max) {\n revert SafeCastOverflowedUintDowncast(24, value);\n }\n return uint24(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n if (value > type(uint16).max) {\n revert SafeCastOverflowedUintDowncast(16, value);\n }\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n if (value > type(uint8).max) {\n revert SafeCastOverflowedUintDowncast(8, value);\n }\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n if (value < 0) {\n revert SafeCastOverflowedIntToUint(value);\n }\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int248 from int256, reverting on\n * overflow (when the input is less than smallest int248 or\n * greater than largest int248).\n *\n * Counterpart to Solidity's `int248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toInt248(int256 value) internal pure returns (int248 downcasted) {\n downcasted = int248(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(248, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int240 from int256, reverting on\n * overflow (when the input is less than smallest int240 or\n * greater than largest int240).\n *\n * Counterpart to Solidity's `int240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toInt240(int256 value) internal pure returns (int240 downcasted) {\n downcasted = int240(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(240, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int232 from int256, reverting on\n * overflow (when the input is less than smallest int232 or\n * greater than largest int232).\n *\n * Counterpart to Solidity's `int232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toInt232(int256 value) internal pure returns (int232 downcasted) {\n downcasted = int232(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(232, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int224 from int256, reverting on\n * overflow (when the input is less than smallest int224 or\n * greater than largest int224).\n *\n * Counterpart to Solidity's `int224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toInt224(int256 value) internal pure returns (int224 downcasted) {\n downcasted = int224(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(224, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int216 from int256, reverting on\n * overflow (when the input is less than smallest int216 or\n * greater than largest int216).\n *\n * Counterpart to Solidity's `int216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toInt216(int256 value) internal pure returns (int216 downcasted) {\n downcasted = int216(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(216, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int208 from int256, reverting on\n * overflow (when the input is less than smallest int208 or\n * greater than largest int208).\n *\n * Counterpart to Solidity's `int208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toInt208(int256 value) internal pure returns (int208 downcasted) {\n downcasted = int208(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(208, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int200 from int256, reverting on\n * overflow (when the input is less than smallest int200 or\n * greater than largest int200).\n *\n * Counterpart to Solidity's `int200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toInt200(int256 value) internal pure returns (int200 downcasted) {\n downcasted = int200(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(200, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int192 from int256, reverting on\n * overflow (when the input is less than smallest int192 or\n * greater than largest int192).\n *\n * Counterpart to Solidity's `int192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toInt192(int256 value) internal pure returns (int192 downcasted) {\n downcasted = int192(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(192, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int184 from int256, reverting on\n * overflow (when the input is less than smallest int184 or\n * greater than largest int184).\n *\n * Counterpart to Solidity's `int184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toInt184(int256 value) internal pure returns (int184 downcasted) {\n downcasted = int184(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(184, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int176 from int256, reverting on\n * overflow (when the input is less than smallest int176 or\n * greater than largest int176).\n *\n * Counterpart to Solidity's `int176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toInt176(int256 value) internal pure returns (int176 downcasted) {\n downcasted = int176(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(176, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int168 from int256, reverting on\n * overflow (when the input is less than smallest int168 or\n * greater than largest int168).\n *\n * Counterpart to Solidity's `int168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toInt168(int256 value) internal pure returns (int168 downcasted) {\n downcasted = int168(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(168, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int160 from int256, reverting on\n * overflow (when the input is less than smallest int160 or\n * greater than largest int160).\n *\n * Counterpart to Solidity's `int160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toInt160(int256 value) internal pure returns (int160 downcasted) {\n downcasted = int160(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(160, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int152 from int256, reverting on\n * overflow (when the input is less than smallest int152 or\n * greater than largest int152).\n *\n * Counterpart to Solidity's `int152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toInt152(int256 value) internal pure returns (int152 downcasted) {\n downcasted = int152(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(152, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int144 from int256, reverting on\n * overflow (when the input is less than smallest int144 or\n * greater than largest int144).\n *\n * Counterpart to Solidity's `int144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toInt144(int256 value) internal pure returns (int144 downcasted) {\n downcasted = int144(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(144, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int136 from int256, reverting on\n * overflow (when the input is less than smallest int136 or\n * greater than largest int136).\n *\n * Counterpart to Solidity's `int136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toInt136(int256 value) internal pure returns (int136 downcasted) {\n downcasted = int136(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(136, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toInt128(int256 value) internal pure returns (int128 downcasted) {\n downcasted = int128(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(128, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int120 from int256, reverting on\n * overflow (when the input is less than smallest int120 or\n * greater than largest int120).\n *\n * Counterpart to Solidity's `int120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toInt120(int256 value) internal pure returns (int120 downcasted) {\n downcasted = int120(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(120, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int112 from int256, reverting on\n * overflow (when the input is less than smallest int112 or\n * greater than largest int112).\n *\n * Counterpart to Solidity's `int112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toInt112(int256 value) internal pure returns (int112 downcasted) {\n downcasted = int112(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(112, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int104 from int256, reverting on\n * overflow (when the input is less than smallest int104 or\n * greater than largest int104).\n *\n * Counterpart to Solidity's `int104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toInt104(int256 value) internal pure returns (int104 downcasted) {\n downcasted = int104(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(104, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int96 from int256, reverting on\n * overflow (when the input is less than smallest int96 or\n * greater than largest int96).\n *\n * Counterpart to Solidity's `int96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toInt96(int256 value) internal pure returns (int96 downcasted) {\n downcasted = int96(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(96, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int88 from int256, reverting on\n * overflow (when the input is less than smallest int88 or\n * greater than largest int88).\n *\n * Counterpart to Solidity's `int88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toInt88(int256 value) internal pure returns (int88 downcasted) {\n downcasted = int88(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(88, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int80 from int256, reverting on\n * overflow (when the input is less than smallest int80 or\n * greater than largest int80).\n *\n * Counterpart to Solidity's `int80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toInt80(int256 value) internal pure returns (int80 downcasted) {\n downcasted = int80(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(80, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int72 from int256, reverting on\n * overflow (when the input is less than smallest int72 or\n * greater than largest int72).\n *\n * Counterpart to Solidity's `int72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toInt72(int256 value) internal pure returns (int72 downcasted) {\n downcasted = int72(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(72, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toInt64(int256 value) internal pure returns (int64 downcasted) {\n downcasted = int64(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(64, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int56 from int256, reverting on\n * overflow (when the input is less than smallest int56 or\n * greater than largest int56).\n *\n * Counterpart to Solidity's `int56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toInt56(int256 value) internal pure returns (int56 downcasted) {\n downcasted = int56(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(56, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int48 from int256, reverting on\n * overflow (when the input is less than smallest int48 or\n * greater than largest int48).\n *\n * Counterpart to Solidity's `int48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toInt48(int256 value) internal pure returns (int48 downcasted) {\n downcasted = int48(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(48, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int40 from int256, reverting on\n * overflow (when the input is less than smallest int40 or\n * greater than largest int40).\n *\n * Counterpart to Solidity's `int40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toInt40(int256 value) internal pure returns (int40 downcasted) {\n downcasted = int40(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(40, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toInt32(int256 value) internal pure returns (int32 downcasted) {\n downcasted = int32(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(32, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int24 from int256, reverting on\n * overflow (when the input is less than smallest int24 or\n * greater than largest int24).\n *\n * Counterpart to Solidity's `int24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toInt24(int256 value) internal pure returns (int24 downcasted) {\n downcasted = int24(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(24, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toInt16(int256 value) internal pure returns (int16 downcasted) {\n downcasted = int16(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(16, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toInt8(int256 value) internal pure returns (int8 downcasted) {\n downcasted = int8(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(8, value);\n }\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n if (value > uint256(type(int256).max)) {\n revert SafeCastOverflowedUintToInt(value);\n }\n return int256(value);\n }\n\n /**\n * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.\n */\n function toUint(bool b) internal pure returns (uint256 u) {\n assembly (\"memory-safe\") {\n u := iszero(iszero(b))\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/math/SignedMath.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)\n\npragma solidity ^0.8.20;\n\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard signed math utilities missing in the Solidity language.\n */\nlibrary SignedMath {\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));\n }\n }\n\n /**\n * @dev Returns the largest of two signed numbers.\n */\n function max(int256 a, int256 b) internal pure returns (int256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two signed numbers.\n */\n function min(int256 a, int256 b) internal pure returns (int256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two signed numbers without overflow.\n * The result is rounded towards zero.\n */\n function average(int256 a, int256 b) internal pure returns (int256) {\n // Formula from the book \"Hacker's Delight\"\n int256 x = (a & b) + ((a ^ b) >> 1);\n return x + (int256(uint256(x) >> 255) & (a ^ b));\n }\n\n /**\n * @dev Returns the absolute unsigned value of a signed value.\n */\n function abs(int256 n) internal pure returns (uint256) {\n unchecked {\n // Formula from the \"Bit Twiddling Hacks\" by Sean Eron Anderson.\n // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,\n // taking advantage of the most significant (or \"sign\" bit) in two's complement representation.\n // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,\n // the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).\n int256 mask = n >> 255;\n\n // A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.\n return uint256((n + mask) ^ mask);\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/Panic.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Helper library for emitting standardized panic codes.\n *\n * ```solidity\n * contract Example {\n * using Panic for uint256;\n *\n * // Use any of the declared internal constants\n * function foo() { Panic.GENERIC.panic(); }\n *\n * // Alternatively\n * function foo() { Panic.panic(Panic.GENERIC); }\n * }\n * ```\n *\n * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].\n *\n * _Available since v5.1._\n */\n// slither-disable-next-line unused-state\nlibrary Panic {\n /// @dev generic / unspecified error\n uint256 internal constant GENERIC = 0x00;\n /// @dev used by the assert() builtin\n uint256 internal constant ASSERT = 0x01;\n /// @dev arithmetic underflow or overflow\n uint256 internal constant UNDER_OVERFLOW = 0x11;\n /// @dev division or modulo by zero\n uint256 internal constant DIVISION_BY_ZERO = 0x12;\n /// @dev enum conversion error\n uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;\n /// @dev invalid encoding in storage\n uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;\n /// @dev empty array pop\n uint256 internal constant EMPTY_ARRAY_POP = 0x31;\n /// @dev array out of bounds access\n uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;\n /// @dev resource error (too large allocation or too large array)\n uint256 internal constant RESOURCE_ERROR = 0x41;\n /// @dev calling invalid internal function\n uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;\n\n /// @dev Reverts with a panic code. Recommended to use with\n /// the internal constants with predefined codes.\n function panic(uint256 code) internal pure {\n assembly (\"memory-safe\") {\n mstore(0x00, 0x4e487b71)\n mstore(0x20, code)\n revert(0x1c, 0x24)\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/ReentrancyGuard.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,\n * consider using {ReentrancyGuardTransient} instead.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant NOT_ENTERED = 1;\n uint256 private constant ENTERED = 2;\n\n uint256 private _status;\n\n /**\n * @dev Unauthorized reentrant call.\n */\n error ReentrancyGuardReentrantCall();\n\n constructor() {\n _status = NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be NOT_ENTERED\n if (_status == ENTERED) {\n revert ReentrancyGuardReentrantCall();\n }\n\n // Any calls to nonReentrant after this point will fail\n _status = ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = NOT_ENTERED;\n }\n\n /**\n * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n * `nonReentrant` function in the call stack.\n */\n function _reentrancyGuardEntered() internal view returns (bool) {\n return _status == ENTERED;\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/ShortStrings.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/ShortStrings.sol)\n\npragma solidity ^0.8.20;\n\nimport {StorageSlot} from \"./StorageSlot.sol\";\n\n// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |\n// | length | 0x BB |\ntype ShortString is bytes32;\n\n/**\n * @dev This library provides functions to convert short memory strings\n * into a `ShortString` type that can be used as an immutable variable.\n *\n * Strings of arbitrary length can be optimized using this library if\n * they are short enough (up to 31 bytes) by packing them with their\n * length (1 byte) in a single EVM word (32 bytes). Additionally, a\n * fallback mechanism can be used for every other case.\n *\n * Usage example:\n *\n * ```solidity\n * contract Named {\n * using ShortStrings for *;\n *\n * ShortString private immutable _name;\n * string private _nameFallback;\n *\n * constructor(string memory contractName) {\n * _name = contractName.toShortStringWithFallback(_nameFallback);\n * }\n *\n * function name() external view returns (string memory) {\n * return _name.toStringWithFallback(_nameFallback);\n * }\n * }\n * ```\n */\nlibrary ShortStrings {\n // Used as an identifier for strings longer than 31 bytes.\n bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;\n\n error StringTooLong(string str);\n error InvalidShortString();\n\n /**\n * @dev Encode a string of at most 31 chars into a `ShortString`.\n *\n * This will trigger a `StringTooLong` error is the input string is too long.\n */\n function toShortString(string memory str) internal pure returns (ShortString) {\n bytes memory bstr = bytes(str);\n if (bstr.length > 31) {\n revert StringTooLong(str);\n }\n return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));\n }\n\n /**\n * @dev Decode a `ShortString` back to a \"normal\" string.\n */\n function toString(ShortString sstr) internal pure returns (string memory) {\n uint256 len = byteLength(sstr);\n // using `new string(len)` would work locally but is not memory safe.\n string memory str = new string(32);\n assembly (\"memory-safe\") {\n mstore(str, len)\n mstore(add(str, 0x20), sstr)\n }\n return str;\n }\n\n /**\n * @dev Return the length of a `ShortString`.\n */\n function byteLength(ShortString sstr) internal pure returns (uint256) {\n uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;\n if (result > 31) {\n revert InvalidShortString();\n }\n return result;\n }\n\n /**\n * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.\n */\n function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {\n if (bytes(value).length < 32) {\n return toShortString(value);\n } else {\n StorageSlot.getStringSlot(store).value = value;\n return ShortString.wrap(FALLBACK_SENTINEL);\n }\n }\n\n /**\n * @dev Decode a string that was encoded to `ShortString` or written to storage using {toShortStringWithFallback}.\n */\n function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {\n if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {\n return toString(value);\n } else {\n return store;\n }\n }\n\n /**\n * @dev Return the length of a string that was encoded to `ShortString` or written to storage using\n * {toShortStringWithFallback}.\n *\n * WARNING: This will return the \"byte length\" of the string. This may not reflect the actual length in terms of\n * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.\n */\n function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {\n if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {\n return byteLength(value);\n } else {\n return bytes(store).length;\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/StorageSlot.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)\n// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC-1967 implementation slot:\n * ```solidity\n * contract ERC1967 {\n * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(newImplementation.code.length > 0);\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * TIP: Consider using this library along with {SlotDerivation}.\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n struct Int256Slot {\n int256 value;\n }\n\n struct StringSlot {\n string value;\n }\n\n struct BytesSlot {\n bytes value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Int256Slot` with member `value` located at `slot`.\n */\n function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `StringSlot` with member `value` located at `slot`.\n */\n function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `StringSlot` representation of the string storage pointer `store`.\n */\n function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := store.slot\n }\n }\n\n /**\n * @dev Returns a `BytesSlot` with member `value` located at `slot`.\n */\n function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.\n */\n function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := store.slot\n }\n }\n}\n" - }, - "npm/@openzeppelin/contracts@5.3.0/utils/Strings.sol": { - "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol)\n\npragma solidity ^0.8.20;\n\nimport {Math} from \"./math/Math.sol\";\nimport {SafeCast} from \"./math/SafeCast.sol\";\nimport {SignedMath} from \"./math/SignedMath.sol\";\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n using SafeCast for *;\n\n bytes16 private constant HEX_DIGITS = \"0123456789abcdef\";\n uint8 private constant ADDRESS_LENGTH = 20;\n uint256 private constant SPECIAL_CHARS_LOOKUP =\n (1 << 0x08) | // backspace\n (1 << 0x09) | // tab\n (1 << 0x0a) | // newline\n (1 << 0x0c) | // form feed\n (1 << 0x0d) | // carriage return\n (1 << 0x22) | // double quote\n (1 << 0x5c); // backslash\n\n /**\n * @dev The `value` string doesn't fit in the specified `length`.\n */\n error StringsInsufficientHexLength(uint256 value, uint256 length);\n\n /**\n * @dev The string being parsed contains characters that are not in scope of the given base.\n */\n error StringsInvalidChar();\n\n /**\n * @dev The string being parsed is not a properly formatted address.\n */\n error StringsInvalidAddressFormat();\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = Math.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n assembly (\"memory-safe\") {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n assembly (\"memory-safe\") {\n mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `int256` to its ASCII `string` decimal representation.\n */\n function toStringSigned(int256 value) internal pure returns (string memory) {\n return string.concat(value < 0 ? \"-\" : \"\", toString(SignedMath.abs(value)));\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, Math.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n uint256 localValue = value;\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = HEX_DIGITS[localValue & 0xf];\n localValue >>= 4;\n }\n if (localValue != 0) {\n revert StringsInsufficientHexLength(value, length);\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal\n * representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal\n * representation, according to EIP-55.\n */\n function toChecksumHexString(address addr) internal pure returns (string memory) {\n bytes memory buffer = bytes(toHexString(addr));\n\n // hash the hex part of buffer (skip length + 2 bytes, length 40)\n uint256 hashValue;\n assembly (\"memory-safe\") {\n hashValue := shr(96, keccak256(add(buffer, 0x22), 40))\n }\n\n for (uint256 i = 41; i > 1; --i) {\n // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)\n if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {\n // case shift by xoring with 0x20\n buffer[i] ^= 0x20;\n }\n hashValue >>= 4;\n }\n return string(buffer);\n }\n\n /**\n * @dev Returns true if the two strings are equal.\n */\n function equal(string memory a, string memory b) internal pure returns (bool) {\n return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));\n }\n\n /**\n * @dev Parse a decimal string and returns the value as a `uint256`.\n *\n * Requirements:\n * - The string must be formatted as `[0-9]*`\n * - The result must fit into an `uint256` type\n */\n function parseUint(string memory input) internal pure returns (uint256) {\n return parseUint(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `[0-9]*`\n * - The result must fit into an `uint256` type\n */\n function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {\n (bool success, uint256 value) = tryParseUint(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {\n return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid\n * character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseUint(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, uint256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseUintUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseUintUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, uint256 value) {\n bytes memory buffer = bytes(input);\n\n uint256 result = 0;\n for (uint256 i = begin; i < end; ++i) {\n uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));\n if (chr > 9) return (false, 0);\n result *= 10;\n result += chr;\n }\n return (true, result);\n }\n\n /**\n * @dev Parse a decimal string and returns the value as a `int256`.\n *\n * Requirements:\n * - The string must be formatted as `[-+]?[0-9]*`\n * - The result must fit in an `int256` type.\n */\n function parseInt(string memory input) internal pure returns (int256) {\n return parseInt(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `[-+]?[0-9]*`\n * - The result must fit in an `int256` type.\n */\n function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {\n (bool success, int256 value) = tryParseInt(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if\n * the result does not fit in a `int256`.\n *\n * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.\n */\n function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {\n return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);\n }\n\n uint256 private constant ABS_MIN_INT256 = 2 ** 255;\n\n /**\n * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid\n * character or if the result does not fit in a `int256`.\n *\n * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.\n */\n function tryParseInt(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, int256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseIntUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseIntUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, int256 value) {\n bytes memory buffer = bytes(input);\n\n // Check presence of a negative sign.\n bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n bool positiveSign = sign == bytes1(\"+\");\n bool negativeSign = sign == bytes1(\"-\");\n uint256 offset = (positiveSign || negativeSign).toUint();\n\n (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);\n\n if (absSuccess && absValue < ABS_MIN_INT256) {\n return (true, negativeSign ? -int256(absValue) : int256(absValue));\n } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {\n return (true, type(int256).min);\n } else return (false, 0);\n }\n\n /**\n * @dev Parse a hexadecimal string (with or without \"0x\" prefix), and returns the value as a `uint256`.\n *\n * Requirements:\n * - The string must be formatted as `(0x)?[0-9a-fA-F]*`\n * - The result must fit in an `uint256` type.\n */\n function parseHexUint(string memory input) internal pure returns (uint256) {\n return parseHexUint(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `(0x)?[0-9a-fA-F]*`\n * - The result must fit in an `uint256` type.\n */\n function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {\n (bool success, uint256 value) = tryParseHexUint(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {\n return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an\n * invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseHexUint(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, uint256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseHexUintUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseHexUintUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, uint256 value) {\n bytes memory buffer = bytes(input);\n\n // skip 0x prefix if present\n bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2(\"0x\"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n uint256 offset = hasPrefix.toUint() * 2;\n\n uint256 result = 0;\n for (uint256 i = begin + offset; i < end; ++i) {\n uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));\n if (chr > 15) return (false, 0);\n result *= 16;\n unchecked {\n // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).\n // This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.\n result += chr;\n }\n }\n return (true, result);\n }\n\n /**\n * @dev Parse a hexadecimal string (with or without \"0x\" prefix), and returns the value as an `address`.\n *\n * Requirements:\n * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`\n */\n function parseAddress(string memory input) internal pure returns (address) {\n return parseAddress(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`\n */\n function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {\n (bool success, address value) = tryParseAddress(input, begin, end);\n if (!success) revert StringsInvalidAddressFormat();\n return value;\n }\n\n /**\n * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly\n * formatted address. See {parseAddress-string} requirements.\n */\n function tryParseAddress(string memory input) internal pure returns (bool success, address value) {\n return tryParseAddress(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly\n * formatted address. See {parseAddress-string-uint256-uint256} requirements.\n */\n function tryParseAddress(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, address value) {\n if (end > bytes(input).length || begin > end) return (false, address(0));\n\n bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2(\"0x\"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n uint256 expectedLength = 40 + hasPrefix.toUint() * 2;\n\n // check that input is the correct length\n if (end - begin == expectedLength) {\n // length guarantees that this does not overflow, and value is at most type(uint160).max\n (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);\n return (s, address(uint160(v)));\n } else {\n return (false, address(0));\n }\n }\n\n function _tryParseChr(bytes1 chr) private pure returns (uint8) {\n uint8 value = uint8(chr);\n\n // Try to parse `chr`:\n // - Case 1: [0-9]\n // - Case 2: [a-f]\n // - Case 3: [A-F]\n // - otherwise not supported\n unchecked {\n if (value > 47 && value < 58) value -= 48;\n else if (value > 96 && value < 103) value -= 87;\n else if (value > 64 && value < 71) value -= 55;\n else return type(uint8).max;\n }\n\n return value;\n }\n\n /**\n * @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.\n *\n * WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.\n *\n * NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of\n * RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode\n * characters that are not in this range, but other tooling may provide different results.\n */\n function escapeJSON(string memory input) internal pure returns (string memory) {\n bytes memory buffer = bytes(input);\n bytes memory output = new bytes(2 * buffer.length); // worst case scenario\n uint256 outputLength = 0;\n\n for (uint256 i; i < buffer.length; ++i) {\n bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));\n if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {\n output[outputLength++] = \"\\\\\";\n if (char == 0x08) output[outputLength++] = \"b\";\n else if (char == 0x09) output[outputLength++] = \"t\";\n else if (char == 0x0a) output[outputLength++] = \"n\";\n else if (char == 0x0c) output[outputLength++] = \"f\";\n else if (char == 0x0d) output[outputLength++] = \"r\";\n else if (char == 0x5c) output[outputLength++] = \"\\\\\";\n else if (char == 0x22) {\n // solhint-disable-next-line quotes\n output[outputLength++] = '\"';\n }\n } else {\n output[outputLength++] = char;\n }\n }\n // write the actual length and deallocate unused memory\n assembly (\"memory-safe\") {\n mstore(output, outputLength)\n mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))\n }\n\n return string(output);\n }\n\n /**\n * @dev Reads a bytes32 from a bytes array without bounds checking.\n *\n * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the\n * assembly block as such would prevent some optimizations.\n */\n function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {\n // This is not memory safe in the general case, but all calls to this private function are within bounds.\n assembly (\"memory-safe\") {\n value := mload(add(buffer, add(0x20, offset)))\n }\n }\n}\n" - }, - "project/contracts/Controller.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/access/Ownable.sol';\n\nimport './interfaces/IController.sol';\n\n/**\n * @title Controller\n * @dev Manages allow lists for solvers, executors, proposal signers and validators\n */\ncontract Controller is IController, Ownable {\n // List of allowed solvers\n mapping (address => bool) public override isSolverAllowed;\n\n // List of allowed executors\n mapping (address => bool) public override isExecutorAllowed;\n\n // List of allowed proposal signers\n mapping (address => bool) public override isProposalSignerAllowed;\n\n // List of allowed validators\n mapping (address => bool) public override isValidatorAllowed;\n\n // Minimum number of validations allowed\n uint8 public override minValidations;\n\n /**\n * @dev Creates a new Controller contract\n * @param owner Address that will own the contract\n * @param solvers List of allowed solvers\n * @param executors List of allowed executors\n * @param proposalSigners List of allowed proposal signers\n * @param validators List of allowed validators\n * @param _minValidations Minimum number of validations allowed\n */\n constructor(\n address owner,\n address[] memory solvers,\n address[] memory executors,\n address[] memory proposalSigners,\n address[] memory validators,\n uint8 _minValidations\n ) Ownable(owner) {\n for (uint256 i = 0; i < solvers.length; i++) _setAllowedSolver(solvers[i], true);\n for (uint256 i = 0; i < executors.length; i++) _setAllowedExecutor(executors[i], true);\n for (uint256 i = 0; i < proposalSigners.length; i++) _setAllowedProposalSigner(proposalSigners[i], true);\n for (uint256 i = 0; i < validators.length; i++) _setAllowedValidator(validators[i], true);\n _setMinValidations(_minValidations);\n }\n\n /**\n * @dev Sets permissions for multiple solvers\n * @param solvers List of solver addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedSolvers(address[] memory solvers, bool[] memory alloweds) external override onlyOwner {\n if (solvers.length != alloweds.length) revert ControllerInputInvalidLength();\n for (uint256 i = 0; i < solvers.length; i++) _setAllowedSolver(solvers[i], alloweds[i]);\n }\n\n /**\n * @dev Sets permissions for multiple executors\n * @param executors List of executor addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedExecutors(address[] memory executors, bool[] memory alloweds) external override onlyOwner {\n if (executors.length != alloweds.length) revert ControllerInputInvalidLength();\n for (uint256 i = 0; i < executors.length; i++) _setAllowedExecutor(executors[i], alloweds[i]);\n }\n\n /**\n * @dev Sets permissions for multiple proposal signers\n * @param signers List of proposal signer addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedProposalSigners(address[] memory signers, bool[] memory alloweds) external override onlyOwner {\n if (signers.length != alloweds.length) revert ControllerInputInvalidLength();\n for (uint256 i = 0; i < signers.length; i++) _setAllowedProposalSigner(signers[i], alloweds[i]);\n }\n\n /**\n * @dev Sets permissions for multiple validators\n * @param validators List of validator addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedValidators(address[] memory validators, bool[] memory alloweds) external override onlyOwner {\n if (validators.length != alloweds.length) revert ControllerInputInvalidLength();\n for (uint256 i = 0; i < validators.length; i++) _setAllowedValidator(validators[i], alloweds[i]);\n }\n\n /**\n * @dev Sets the minimum number of validations allowed\n * @param newMinValidations minimum number of validations allowed\n */\n function setMinValidations(uint8 newMinValidations) external override onlyOwner {\n _setMinValidations(newMinValidations);\n }\n\n /**\n * @dev Sets a solver permission\n */\n function _setAllowedSolver(address solver, bool allowed) internal {\n isSolverAllowed[solver] = allowed;\n emit SolverAllowedSet(solver, allowed);\n }\n\n /**\n * @dev Sets an executor permission\n */\n function _setAllowedExecutor(address executor, bool allowed) internal {\n isExecutorAllowed[executor] = allowed;\n emit ExecutorAllowedSet(executor, allowed);\n }\n\n /**\n * @dev Sets a proposal signer permission\n */\n function _setAllowedProposalSigner(address signer, bool allowed) internal {\n isProposalSignerAllowed[signer] = allowed;\n emit ProposalSignerAllowedSet(signer, allowed);\n }\n\n /**\n * @dev Sets a validator permission\n */\n function _setAllowedValidator(address validator, bool allowed) internal {\n isValidatorAllowed[validator] = allowed;\n emit ValidatorAllowedSet(validator, allowed);\n }\n\n /**\n * @dev Sets the minimum number of validations allowed\n */\n function _setMinValidations(uint8 newMinValidations) internal {\n minValidations = newMinValidations;\n emit MinValidationSet(newMinValidations);\n }\n}\n" - }, - "project/contracts/Intents.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Enum representing the type of intent operation.\n * - Swap: Swap tokens between chains or tokens.\n * - Transfer: Transfer tokens to one or more recipients.\n * - Call: Execute arbitrary contract calls.\n */\nenum OpType {\n Swap,\n Transfer,\n Call\n}\n\n/**\n * @dev Execution structure.\n * @param intent Intent to be fulfilled.\n * @param proposal Proposal to be executed.\n * @param signature Proposal signature.\n */\nstruct Execution {\n Intent intent;\n Proposal proposal;\n bytes signature;\n}\n\n/**\n * @dev EIP-712 typed data struct representing a validator's approval of an intent.\n * @param intent The hash of the intent being validated.\n */\nstruct Validation {\n bytes32 intent;\n}\n\n/**\n * @dev General intent structure used to abstract over different intent types.\n * @param op The type of operation this intent represents.\n * @param user The originator of the intent.\n * @param settler The address responsible for executing the intent on-chain.\n * @param nonce A unique value used to prevent replay attacks and distinguish intents.\n * @param deadline The timestamp by which the intent must be executed.\n * @param data ABI-encoded data representing a specific intent type (e.g. SwapIntent, TransferIntent, CallIntent).\n * @param maxFees List of max fees the user is willing to pay for the intent.\n * @param events List of custom intent events to be emitted.\n * @param configSig The signature of the configuration that this intent belongs to\n * @param minValidations The minimum number of validator approvals required for this intent to be considered valid.\n * @param validations The list validator signatures attesting to this intent.\n */\nstruct Intent {\n uint8 op;\n address user;\n address settler;\n bytes32 nonce;\n uint256 deadline;\n bytes data;\n MaxFee[] maxFees;\n IntentEvent[] events;\n bytes configSig;\n uint256 minValidations;\n bytes[] validations;\n}\n\n/**\n * @dev Max fee representation\n * @param token Token used to pay for the execution fee.\n * @param amount Max amount of fee token to be paid for settling this intent.\n */\nstruct MaxFee {\n address token;\n uint256 amount;\n}\n\n/**\n * @dev Intent event representation.\n * @param topic Event topic to be emitted.\n * @param data Event data to be emitted.\n */\nstruct IntentEvent {\n bytes32 topic;\n bytes data;\n}\n\n/**\n * @dev Represents a swap intent between two chains.\n * @param sourceChain Chain ID where tokens will be sent from.\n * @param destinationChain Chain ID where tokens will be received.\n * @param tokensIn List of input tokens and amounts to swap.\n * @param tokensOut List of expected output tokens, minimum amounts, and recipients.\n */\nstruct SwapIntent {\n uint256 sourceChain;\n uint256 destinationChain;\n TokenIn[] tokensIn;\n TokenOut[] tokensOut;\n}\n\n/**\n * @dev Token in representation.\n * @param token Address of a token to be sent.\n * @param amount Amount of tokens to be sent.\n */\nstruct TokenIn {\n address token;\n uint256 amount;\n}\n\n/**\n * @dev Token out representation.\n * @param token Address of a token to be received.\n * @param minAmount Minimum amount of tokens to be received.\n * @param recipient Recipient address that will receive the token out.\n */\nstruct TokenOut {\n address token;\n uint256 minAmount;\n address recipient;\n}\n\n/**\n * @dev Represents a transfer intent containing multiple token transfers.\n * @param chainId Chain ID where the transfers should be executed.\n * @param transfers List of token transfers to be performed.\n */\nstruct TransferIntent {\n uint256 chainId;\n TransferData[] transfers;\n}\n\n/**\n * @dev Transfer data for a single token transfer.\n * @param token Address of the token to transfer.\n * @param amount Amount of the token to transfer.\n * @param recipient Recipient of the token transfer.\n */\nstruct TransferData {\n address token;\n uint256 amount;\n address recipient;\n}\n\n/**\n * @dev Represents a generic call intent consisting of one or more contract calls.\n * @param chainId Chain ID where the calls should be executed.\n * @param calls List of low-level contract calls to be executed.\n */\nstruct CallIntent {\n uint256 chainId;\n CallData[] calls;\n}\n\n/**\n * @dev Low-level call data for a target contract interaction.\n * @param target Target contract address.\n * @param data Calldata to be sent to the target.\n * @param value ETH value to send along with the call.\n */\nstruct CallData {\n address target;\n bytes data;\n uint256 value;\n}\n\n/**\n * @dev Generic proposal structure representing a solver’s response to an intent.\n * @param deadline Timestamp until when the proposal is valid.\n * @param data ABI-encoded proposal-specific data (e.g. SwapProposal).\n * @param fees List of fee amounts the solver requires for execution.\n */\nstruct Proposal {\n uint256 deadline;\n bytes data;\n uint256[] fees;\n}\n\n/**\n * @dev Swap proposal representation for a swap intent.\n * @param executor Address of the executor contract that should be called during intent execution.\n * @param data Arbitrary data used to call the executor contract.\n * @param amountsOut List of amounts of tokens out proposed by the solver.\n */\nstruct SwapProposal {\n address executor;\n bytes data;\n uint256[] amountsOut;\n}\n\nlibrary IntentsHelpers {\n bytes32 internal constant INTENT_TYPE_HASH =\n keccak256(\n 'Intent(uint8 op,address user,address settler,bytes32 nonce,uint256 deadline,bytes data,MaxFee[] maxFees,IntentEvent[] events,bytes configSig,uint256 minValidations)IntentEvent(bytes32 topic,bytes data)MaxFee(address token,uint256 amount)'\n );\n\n bytes32 internal constant PROPOSAL_TYPE_HASH =\n keccak256('Proposal(bytes32 intent,address solver,uint256 deadline,bytes data,uint256[] fees)');\n\n bytes32 internal constant VALIDATION_TYPE_HASH = keccak256('Validation(bytes32 intent)');\n\n bytes32 internal constant MAX_FEE_TYPE_HASH = keccak256('MaxFee(address token,uint256 amount)');\n\n bytes32 internal constant INTENT_EVENT_TYPE_HASH = keccak256('IntentEvent(bytes32 topic,bytes data)');\n\n function hash(Intent memory intent) internal pure returns (bytes32) {\n return\n keccak256(\n abi.encode(\n INTENT_TYPE_HASH,\n intent.op,\n intent.user,\n intent.settler,\n intent.nonce,\n intent.deadline,\n keccak256(intent.data),\n hash(intent.maxFees),\n hash(intent.events),\n intent.configSig,\n intent.minValidations\n )\n );\n }\n\n function hash(Proposal memory proposal, Intent memory intent, address solver) internal pure returns (bytes32) {\n return\n keccak256(\n abi.encode(\n PROPOSAL_TYPE_HASH,\n hash(intent),\n solver,\n proposal.deadline,\n keccak256(proposal.data),\n hash(proposal.fees)\n )\n );\n }\n\n function hash(MaxFee[] memory fees) internal pure returns (bytes32) {\n bytes32[] memory hashes = new bytes32[](fees.length);\n for (uint256 i = 0; i < fees.length; i++) {\n hashes[i] = keccak256(abi.encode(MAX_FEE_TYPE_HASH, fees[i].token, fees[i].amount));\n }\n return keccak256(abi.encodePacked(hashes));\n }\n\n function hash(IntentEvent[] memory events) internal pure returns (bytes32) {\n bytes32[] memory hashes = new bytes32[](events.length);\n for (uint256 i = 0; i < events.length; i++) {\n hashes[i] = keccak256(abi.encode(INTENT_EVENT_TYPE_HASH, events[i].topic, keccak256(events[i].data)));\n }\n return keccak256(abi.encodePacked(hashes));\n }\n\n function hash(uint256[] memory fees) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(fees));\n }\n\n function hash(Validation memory validation) internal pure returns (bytes32) {\n return keccak256(abi.encode(VALIDATION_TYPE_HASH, validation.intent));\n }\n}\n" - }, - "project/contracts/interfaces/IController.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @title Controller interface\n */\ninterface IController {\n /**\n * @dev The input arrays are not of equal length\n */\n error ControllerInputInvalidLength();\n\n /**\n * @dev Emitted every time a solver permission is set\n */\n event SolverAllowedSet(address indexed solver, bool allowed);\n\n /**\n * @dev Emitted every time an executor permission is set\n */\n event ExecutorAllowedSet(address indexed executor, bool allowed);\n\n /**\n * @dev Emitted every time a proposal signer permission is set\n */\n event ProposalSignerAllowedSet(address indexed proposalSigner, bool allowed);\n\n /**\n * @dev Emitted every time a validator permission is set\n */\n event ValidatorAllowedSet(address indexed validator, bool allowed);\n\n /**\n * @dev Emitted when the minimum validations changes\n */\n event MinValidationSet(uint8 indexed newMinValidation);\n\n /**\n * @dev Tells whether a solver is allowed\n * @param solver Address of the solver being queried\n */\n function isSolverAllowed(address solver) external view returns (bool);\n\n /**\n * @dev Tells whether an executor is allowed\n * @param executor Address of the executor being queried\n */\n function isExecutorAllowed(address executor) external view returns (bool);\n\n /**\n * @dev Tells whether a proposal signer is allowed\n * @param signer Address of the proposal signer being queried\n */\n function isProposalSignerAllowed(address signer) external view returns (bool);\n\n /**\n * @dev Tells whether a validator is allowed\n * @param validator Address of the validator being queried\n */\n function isValidatorAllowed(address validator) external view returns (bool);\n\n /**\n * @dev Tells the minimum number of validations allowed\n */\n function minValidations() external view returns (uint8);\n\n /**\n * @dev Sets permissions for multiple solvers\n * @param solvers List of solver addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedSolvers(address[] memory solvers, bool[] memory alloweds) external;\n\n /**\n * @dev Sets permissions for multiple executors\n * @param executors List of executor addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedExecutors(address[] memory executors, bool[] memory alloweds) external;\n\n /**\n * @dev Sets permissions for multiple proposal signers\n * @param signers List of proposal signer addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedProposalSigners(address[] memory signers, bool[] memory alloweds) external;\n\n /**\n * @dev Sets permissions for multiple validators\n * @param validators List of validator addresses\n * @param alloweds List of permission statuses\n */\n function setAllowedValidators(address[] memory validators, bool[] memory alloweds) external;\n\n /**\n * @dev Sets the minimum number of validations allowed\n * @param newMinValidations minimum number of validations allowed\n */\n function setMinValidations(uint8 newMinValidations) external;\n}\n" - }, - "project/contracts/interfaces/ICreateX.sol": { - "content": "// SPDX-License-Identifier: AGPL-3.0-only\n\npragma solidity ^0.8.4;\n\n/**\n * @title CreateX Factory Interface Definition\n * @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/)\n * @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/)\n */\ninterface ICreateX {\n event ContractCreation(address indexed newContract);\n\n function deployCreate3(bytes32 salt, bytes memory initCode) external payable returns (address newContract);\n}\n" - }, - "project/contracts/interfaces/IExecutor.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../Intents.sol';\n\n/**\n * @title Executor interface\n */\ninterface IExecutor {\n /**\n * @dev Executes an intent proposal\n * @param intent Intent to be executed\n * @param proposal Proposal to be executed to fulfill the intent\n */\n function execute(Intent memory intent, Proposal memory proposal) external;\n}\n" - }, - "project/contracts/interfaces/IIntentsValidator.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../Intents.sol';\nimport '../safeguards/Safeguards.sol';\n\n/**\n * @title IntentsValidator\n * @dev Performs intents validations based on safeguards\n */\ninterface IIntentsValidator {\n /**\n * @dev Validates an intent for a safeguard\n * @param intent Intent to be validated\n * @param config Safeguard config to validate the intent with\n */\n function validate(Intent memory intent, bytes memory config) external pure;\n}\n" - }, - "project/contracts/interfaces/ISafe.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nenum SafeOperation {\n Call,\n DelegateCall\n}\n\ninterface ISafe {\n function getThreshold() external view returns (uint256);\n\n function execTransactionFromModuleReturnData(address to, uint256 value, bytes memory data, SafeOperation operation)\n external\n returns (bool success, bytes memory returnData);\n}\n" - }, - "project/contracts/interfaces/ISettler.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../Intents.sol';\nimport '../safeguards/Safeguards.sol';\n\n/**\n * @title Settler interface\n */\ninterface ISettler {\n /**\n * @dev The requested intent type is unknown\n */\n error SettlerUnknownIntentType(uint8 op);\n\n /**\n * @dev The simulation has been successful\n */\n error SettlerSimulationSuccess(uint256 gasUsed);\n\n /**\n * @dev The solver is not allowed\n */\n error SettlerSolverNotAllowed(address solver);\n\n /**\n * @dev The executor is not allowed\n */\n error SettlerExecutorNotAllowed(address executor);\n\n /**\n * @dev The proposal signer is not allowed\n */\n error SettlerProposalSignerNotAllowed(address signer);\n\n /**\n * @dev The validator is not allowed\n */\n error SettlerValidatorNotAllowed(address validator);\n\n /**\n * @dev The validator is duplicated\n */\n error SettlerValidatorDuplicatedOrUnsorted(address previous, address current);\n\n /**\n * @dev The settler is not the current contract\n */\n error SettlerInvalidSettler(address settler);\n\n /**\n * @dev The nonce is zero\n */\n error SettlerNonceZero();\n\n /**\n * @dev The nonce has already been used for the user\n */\n error SettlerNonceAlreadyUsed(address user, bytes32 nonce);\n\n /**\n * @dev The intent deadline is in the past\n */\n error SettlerIntentPastDeadline(uint256 deadline, uint256 timestamp);\n\n /**\n * @dev The current chain is not valid\n */\n error SettlerInvalidChain(uint256 chainId);\n\n /**\n * @dev The recipient is the settler contract\n */\n error SettlerInvalidRecipient(address to);\n\n /**\n * @dev The user is not a smart account\n */\n error SettlerUserNotSmartAccount(address user);\n\n /**\n * @dev The amount out is lower than the proposed amount\n */\n error SettlerAmountOutLtProposed(uint256 index, uint256 amountOut, uint256 proposed);\n\n /**\n * @dev The proposed amount is lower than the minimum amount\n */\n error SettlerProposedAmountLtMinAmount(uint256 index, uint256 proposed, uint256 minAmount);\n\n /**\n * @dev The proposed amounts array and the tokens out array are not of equal length\n */\n error SettlerInvalidProposedAmounts();\n\n /**\n * @dev The balance after the proposal execution is lower than the balance before\n */\n error SettlerPostBalanceOutLtPre(uint256 index, uint256 post, uint256 pre);\n\n /**\n * @dev The solver fees length does not match the requested by the user\n */\n error SettlerSolverFeeInvalidLength();\n\n /**\n * @dev The solver fee is too high\n */\n error SettlerSolverFeeTooHigh(uint256 fee, uint256 max);\n\n /**\n * @dev The intent validations are not enough\n */\n error SettlerIntentValidationsNotEnough(uint256 min, uint256 current);\n\n /**\n * @dev The proposal deadline is in the past\n */\n error SettlerProposalPastDeadline(uint256 deadline, uint256 timestamp);\n\n /**\n * @dev The proposal data is not empty\n */\n error SettlerProposalDataNotEmpty();\n\n /**\n * @dev The rescue funds recipient is zero\n */\n error SettlerRescueFundsRecipientZero();\n\n /**\n * @dev The list of safeguards exceed the maximum allowed\n */\n error SettlerTooManySafeguards(uint256 lengthRequested);\n\n /**\n * @dev The new smart accounts handler is zero\n */\n error SmartAccountsHandlerZero();\n\n /**\n * @dev Custom events emitted for each intent\n */\n event IntentExecuted(\n address indexed user,\n bytes32 indexed topic,\n uint8 indexed op,\n Intent intent,\n Proposal proposal,\n bytes output,\n bytes data\n );\n\n /**\n * @dev Emitted every time an intent is fulfilled\n */\n event ProposalExecuted(bytes32 indexed proposal, uint256 index);\n\n /**\n * @dev Emitted every time tokens are withdrawn from the contract balance\n */\n event FundsRescued(address indexed token, address indexed recipient, uint256 amount);\n\n /**\n * @dev Emitted every time the smart accounts handler is set\n */\n event SmartAccountsHandlerSet(address indexed smartAccountsHandler);\n\n /**\n * @dev Emitted every time the intents validator is set\n */\n event IntentsValidatorSet(address indexed intentsValidator);\n\n /**\n * @dev Emitted every time a safeguard is set\n */\n event SafeguardSet(address indexed user);\n\n /**\n * @dev Tells the reference to the Mimic controller\n */\n function controller() external view returns (address);\n\n /**\n * @dev Tells the reference to the smart accounts handler\n */\n function smartAccountsHandler() external view returns (address);\n\n /**\n * @dev Tells the reference to the intents validator\n */\n function intentsValidator() external view returns (address);\n\n /**\n * @dev Tells the block at which a user nonce was used. Returns 0 if unused.\n * @param user Address of the user being queried\n * @param nonce Nonce being queried\n */\n function getNonceBlock(address user, bytes32 nonce) external view returns (uint256);\n\n /**\n * @dev Tells the safeguard set for a user\n * @param user Address of the user being queried\n */\n function getUserSafeguard(address user) external view returns (bytes memory);\n\n /**\n * @dev Tells the hash of an intent\n * @param intent Intent to get the hash of\n */\n function getIntentHash(Intent memory intent) external pure returns (bytes32);\n\n /**\n * @dev Tells the hash of a proposal\n * @param proposal Proposal to be hashed\n * @param intent Intent being fulfilled by the requested proposal\n * @param solver Address of the solver that made the proposal\n */\n function getProposalHash(Proposal memory proposal, Intent memory intent, address solver)\n external\n pure\n returns (bytes32);\n\n /**\n * @dev Withdraws ERC20 or native tokens from the contract\n * @param token Address of the token to be withdrawn\n * @param recipient Address of the account receiving the tokens\n * @param amount Amount of tokens to be withdrawn\n */\n function rescueFunds(address token, address recipient, uint256 amount) external;\n\n /**\n * @dev Sets a new smart accounts handler\n * @param newSmartAccountsHandler New smart accounts handler to be set\n */\n function setSmartAccountsHandler(address newSmartAccountsHandler) external;\n\n /**\n * @dev Sets a new intents validator address\n * @param newIntentsValidator New intents validator to be set\n */\n function setIntentsValidator(address newIntentsValidator) external;\n\n /**\n * @dev Sets a safeguard for a user\n * @param safeguard Safeguard to be set\n */\n function setSafeguard(bytes memory safeguard) external;\n\n /**\n * @dev Executes a proposal to fulfill an intent\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n */\n function execute(Execution[] memory executions) external;\n\n /**\n * @dev Simulates an execution. It will always revert. Successful executions are returned as\n * `SettlerSimulationSuccess` errors. Any other error should be treated as failure.\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n */\n function simulate(Execution[] memory executions) external;\n}\n" - }, - "project/contracts/interfaces/ISmartAccount.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/interfaces/IERC1271.sol';\nimport '@openzeppelin/contracts/utils/introspection/IERC165.sol';\n\n/**\n * @title SmartAccount interface\n */\ninterface ISmartAccount is IERC165, IERC1271 {\n /**\n * @dev Emitted every time tokens are transferred\n */\n event Transferred(address indexed token, address indexed recipient, uint256 amount);\n\n /**\n * @dev Emitted every time `call` is called\n */\n event Called(address indexed target, bytes data, uint256 value, bytes result);\n\n /**\n * @dev Transfers ERC20 or native tokens to the recipient. Sender must be the owner or the settler.\n * @param token Address of the token to be withdrawn\n * @param recipient Address of the account receiving the tokens\n * @param amount Amount of tokens to be withdrawn\n */\n function transfer(address token, address recipient, uint256 amount) external;\n\n /**\n * @dev Executes an arbitrary call from the contract. Sender must be the owner or the settler.\n * @param target Address where the call will be sent\n * @param data Calldata to be sent to the target\n * @param value Native token value to send along with the call\n */\n function call(address target, bytes memory data, uint256 value) external returns (bytes memory result);\n}\n" - }, - "project/contracts/interfaces/ISmartAccountsHandler.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\ninterface ISmartAccountsHandler {\n /**\n * @dev The smart account given is not supported\n */\n error SmartAccountsHandlerUnsupportedAccount(address account);\n\n /**\n * @dev Tells whether an account is a supported smart account\n * @param account Address of the account being queried\n */\n function isSmartAccount(address account) external view returns (bool);\n\n /**\n * @dev Performs a transfer from a smart account\n */\n function transfer(address account, address token, address to, uint256 amount) external;\n\n /**\n * @dev Performs a call from a smart account\n */\n function call(address account, address target, bytes memory data, uint256 value) external returns (bytes memory);\n}\n" - }, - "project/contracts/safeguards/BaseIntentsValidator.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './Safeguards.sol';\nimport '../Intents.sol';\n\n/**\n * @title BaseIntentsValidator\n */\ncontract BaseIntentsValidator {\n /**\n * @dev No intents allowed\n */\n error IntentsValidatorNoneAllowed();\n\n /**\n * @dev Intent type unknown\n */\n error IntentsValidatorUnknownIntentType(uint8 opType);\n\n /**\n * @dev Invalid safeguard mode\n */\n error IntentsValidatorInvalidSafeguardMode(uint8 mode);\n\n /**\n * @dev Tells whether a chain is allowed\n */\n function _isChainAllowed(uint256 chainId, bytes memory config) internal pure returns (bool) {\n (bool isDenyList, uint256[] memory values) = abi.decode(config, (bool, uint256[]));\n if (isDenyList) {\n for (uint256 i = 0; i < values.length; i++) {\n if (chainId == values[i]) return false;\n }\n return true;\n } else {\n for (uint256 i = 0; i < values.length; i++) {\n if (chainId == values[i]) return true;\n }\n return false;\n }\n }\n\n /**\n * @dev Tells whether an account is allowed\n */\n function _isAccountAllowed(address account, bytes memory config) internal pure returns (bool) {\n (bool isDenyList, address[] memory values) = abi.decode(config, (bool, address[]));\n if (isDenyList) {\n for (uint256 i = 0; i < values.length; i++) {\n if (account == values[i]) return false;\n }\n return true;\n } else {\n for (uint256 i = 0; i < values.length; i++) {\n if (account == values[i]) return true;\n }\n return false;\n }\n }\n\n /**\n * @dev Tells whether a selector is allowed\n */\n function _isSelectorAllowed(bytes4 selector, bytes memory config) internal pure returns (bool) {\n (bool isDenyList, bytes4[] memory values) = abi.decode(config, (bool, bytes4[]));\n if (isDenyList) {\n for (uint256 i = 0; i < values.length; i++) {\n if (selector == values[i]) return false;\n }\n return true;\n } else {\n for (uint256 i = 0; i < values.length; i++) {\n if (selector == values[i]) return true;\n }\n return false;\n }\n }\n}\n" - }, - "project/contracts/safeguards/CallIntentsValidator.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './Safeguards.sol';\nimport './BaseIntentsValidator.sol';\nimport '../Intents.sol';\n\n/**\n * @dev Call safeguard modes to validate call intents\n * @param None To ensure no calls are allowed\n * @param Chain To validate that the chain where calls execute is allowed\n * @param Target To validate that the call targets (contract addresses) are allowed\n * @param Selector To validate that the function selectors being called are allowed\n */\nenum CallSafeguardMode {\n None,\n Chain,\n Target,\n Selector\n}\n\n/**\n * @title CallIntentsValidator\n * @dev Performs call intents validations based on safeguards\n */\ncontract CallIntentsValidator is BaseIntentsValidator {\n /**\n * @dev Tells whether a call intent is valid for a safeguard\n * @param intent Call intent to be validated\n * @param safeguard Safeguard to validate the intent with\n */\n function _isCallIntentValid(Intent memory intent, Safeguard memory safeguard) internal pure returns (bool) {\n CallIntent memory callIntent = abi.decode(intent.data, (CallIntent));\n if (safeguard.mode == uint8(CallSafeguardMode.Chain))\n return _isChainAllowed(callIntent.chainId, safeguard.config);\n if (safeguard.mode == uint8(CallSafeguardMode.Target))\n return _areCallTargetsValid(callIntent.calls, safeguard.config);\n if (safeguard.mode == uint8(CallSafeguardMode.Selector))\n return _areCallSelectorsValid(callIntent.calls, safeguard.config);\n revert IntentsValidatorInvalidSafeguardMode(safeguard.mode);\n }\n\n /**\n * @dev Tells whether the call targets are allowed\n */\n function _areCallTargetsValid(CallData[] memory calls, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < calls.length; i++) {\n if (!_isAccountAllowed(calls[i].target, config)) return false;\n }\n return true;\n }\n\n /**\n * @dev Tells whether the call selectors are allowed\n */\n function _areCallSelectorsValid(CallData[] memory calls, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < calls.length; i++) {\n if (!_isSelectorAllowed(bytes4(calls[i].data), config)) return false;\n }\n return true;\n }\n}\n" - }, - "project/contracts/safeguards/IntentsValidator.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './CallIntentsValidator.sol';\nimport './TransferIntentsValidator.sol';\nimport './Safeguards.sol';\nimport './SwapIntentsValidator.sol';\nimport '../Intents.sol';\nimport '../interfaces/IIntentsValidator.sol';\n\n/**\n * @title IntentsValidator\n * @dev Performs intents validations based on safeguards\n */\ncontract IntentsValidator is IIntentsValidator, SwapIntentsValidator, TransferIntentsValidator, CallIntentsValidator {\n /**\n * @dev Safeguard validation failed\n */\n error IntentsValidatorSafeguardFailed();\n\n /**\n * @dev Invalid safeguard config mode\n */\n error IntentsValidatorInvalidSafeguardConfigMode(uint8 mode);\n\n /**\n * @dev Invalid safeguard group logic mode\n */\n error IntentsValidatorInvalidSafeguardGroupLogicMode(uint8 mode);\n\n /**\n * @dev Tells whether an intent is valid for a safeguard\n * @param intent Intent to be validated\n * @param config Safeguard config to validate the intent with\n */\n function validate(Intent memory intent, bytes memory config) external pure override {\n (uint8 mode, bytes memory safeguard) = abi.decode(config, (uint8, bytes));\n if (mode == uint8(SafeguardConfigMode.List)) _validate(intent, abi.decode(safeguard, (Safeguard[])));\n else if (mode == uint8(SafeguardConfigMode.Tree)) _validate(intent, _decodeSafeguardTree(safeguard));\n else revert IntentsValidatorInvalidSafeguardConfigMode(mode);\n }\n\n /**\n * @dev Tells whether an intent is valid for a safeguards list\n * @param intent Intent to be validated\n * @param safeguards Safeguard list to validate the intent with\n */\n function _validate(Intent memory intent, Safeguard[] memory safeguards) internal pure {\n if (safeguards.length == 0) revert IntentsValidatorSafeguardFailed();\n for (uint256 i = 0; i < safeguards.length; i++) {\n if (!_isSafeguardValid(intent, safeguards[i])) revert IntentsValidatorSafeguardFailed();\n }\n }\n\n /**\n * @dev Tells whether an intent is valid for a safeguard tree\n * @param intent Intent to be validated\n * @param tree Safeguard tree to validate the intent with\n */\n function _validate(Intent memory intent, SafeguardTree memory tree) internal pure {\n if (tree.nodes.length == 0) revert IntentsValidatorSafeguardFailed();\n if (!_isSafeguardGroupValid(intent, tree, 0)) revert IntentsValidatorSafeguardFailed();\n }\n\n /**\n * @dev Tells whether an intent is valid for a safeguard tree at a certain level\n * @param intent Intent to be validated\n * @param tree Safeguard tree to validate the intent with\n * @param index Index of the group node to evaluate\n */\n function _isSafeguardGroupValid(Intent memory intent, SafeguardTree memory tree, uint16 index)\n internal\n pure\n returns (bool)\n {\n SafeguardGroup memory group = tree.nodes[index];\n\n if (group.logic == uint8(SafeguardGroupLogic.NOT)) {\n for (uint256 i = 0; i < group.leaves.length; i++) {\n if (_isSafeguardValid(intent, tree.leaves[group.leaves[i]])) return false;\n }\n for (uint256 i = 0; i < group.children.length; i++) {\n if (_isSafeguardGroupValid(intent, tree, group.children[i])) return false;\n }\n return true;\n }\n\n if (group.logic == uint8(SafeguardGroupLogic.AND)) {\n for (uint256 i = 0; i < group.leaves.length; i++) {\n if (!_isSafeguardValid(intent, tree.leaves[group.leaves[i]])) return false;\n }\n for (uint256 i = 0; i < group.children.length; i++) {\n if (!_isSafeguardGroupValid(intent, tree, group.children[i])) return false;\n }\n return true;\n }\n\n if (group.logic == uint8(SafeguardGroupLogic.OR)) {\n for (uint256 i = 0; i < group.leaves.length; i++) {\n if (_isSafeguardValid(intent, tree.leaves[group.leaves[i]])) return true;\n }\n for (uint256 i = 0; i < group.children.length; i++) {\n if (_isSafeguardGroupValid(intent, tree, group.children[i])) return true;\n }\n return false;\n }\n\n if (group.logic == uint8(SafeguardGroupLogic.XOR)) {\n uint256 hits = 0;\n for (uint256 i = 0; i < group.leaves.length; i++) {\n if (_isSafeguardValid(intent, tree.leaves[group.leaves[i]]))\n if (++hits > 1) return false;\n }\n for (uint256 i = 0; i < group.children.length; i++) {\n if (_isSafeguardGroupValid(intent, tree, group.children[i]))\n if (++hits > 1) return false;\n }\n return hits == 1;\n }\n\n revert IntentsValidatorInvalidSafeguardGroupLogicMode(group.logic);\n }\n\n /**\n * @dev Tells whether an intent is valid for a safeguard\n * @param intent Intent to be validated\n * @param safeguard Safeguard to validate the intent with\n */\n function _isSafeguardValid(Intent memory intent, Safeguard memory safeguard) internal pure returns (bool) {\n if (safeguard.mode == uint8(0)) revert IntentsValidatorNoneAllowed();\n if (intent.op == uint8(OpType.Swap)) return _isSwapIntentValid(intent, safeguard);\n if (intent.op == uint8(OpType.Transfer)) return _isTransferIntentValid(intent, safeguard);\n if (intent.op == uint8(OpType.Call)) return _isCallIntentValid(intent, safeguard);\n revert IntentsValidatorUnknownIntentType(uint8(intent.op));\n }\n\n /**\n * @dev Safely decodes a safeguard tree avoiding compiler issues with dynamic arrays\n * @param data Safeguard tree data to be decoded\n */\n function _decodeSafeguardTree(bytes memory data) private pure returns (SafeguardTree memory) {\n (SafeguardGroup[] memory nodes, Safeguard[] memory leaves) = abi.decode(data, (SafeguardGroup[], Safeguard[]));\n return SafeguardTree(nodes, leaves);\n }\n}\n" - }, - "project/contracts/safeguards/Safeguards.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Safeguard config modes\n * - List: Safeguard lists\n * - Tree: Safeguard groups\n */\nenum SafeguardConfigMode {\n List,\n Tree\n}\n\n/**\n * @dev Logical operators for safeguard groups\n * - AND: every child must pass\n * - OR: at least one child must pass\n * - XOR: exactly one child must pass\n * - NOT: every child must fail\n */\nenum SafeguardGroupLogic {\n AND,\n OR,\n XOR,\n NOT\n}\n\n/**\n * @dev Flat node in the safeguard tree\n * @param logic Group operator (AND/OR/XOR/NOT)\n * @param leaves Indices into `SafeguardTree.leaves`\n * @param children Indices into `SafeguardTree.nodes`\n */\nstruct SafeguardGroup {\n uint8 logic;\n uint16[] leaves;\n uint16[] children;\n}\n\n/**\n * @dev Safeguard tree representation\n * @param nodes List of all the nodes in the tree\n * @param leaves List of all the leaves in the tree\n */\nstruct SafeguardTree {\n SafeguardGroup[] nodes;\n Safeguard[] leaves;\n}\n\n/**\n * @dev Safeguard representation\n * @param mode Safeguard mode\n * @param config Safeguard configuration settings or parameters\n */\nstruct Safeguard {\n uint8 mode;\n bytes config;\n}\n" - }, - "project/contracts/safeguards/SwapIntentsValidator.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './BaseIntentsValidator.sol';\nimport './Safeguards.sol';\nimport '../Intents.sol';\n\n/**\n * @dev Swap safeguard modes to validate swap intents\n * @param None To ensure no swaps are allowed\n * @param SourceChain To validate that the source chain is allowed\n * @param DestinationChain To validate that the destination chain is allowed\n * @param TokenIn To validate that the tokens to be sent are allowed\n * @param TokenOut To validate that the tokens to be received are allowed\n * @param Recipient To validate that the recipients that will receive the tokens are allowed\n */\nenum SwapSafeguardMode {\n None,\n SourceChain,\n DestinationChain,\n TokenIn,\n TokenOut,\n Recipient\n}\n\n/**\n * @title SwapIntentsValidator\n * @dev Performs swap intents validations based on safeguards\n */\ncontract SwapIntentsValidator is BaseIntentsValidator {\n /**\n * @dev Tells whether a swap intent is valid for a safeguard\n * @param intent Swap intent to be validated\n * @param safeguard Safeguard to validate the intent with\n */\n function _isSwapIntentValid(Intent memory intent, Safeguard memory safeguard) internal pure returns (bool) {\n SwapIntent memory swapIntent = abi.decode(intent.data, (SwapIntent));\n if (safeguard.mode == uint8(SwapSafeguardMode.SourceChain))\n return _isChainAllowed(swapIntent.sourceChain, safeguard.config);\n if (safeguard.mode == uint8(SwapSafeguardMode.DestinationChain))\n return _isChainAllowed(swapIntent.destinationChain, safeguard.config);\n if (safeguard.mode == uint8(SwapSafeguardMode.TokenIn))\n return _areSwapTokensInValid(swapIntent.tokensIn, safeguard.config);\n if (safeguard.mode == uint8(SwapSafeguardMode.TokenOut))\n return _areSwapTokensOutValid(swapIntent.tokensOut, safeguard.config);\n if (safeguard.mode == uint8(SwapSafeguardMode.Recipient))\n return _areSwapRecipientsValid(swapIntent.tokensOut, safeguard.config);\n revert IntentsValidatorInvalidSafeguardMode(safeguard.mode);\n }\n\n /**\n * @dev Tells whether the tokens to be sent are allowed\n */\n function _areSwapTokensInValid(TokenIn[] memory tokensIn, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < tokensIn.length; i++) {\n if (!_isAccountAllowed(tokensIn[i].token, config)) return false;\n }\n return true;\n }\n\n /**\n * @dev Tells whether the tokens to be received are allowed\n */\n function _areSwapTokensOutValid(TokenOut[] memory tokensOut, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < tokensOut.length; i++) {\n if (!_isAccountAllowed(tokensOut[i].token, config)) return false;\n }\n return true;\n }\n\n /**\n * @dev Tells whether the recipients to be received are allowed\n */\n function _areSwapRecipientsValid(TokenOut[] memory tokensOut, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < tokensOut.length; i++) {\n if (!_isAccountAllowed(tokensOut[i].recipient, config)) return false;\n }\n return true;\n }\n}\n" - }, - "project/contracts/safeguards/TransferIntentsValidator.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport './BaseIntentsValidator.sol';\nimport './Safeguards.sol';\nimport '../Intents.sol';\n\n/**\n * @dev Transfer safeguard modes to validate transfer intents\n * @param None To ensure no transfers are allowed\n * @param Chain To validate that the chain where transfers execute is allowed\n * @param Token To validate that the tokens being transferred are allowed\n * @param Recipient To validate that the recipients of the transfers are allowed\n */\nenum TransferSafeguardMode {\n None,\n Chain,\n Token,\n Recipient\n}\n\n/**\n * @title TransferIntentsValidator\n * @dev Performs transfer intents validations based on safeguards\n */\ncontract TransferIntentsValidator is BaseIntentsValidator {\n /**\n * @dev Tells whether a transfer intent is valid for a safeguard\n * @param intent Transfer intent to be validated\n * @param safeguard Safeguard to validate the intent with\n */\n function _isTransferIntentValid(Intent memory intent, Safeguard memory safeguard) internal pure returns (bool) {\n TransferIntent memory transferIntent = abi.decode(intent.data, (TransferIntent));\n if (safeguard.mode == uint8(TransferSafeguardMode.Chain))\n return _isChainAllowed(transferIntent.chainId, safeguard.config);\n if (safeguard.mode == uint8(TransferSafeguardMode.Token))\n return _areTransferTokensValid(transferIntent.transfers, safeguard.config);\n if (safeguard.mode == uint8(TransferSafeguardMode.Recipient))\n return _areTransferRecipientsValid(transferIntent.transfers, safeguard.config);\n revert IntentsValidatorInvalidSafeguardMode(safeguard.mode);\n }\n\n /**\n * @dev Tells whether the tokens being transferred are allowed\n */\n function _areTransferTokensValid(TransferData[] memory transfers, bytes memory config) private pure returns (bool) {\n for (uint256 i = 0; i < transfers.length; i++) {\n if (!_isAccountAllowed(transfers[i].token, config)) return false;\n }\n return true;\n }\n\n /**\n * @dev Tells whether the recipients of the transfers are allowed\n */\n function _areTransferRecipientsValid(TransferData[] memory transfers, bytes memory config)\n private\n pure\n returns (bool)\n {\n for (uint256 i = 0; i < transfers.length; i++) {\n if (!_isAccountAllowed(transfers[i].recipient, config)) return false;\n }\n return true;\n }\n}\n" - }, - "project/contracts/Settler.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/access/Ownable.sol';\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport '@openzeppelin/contracts/utils/ReentrancyGuard.sol';\nimport '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';\nimport '@openzeppelin/contracts/utils/cryptography/EIP712.sol';\nimport '@openzeppelin/contracts/utils/introspection/ERC165Checker.sol';\n\nimport './Intents.sol';\nimport './interfaces/IController.sol';\nimport './interfaces/IIntentsValidator.sol';\nimport './interfaces/IExecutor.sol';\nimport './interfaces/ISettler.sol';\nimport './utils/Denominations.sol';\nimport './utils/ERC20Helpers.sol';\nimport './smart-accounts/SmartAccountsHandler.sol';\nimport './smart-accounts/SmartAccountsHandlerHelpers.sol';\n\n/**\n * @title Settler\n * @dev Contract that provides the appropriate context for solvers to execute proposals that fulfill user intents\n */\ncontract Settler is ISettler, Ownable, ReentrancyGuard, EIP712 {\n using SafeERC20 for IERC20;\n using IntentsHelpers for Intent;\n using IntentsHelpers for Proposal;\n using IntentsHelpers for Validation;\n using SmartAccountsHandlerHelpers for address;\n\n // Mimic controller reference\n // solhint-disable-next-line immutable-vars-naming\n address public immutable override controller;\n\n // Smart accounts handler reference\n address public override smartAccountsHandler;\n\n // Intents validator reference\n address public override intentsValidator;\n\n // List of block numbers at which a user nonce was used\n mapping (address => mapping (bytes32 => uint256)) public override getNonceBlock;\n\n // Safeguard config per user\n mapping (address => bytes) internal _userSafeguard;\n\n /**\n * @dev Modifier to tag settler functions in order to check if the sender is an allowed solver\n */\n modifier onlySolver() {\n address sender = _msgSender();\n if (!IController(controller).isSolverAllowed(sender)) revert SettlerSolverNotAllowed(sender);\n _;\n }\n\n /**\n * @dev Creates a new Settler contract\n * @param _controller Address of the Settler controller\n * @param _owner Address that will own the contract\n */\n constructor(address _controller, address _owner) Ownable(_owner) EIP712('Mimic Protocol Settler', '1') {\n controller = _controller;\n smartAccountsHandler = address(new SmartAccountsHandler());\n }\n\n /**\n * @dev Tells the hash of an intent\n * @param intent Intent to get the hash of\n */\n function getIntentHash(Intent memory intent) external pure override returns (bytes32) {\n return intent.hash();\n }\n\n /**\n * @dev Tells the hash of a proposal\n * @param proposal Proposal to be hashed\n * @param intent Intent being fulfilled by the requested proposal\n * @param solver Address of the solver that made the proposal\n */\n function getProposalHash(Proposal memory proposal, Intent memory intent, address solver)\n external\n pure\n override\n returns (bytes32)\n {\n return proposal.hash(intent, solver);\n }\n\n /**\n * @dev Tells the safeguard set for a user\n * @param user Address of the user being queried\n */\n function getUserSafeguard(address user) external view override returns (bytes memory) {\n return _userSafeguard[user];\n }\n\n /**\n * @dev It allows receiving native token transfers\n * Note: This method mainly allows supporting native tokens for swaps\n */\n receive() external payable {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n /**\n * @dev Withdraws ERC20 or native tokens from the contract\n * @param token Address of the token to be withdrawn\n * @param recipient Address of the account receiving the tokens\n * @param amount Amount of tokens to be withdrawn\n */\n function rescueFunds(address token, address recipient, uint256 amount) external override onlyOwner nonReentrant {\n if (recipient == address(0)) revert SettlerRescueFundsRecipientZero();\n ERC20Helpers.transfer(token, recipient, amount);\n emit FundsRescued(token, recipient, amount);\n }\n\n /**\n * @dev Sets a new smart accounts handler\n * @param newSmartAccountsHandler New smart accounts handler to be set\n */\n function setSmartAccountsHandler(address newSmartAccountsHandler) external override onlyOwner {\n _setSmartAccountsHandler(newSmartAccountsHandler);\n }\n\n /**\n * @dev Sets a new intents validator address\n * @param newIntentsValidator New intents validator to be set\n */\n function setIntentsValidator(address newIntentsValidator) external override onlyOwner {\n _setIntentsValidator(newIntentsValidator);\n }\n\n /**\n * @dev Sets a safeguard for a user\n * @param safeguard Safeguard to be set\n */\n function setSafeguard(bytes memory safeguard) external override {\n _setSafeguard(msg.sender, safeguard);\n }\n\n /**\n * @dev Executes a proposal to fulfill an intent\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n */\n function execute(Execution[] memory executions) external override onlySolver {\n _execute(executions, false);\n }\n\n /**\n * @dev Simulates an execution. It will always revert. Successful executions are returned as\n * `SettlerSimulationSuccess` errors. Any other error should be treated as failure.\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n */\n function simulate(Execution[] memory executions) external override onlySolver {\n uint256 initialGas = gasleft();\n _execute(executions, true);\n uint256 gasUsed = initialGas - gasleft();\n revert SettlerSimulationSuccess(gasUsed);\n }\n\n /**\n * @dev Validates and executes a proposal to fulfill an intent\n * @param executions List of executions, each including the intent, proposal, and proposal signature\n * @param simulated Whether the execution is a simulation\n */\n function _execute(Execution[] memory executions, bool simulated) internal nonReentrant {\n for (uint256 i = 0; i < executions.length; i++) {\n Intent memory intent = executions[i].intent;\n Proposal memory proposal = executions[i].proposal;\n bytes memory signature = executions[i].signature;\n\n _validateIntent(intent, proposal, signature, simulated);\n getNonceBlock[intent.user][intent.nonce] = block.number;\n\n if (intent.op == uint8(OpType.Swap)) _executeSwap(intent, proposal);\n else if (intent.op == uint8(OpType.Transfer)) _executeTransfer(intent, proposal);\n else if (intent.op == uint8(OpType.Call)) _executeCall(intent, proposal);\n else revert SettlerUnknownIntentType(uint8(intent.op));\n\n emit ProposalExecuted(proposal.hash(intent, _msgSender()), i);\n }\n }\n\n /**\n * @dev Validates and executes a proposal to fulfill a swap intent\n * @param intent Swap intent to be fulfilled\n * @param proposal Swap proposal to be executed\n */\n function _executeSwap(Intent memory intent, Proposal memory proposal) internal {\n SwapIntent memory swapIntent = abi.decode(intent.data, (SwapIntent));\n SwapProposal memory swapProposal = abi.decode(proposal.data, (SwapProposal));\n _validateSwapIntent(swapIntent, swapProposal);\n\n bool isSmartAccount = smartAccountsHandler.isSmartAccount(intent.user);\n if (swapIntent.sourceChain == block.chainid) {\n for (uint256 i = 0; i < swapIntent.tokensIn.length; i++) {\n TokenIn memory tokenIn = swapIntent.tokensIn[i];\n _transferFrom(tokenIn.token, intent.user, swapProposal.executor, tokenIn.amount, isSmartAccount);\n }\n }\n\n uint256[] memory preBalancesOut = _getTokensOutBalance(swapIntent);\n IExecutor(swapProposal.executor).execute(intent, proposal);\n\n if (swapIntent.destinationChain == block.chainid) {\n uint256[] memory outputs = new uint256[](swapIntent.tokensOut.length);\n for (uint256 i = 0; i < swapIntent.tokensOut.length; i++) {\n TokenOut memory tokenOut = swapIntent.tokensOut[i];\n uint256 postBalanceOut = ERC20Helpers.balanceOf(tokenOut.token, address(this));\n uint256 preBalanceOut = preBalancesOut[i];\n if (postBalanceOut < preBalanceOut) revert SettlerPostBalanceOutLtPre(i, postBalanceOut, preBalanceOut);\n\n outputs[i] = postBalanceOut - preBalanceOut;\n uint256 proposedAmount = swapProposal.amountsOut[i];\n if (outputs[i] < proposedAmount) revert SettlerAmountOutLtProposed(i, outputs[i], proposedAmount);\n\n ERC20Helpers.transfer(tokenOut.token, tokenOut.recipient, outputs[i]);\n }\n\n _emitIntentEvents(intent, proposal, abi.encode(outputs));\n _payFees(intent, proposal, isSmartAccount);\n }\n }\n\n /**\n * @dev Validates and executes a proposal to fulfill a transfer intent\n * @param intent Transfer intent to be fulfilled\n * @param proposal Transfer proposal to be executed\n */\n function _executeTransfer(Intent memory intent, Proposal memory proposal) internal {\n TransferIntent memory transferIntent = abi.decode(intent.data, (TransferIntent));\n _validateTransferIntent(transferIntent, proposal);\n\n bool isSmartAccount = smartAccountsHandler.isSmartAccount(intent.user);\n for (uint256 i = 0; i < transferIntent.transfers.length; i++) {\n TransferData memory transfer = transferIntent.transfers[i];\n _transferFrom(transfer.token, intent.user, transfer.recipient, transfer.amount, isSmartAccount);\n }\n\n _emitIntentEvents(intent, proposal, new bytes(0));\n _payFees(intent, proposal, isSmartAccount);\n }\n\n /**\n * @dev Validates and executes a proposal to fulfill a call intent\n * @param intent Call intent to be fulfilled\n * @param proposal Call proposal to be executed\n */\n function _executeCall(Intent memory intent, Proposal memory proposal) internal {\n CallIntent memory callIntent = abi.decode(intent.data, (CallIntent));\n _validateCallIntent(callIntent, proposal, intent.user);\n\n bytes[] memory outputs = new bytes[](callIntent.calls.length);\n for (uint256 i = 0; i < callIntent.calls.length; i++) {\n CallData memory call = callIntent.calls[i];\n // solhint-disable-next-line avoid-low-level-calls\n outputs[i] = smartAccountsHandler.call(intent.user, call.target, call.data, call.value);\n }\n\n _emitIntentEvents(intent, proposal, abi.encode(outputs));\n _payFees(intent, proposal, true);\n }\n\n /**\n * @dev Validates an intent and its corresponding proposal\n * @param intent Intent to be fulfilled\n * @param proposal Proposal to be executed\n * @param signature Proposal signature\n * @param simulated Whether the execution is a simulation\n */\n function _validateIntent(Intent memory intent, Proposal memory proposal, bytes memory signature, bool simulated)\n internal\n view\n {\n if (intent.settler != address(this)) revert SettlerInvalidSettler(intent.settler);\n if (intent.nonce == bytes32(0)) revert SettlerNonceZero();\n if (getNonceBlock[intent.user][intent.nonce] != 0) revert SettlerNonceAlreadyUsed(intent.user, intent.nonce);\n\n if (intentsValidator != address(0)) {\n bytes memory safeguard = _userSafeguard[intent.user];\n if (safeguard.length > 0) IIntentsValidator(intentsValidator).validate(intent, safeguard);\n }\n\n bool shouldValidateDeadlines = _shouldValidateDeadlines(intent);\n if (shouldValidateDeadlines) {\n if (intent.deadline <= block.timestamp) revert SettlerIntentPastDeadline(intent.deadline, block.timestamp);\n bool isProposalPastDeadline = proposal.deadline <= block.timestamp;\n if (isProposalPastDeadline) revert SettlerProposalPastDeadline(proposal.deadline, block.timestamp);\n }\n\n if (intent.maxFees.length != proposal.fees.length) revert SettlerSolverFeeInvalidLength();\n for (uint256 i = 0; i < intent.maxFees.length; i++) {\n uint256 maxFee = intent.maxFees[i].amount;\n uint256 proposalFee = proposal.fees[i];\n if (proposalFee > maxFee) revert SettlerSolverFeeTooHigh(proposalFee, maxFee);\n }\n\n uint8 minValidations = IController(controller).minValidations();\n uint256 requiredValidations = intent.minValidations > minValidations ? intent.minValidations : minValidations;\n\n if (intent.validations.length < requiredValidations) {\n revert SettlerIntentValidationsNotEnough(requiredValidations, intent.validations.length);\n }\n\n address lastValidator = address(0);\n Validation memory validation = Validation(intent.hash());\n bytes32 typedDataHash = _hashTypedDataV4(validation.hash());\n for (uint256 i = 0; i < intent.validations.length; i++) {\n address validator = ECDSA.recover(typedDataHash, intent.validations[i]);\n if (validator <= lastValidator) {\n revert SettlerValidatorDuplicatedOrUnsorted(lastValidator, validator);\n }\n lastValidator = validator;\n bool isValidatorNotAllowed = !IController(controller).isValidatorAllowed(validator);\n if (isValidatorNotAllowed) revert SettlerValidatorNotAllowed(validator);\n }\n\n address signer = ECDSA.recover(_hashTypedDataV4(proposal.hash(intent, _msgSender())), signature);\n bool isProposalSignerNotAllowed = !IController(controller).isProposalSignerAllowed(signer) && !simulated;\n if (isProposalSignerNotAllowed) revert SettlerProposalSignerNotAllowed(signer);\n }\n\n /**\n * @dev Validates a swap intent and its corresponding proposal\n * @param intent Swap intent to be fulfilled\n * @param proposal Proposal to be executed\n */\n function _validateSwapIntent(SwapIntent memory intent, SwapProposal memory proposal) internal view {\n bool isChainInvalid = intent.sourceChain != block.chainid && intent.destinationChain != block.chainid;\n if (isChainInvalid) revert SettlerInvalidChain(block.chainid);\n\n if (proposal.amountsOut.length != intent.tokensOut.length) revert SettlerInvalidProposedAmounts();\n\n for (uint256 i = 0; i < intent.tokensOut.length; i++) {\n TokenOut memory tokenOut = intent.tokensOut[i];\n address recipient = tokenOut.recipient;\n if (recipient == address(this)) revert SettlerInvalidRecipient(recipient);\n\n uint256 minAmount = tokenOut.minAmount;\n uint256 proposedAmount = proposal.amountsOut[i];\n if (proposedAmount < minAmount) revert SettlerProposedAmountLtMinAmount(i, proposedAmount, minAmount);\n }\n\n if (intent.sourceChain != intent.destinationChain) {\n bool isExecutorInvalid = !IController(controller).isExecutorAllowed(proposal.executor);\n if (isExecutorInvalid) revert SettlerExecutorNotAllowed(proposal.executor);\n }\n }\n\n /**\n * @dev Validates a transfer intent and its corresponding proposal\n * @param intent Transfer intent to be fulfilled\n * @param proposal Proposal to be executed\n */\n function _validateTransferIntent(TransferIntent memory intent, Proposal memory proposal) internal view {\n if (intent.chainId != block.chainid) revert SettlerInvalidChain(block.chainid);\n if (proposal.data.length > 0) revert SettlerProposalDataNotEmpty();\n for (uint256 i = 0; i < intent.transfers.length; i++) {\n address recipient = intent.transfers[i].recipient;\n if (recipient == address(this)) revert SettlerInvalidRecipient(recipient);\n }\n }\n\n /**\n * @dev Validates a call intent and its corresponding proposal\n * @param intent Call intent to be fulfilled\n * @param proposal Proposal to be executed\n * @param user The originator of the intent\n */\n function _validateCallIntent(CallIntent memory intent, Proposal memory proposal, address user) internal view {\n if (intent.chainId != block.chainid) revert SettlerInvalidChain(block.chainid);\n if (proposal.data.length > 0) revert SettlerProposalDataNotEmpty();\n if (!smartAccountsHandler.isSmartAccount(user)) revert SettlerUserNotSmartAccount(user);\n }\n\n /**\n * @dev Tells the contract balance for each token out of a swap intent\n * @param intent Swap intent containing the list of tokens out\n */\n function _getTokensOutBalance(SwapIntent memory intent) internal view returns (uint256[] memory balances) {\n balances = new uint256[](intent.tokensOut.length);\n if (intent.destinationChain == block.chainid) {\n for (uint256 i = 0; i < intent.tokensOut.length; i++) {\n balances[i] = ERC20Helpers.balanceOf(intent.tokensOut[i].token, address(this));\n }\n }\n }\n\n /**\n * @dev Tells if the intent and proposal deadlines should be validated\n * @param intent Intent to be fulfilled\n */\n function _shouldValidateDeadlines(Intent memory intent) internal view returns (bool) {\n if (intent.op != uint8(OpType.Swap)) return true;\n SwapIntent memory swapIntent = abi.decode(intent.data, (SwapIntent));\n if (swapIntent.sourceChain == swapIntent.destinationChain) return true;\n return swapIntent.sourceChain == block.chainid;\n }\n\n /**\n * @dev Emits intent custom events\n * @param intent Intent to emit the custom events for\n * @param proposal Proposal that fulfills the intent\n * @param output Encoded array of outputs\n */\n function _emitIntentEvents(Intent memory intent, Proposal memory proposal, bytes memory output) internal {\n for (uint256 i = 0; i < intent.events.length; i++) {\n IntentEvent memory intentEvent = intent.events[i];\n emit IntentExecuted(\n intent.user,\n intentEvent.topic,\n uint8(intent.op),\n intent,\n proposal,\n output,\n intentEvent.data\n );\n }\n }\n\n /**\n * @dev Pays fees\n * @param intent Intent to be fulfilled\n * @param proposal Proposal to be executed\n * @param isSmartAccount Whether the intent user is a smart account\n */\n function _payFees(Intent memory intent, Proposal memory proposal, bool isSmartAccount) internal {\n address from = intent.user;\n address to = _msgSender();\n for (uint256 i = 0; i < intent.maxFees.length; i++) {\n address token = intent.maxFees[i].token;\n if (!Denominations.isUSD(token)) _transferFrom(token, from, to, proposal.fees[i], isSmartAccount);\n }\n }\n\n /**\n * @dev Transfers tokens from one account to another\n * @param token Address of the token to transfer\n * @param from Address of the account sending the tokens\n * @param to Address of the account receiving the tokens\n * @param amount Amount of tokens to transfer\n * @param isSmartAccount Whether the sender is a smart account\n */\n function _transferFrom(address token, address from, address to, uint256 amount, bool isSmartAccount) internal {\n if (isSmartAccount) {\n smartAccountsHandler.transfer(from, token, to, amount);\n } else {\n IERC20(token).safeTransferFrom(from, to, amount);\n }\n }\n\n /**\n * @dev Sets a new smart accounts handler\n * @param newSmartAccountsHandler New smart accounts handler to be set\n */\n function _setSmartAccountsHandler(address newSmartAccountsHandler) internal {\n if (newSmartAccountsHandler == address(0)) revert SmartAccountsHandlerZero();\n smartAccountsHandler = newSmartAccountsHandler;\n emit SmartAccountsHandlerSet(newSmartAccountsHandler);\n }\n\n /**\n * @dev Sets the intents validator\n * @param newIntentsValidator New intents validator to be set\n */\n function _setIntentsValidator(address newIntentsValidator) internal {\n intentsValidator = newIntentsValidator;\n emit IntentsValidatorSet(newIntentsValidator);\n }\n\n /**\n * @dev Sets a safeguard for a user\n * @param user Address of the user to set the safeguard for\n * @param safeguard Safeguard to be set\n */\n function _setSafeguard(address user, bytes memory safeguard) internal {\n delete _userSafeguard[user];\n _userSafeguard[user] = safeguard;\n emit SafeguardSet(user);\n }\n}\n" - }, - "project/contracts/smart-accounts/SmartAccount.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/access/Ownable.sol';\nimport '@openzeppelin/contracts/utils/Address.sol';\nimport '@openzeppelin/contracts/utils/ReentrancyGuard.sol';\nimport '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';\nimport '@openzeppelin/contracts/utils/introspection/ERC165.sol';\n\nimport '../interfaces/ISmartAccount.sol';\nimport '../utils/ERC20Helpers.sol';\n\n/**\n * @title SmartAccount\n * @dev Provides the logic for managing assets, executing arbitrary calls, and controlling permissions\n */\ncontract SmartAccount is ISmartAccount, ERC165, Ownable, ReentrancyGuard {\n // EIP1271 magic return value\n bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e;\n\n // EIP1271 invalid signature return value\n bytes4 internal constant EIP1271_INVALID_SIGNATURE = 0xffffffff;\n\n // List of account permissions\n mapping (address => bool) public isSignerAllowed;\n\n // Mimic settler reference\n address public settler;\n\n /**\n * @dev The settler is zero\n */\n error SmartAccountSettlerZero();\n\n /**\n * @dev The input arrays are not of equal length\n */\n error SmartAccountInputInvalidLength();\n\n /**\n * @dev The sender is not the owner or the settler\n */\n error SmartAccountUnauthorizedSender(address sender);\n\n /**\n * @dev Emitted every time the settler is set\n */\n event SettlerSet(address indexed settler);\n\n /**\n * @dev Emitted every time a signer allowance is set\n */\n event SignerAllowedSet(address indexed account, bool allowed);\n\n /**\n * @dev Reverts unless the sender is the owner or the settler\n */\n modifier onlyOwnerOrSettler() {\n address sender = _msgSender();\n bool isAuthorized = sender == owner() || sender == settler;\n if (!isAuthorized) revert SmartAccountUnauthorizedSender(sender);\n _;\n }\n\n /**\n * @dev Creates a new SmartAccount contract\n * @param _settler Address of the Mimic settler\n * @param _owner Address that will own the contract\n */\n constructor(address _settler, address _owner) Ownable(_owner) {\n _setSettler(_settler);\n }\n\n /**\n * @dev Tells whether the contract supports the given interface ID. Overrides ERC165 to declare support for ISmartAccount interface.\n * @param interfaceId Interface ID is defined as the XOR of all function selectors in the interface\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {\n return interfaceId == type(ISmartAccount).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Tells whether the signature provided belongs to an allowed account.\n * @param hash Message signed by the account\n * @param signature Signature provided to be verified\n */\n function isValidSignature(bytes32 hash, bytes memory signature) external view override returns (bytes4) {\n (address signer, , ) = ECDSA.tryRecover(hash, signature);\n if (signer != address(0) && (signer == owner() || isSignerAllowed[signer])) return EIP1271_MAGIC_VALUE;\n return EIP1271_INVALID_SIGNATURE;\n }\n\n /**\n * @dev It allows receiving native token transfers\n */\n receive() external payable {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n /**\n * @dev Transfers ERC20 or native tokens to the recipient. Sender must be the owner or the settler.\n * @param token Address of the token to be withdrawn\n * @param recipient Address of the account receiving the tokens\n * @param amount Amount of tokens to be withdrawn\n */\n function transfer(address token, address recipient, uint256 amount)\n external\n override\n onlyOwnerOrSettler\n nonReentrant\n {\n ERC20Helpers.transfer(token, recipient, amount);\n emit Transferred(token, recipient, amount);\n }\n\n /**\n * @dev Executes an arbitrary call from the contract. Sender must be the owner or the settler.\n * @param target Address where the call will be sent\n * @param data Calldata to be sent to the target\n * @param value Native token value to send along with the call\n * @return result Call response if it was successful, otherwise it reverts\n */\n function call(address target, bytes memory data, uint256 value)\n external\n override\n onlyOwnerOrSettler\n nonReentrant\n returns (bytes memory result)\n {\n result = Address.functionCallWithValue(target, data, value);\n emit Called(target, data, value, result);\n }\n\n /**\n * @dev Sets the settler. Sender must be the owner.\n * @param newSettler Address of the new settler to be set\n */\n function setSettler(address newSettler) external onlyOwner {\n _setSettler(newSettler);\n }\n\n /**\n * @dev Sets a list of allowed signers. Sender must be the owner.\n * @param accounts List of account addresses\n * @param allowances List of allowed condition per account\n */\n function setAllowedSigners(address[] memory accounts, bool[] memory allowances) external onlyOwner {\n if (accounts.length != allowances.length) revert SmartAccountInputInvalidLength();\n for (uint256 i = 0; i < accounts.length; i++) {\n address account = accounts[i];\n bool allowed = allowances[i];\n isSignerAllowed[account] = allowed;\n emit SignerAllowedSet(account, allowed);\n }\n }\n\n /**\n * @dev Sets the settler\n * @param newSettler Address of the new settler to be set\n */\n function _setSettler(address newSettler) internal {\n if (newSettler == address(0)) revert SmartAccountSettlerZero();\n settler = newSettler;\n emit SettlerSet(newSettler);\n }\n}\n" - }, - "project/contracts/smart-accounts/SmartAccountsHandler.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/utils/Address.sol';\n\nimport '../interfaces/ISafe.sol';\nimport '../interfaces/ISmartAccount.sol';\nimport '../interfaces/ISmartAccountsHandler.sol';\nimport '../utils/Denominations.sol';\n\ncontract SmartAccountsHandler is ISmartAccountsHandler {\n /**\n * @dev Tells whether an account is a supported smart account\n * @param account Address of the account being queried\n */\n function isSmartAccount(address account) external view override returns (bool) {\n if (account.code.length == 0) return false;\n if (_isMimicSmartAccount(account)) return true;\n if (_isSafe(account)) return true;\n return false;\n }\n\n /**\n * @dev Performs a transfer from a smart account\n */\n function transfer(address account, address token, address to, uint256 amount) external override {\n if (_isMimicSmartAccount(account)) return ISmartAccount(account).transfer(token, to, amount);\n if (_isSafe(account)) return _transferSafe(account, token, to, amount);\n revert SmartAccountsHandlerUnsupportedAccount(account);\n }\n\n /**\n * @dev Performs a call from a smart account\n */\n function call(address account, address target, bytes memory data, uint256 value)\n external\n override\n returns (bytes memory)\n {\n // solhint-disable-next-line avoid-low-level-calls\n if (_isMimicSmartAccount(account)) return ISmartAccount(account).call(target, data, value);\n if (_isSafe(account)) return _callSafe(account, target, data, value);\n revert SmartAccountsHandlerUnsupportedAccount(account);\n }\n\n /**\n * @dev Performs a transfer from a safe\n */\n function _transferSafe(address account, address token, address to, uint256 amount) internal {\n Denominations.isNativeToken(token)\n ? _callSafe(account, to, new bytes(0), amount)\n : _callSafe(account, token, abi.encodeWithSelector(IERC20.transfer.selector, to, amount), 0);\n }\n\n /**\n * @dev Performs a call from a safe\n */\n function _callSafe(address account, address target, bytes memory data, uint256 value)\n internal\n returns (bytes memory)\n {\n (bool success, bytes memory result) = ISafe(account).execTransactionFromModuleReturnData(\n target,\n value,\n data,\n SafeOperation.Call\n );\n return\n data.length == 0\n ? Address.verifyCallResult(success, result)\n : Address.verifyCallResultFromTarget(target, success, result);\n }\n\n /**\n * @dev Tells whether an account is a Mimic smart account\n * @param account Address of the account being queried\n */\n function _isMimicSmartAccount(address account) internal view returns (bool) {\n try ISmartAccount(account).supportsInterface(type(ISmartAccount).interfaceId) returns (bool ok) {\n return ok;\n } catch {\n return false;\n }\n }\n\n /**\n * @dev Tells whether an account is a Gnosis Safe\n * @param account Address of the account being queried\n */\n function _isSafe(address account) internal view returns (bool) {\n try ISafe(account).getThreshold() returns (uint256) {\n return true;\n } catch {\n return false;\n }\n }\n}\n" - }, - "project/contracts/smart-accounts/SmartAccountsHandlerHelpers.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/utils/Address.sol';\n\nimport '../interfaces/ISmartAccountsHandler.sol';\n\nlibrary SmartAccountsHandlerHelpers {\n /**\n * @dev Tells whether an account is a supported smart account\n * @param account Address of the account being queried\n */\n function isSmartAccount(address handler, address account) internal view returns (bool) {\n return ISmartAccountsHandler(handler).isSmartAccount(account);\n }\n\n /**\n * @dev Performs a transfer from a smart account\n */\n function transfer(address handler, address account, address token, address to, uint256 amount) internal {\n Address.functionDelegateCall(\n handler,\n abi.encodeWithSelector(ISmartAccountsHandler.transfer.selector, account, token, to, amount)\n );\n }\n\n /**\n * @dev Performs a call from a smart account\n */\n function call(address handler, address account, address target, bytes memory data, uint256 value)\n internal\n returns (bytes memory)\n {\n return\n Address.functionDelegateCall(\n handler,\n abi.encodeWithSelector(ISmartAccountsHandler.call.selector, account, target, data, value)\n );\n }\n}\n" - }, - "project/contracts/test/CallMock.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\ncontract CallMock {\n event CallReceived(address indexed sender, uint256 value);\n error CallError();\n\n function call() external payable {\n emit CallReceived(msg.sender, msg.value);\n }\n\n function callError() external pure {\n revert CallError();\n }\n}\n" - }, - "project/contracts/test/executors/EmptyExecutorMock.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../../interfaces/IExecutor.sol';\n\ncontract EmptyExecutorMock is IExecutor {\n event Executed();\n\n function execute(Intent memory, Proposal memory) external override {\n emit Executed();\n }\n}\n" - }, - "project/contracts/test/executors/MintExecutorMock.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../TokenMock.sol';\nimport '../../interfaces/IExecutor.sol';\n\n/* solhint-disable custom-errors */\n\ncontract MintExecutorMock is IExecutor {\n event Minted();\n\n function execute(Intent memory intent, Proposal memory proposal) external override {\n require(intent.op == uint8(OpType.Swap), 'Invalid intent type');\n\n SwapProposal memory swapProposal = abi.decode(proposal.data, (SwapProposal));\n (address[] memory tokens, uint256[] memory amounts) = abi.decode(swapProposal.data, (address[], uint256[]));\n\n require(tokens.length == amounts.length, 'Invalid inputs');\n\n for (uint256 i = 0; i < tokens.length; i++) {\n TokenMock(tokens[i]).mint(msg.sender, amounts[i]);\n emit Minted();\n }\n }\n}\n" - }, - "project/contracts/test/executors/ReentrantExecutorMock.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../../interfaces/IExecutor.sol';\nimport '../../interfaces/ISettler.sol';\n\ncontract ReentrantExecutorMock is IExecutor {\n // solhint-disable-next-line immutable-vars-naming\n address payable public immutable settler;\n\n constructor(address payable _settler) {\n settler = _settler;\n }\n\n function execute(Intent memory, Proposal memory) external override {\n ISettler(settler).execute(new Execution[](0));\n }\n}\n" - }, - "project/contracts/test/executors/TransferExecutorMock.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../../interfaces/IExecutor.sol';\nimport '../../utils/ERC20Helpers.sol';\n\n/* solhint-disable custom-errors */\n\ncontract TransferExecutorMock is IExecutor {\n event Transferred();\n\n receive() external payable {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n function execute(Intent memory intent, Proposal memory proposal) external override {\n require(intent.op == uint8(OpType.Swap), 'Invalid intent type');\n\n SwapProposal memory swapProposal = abi.decode(proposal.data, (SwapProposal));\n (address[] memory tokens, uint256[] memory amounts) = abi.decode(swapProposal.data, (address[], uint256[]));\n\n require(tokens.length == amounts.length, 'Invalid inputs');\n\n for (uint256 i = 0; i < tokens.length; i++) {\n ERC20Helpers.transfer(tokens[i], msg.sender, amounts[i]);\n emit Transferred();\n }\n }\n}\n" - }, - "project/contracts/test/smart-accounts/SafeMock.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '../../interfaces/ISafe.sol';\n\ncontract SafeMock is ISafe {\n event ModuleTxExecuted(\n address indexed target,\n bytes data,\n uint256 value,\n SafeOperation operation,\n bool success,\n bytes result\n );\n\n receive() external payable {}\n\n function getThreshold() external pure returns (uint256) {\n return 1;\n }\n\n function execTransactionFromModuleReturnData(address to, uint256 value, bytes memory data, SafeOperation operation)\n external\n returns (bool success, bytes memory result)\n {\n // solhint-disable-next-line avoid-low-level-calls\n (success, result) = to.call{ value: value }(data);\n emit ModuleTxExecuted(to, data, value, operation, success, result);\n }\n}\n" - }, - "project/contracts/test/TokenMock.sol": { - "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/token/ERC20/ERC20.sol';\n\ncontract TokenMock is ERC20 {\n uint8 internal _decimals;\n\n constructor(string memory symbol, uint8 dec) ERC20(symbol, symbol) {\n _decimals = dec;\n }\n\n function mint(address account, uint256 amount) external {\n _mint(account, amount);\n }\n\n function decimals() public view override returns (uint8) {\n return _decimals;\n }\n}\n" - }, - "project/contracts/test/utils/DenominationsMock.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '../../utils/Denominations.sol';\n\n// solhint-disable func-name-mixedcase\n\ncontract DenominationsMock {\n function NATIVE_TOKEN() external pure returns (address) {\n return Denominations.NATIVE_TOKEN;\n }\n\n function USD() external pure returns (address) {\n return Denominations.USD;\n }\n}\n" - }, - "project/contracts/test/utils/ERC20HelpersMock.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '../../utils/ERC20Helpers.sol';\n\ncontract ERC20HelpersMock {\n receive() external payable {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n function approve(address token, address to, uint256 amount) external {\n ERC20Helpers.approve(token, to, amount);\n }\n\n function transfer(address token, address to, uint256 amount) external {\n ERC20Helpers.transfer(token, to, amount);\n }\n\n function balanceOf(address token, address account) external view returns (uint256) {\n return ERC20Helpers.balanceOf(token, account);\n }\n}\n" - }, - "project/contracts/utils/Denominations.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\n/**\n * @title Denominations\n * @dev Provides a list of ground denominations for those tokens that cannot be represented by an ERC20.\n * For now, the only needed is the native token that could be ETH, MATIC, or other depending on the layer being operated.\n */\nlibrary Denominations {\n address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n // Fiat currencies follow https://en.wikipedia.org/wiki/ISO_4217\n address internal constant USD = address(840);\n\n function isUSD(address token) internal pure returns (bool) {\n return token == USD;\n }\n\n function isNativeToken(address token) internal pure returns (bool) {\n return token == NATIVE_TOKEN;\n }\n}\n" - }, - "project/contracts/utils/ERC20Helpers.sol": { - "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.20;\n\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport '@openzeppelin/contracts/utils/Address.sol';\n\nimport './Denominations.sol';\n\n/**\n * @title ERC20Helpers\n * @dev Provides a list of ERC20 helper methods\n */\nlibrary ERC20Helpers {\n function approve(address token, address to, uint256 amount) internal {\n SafeERC20.forceApprove(IERC20(token), to, amount);\n }\n\n function transfer(address token, address to, uint256 amount) internal {\n if (Denominations.isNativeToken(token)) Address.sendValue(payable(to), amount);\n else SafeERC20.safeTransfer(IERC20(token), to, amount);\n }\n\n function balanceOf(address token, address account) internal view returns (uint256) {\n if (Denominations.isNativeToken(token)) return address(account).balance;\n else return IERC20(token).balanceOf(address(account));\n }\n}\n" - } - } - } -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/deployed_addresses.json b/packages/evm/ignition/deployments/chain-1/deployed_addresses.json deleted file mode 100644 index a1a41be..0000000 --- a/packages/evm/ignition/deployments/chain-1/deployed_addresses.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Create3Controller#ICreateX": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", - "Create3Controller#Controller": "0x6002825BabC837776A08799404fA55f15DCedA6b", - "Create3Settler#ICreateX": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", - "Create3Settler#Settler": "0x609d831C0068844e11eF85a273c7F356212Fd6D1" -} \ No newline at end of file diff --git a/packages/evm/ignition/deployments/chain-1/journal.jsonl b/packages/evm/ignition/deployments/chain-1/journal.jsonl deleted file mode 100644 index d0556e8..0000000 --- a/packages/evm/ignition/deployments/chain-1/journal.jsonl +++ /dev/null @@ -1,20 +0,0 @@ - -{"chainId":1,"type":"DEPLOYMENT_INITIALIZE"} -{"artifactId":"Create3Controller#ICreateX","contractAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","contractName":"ICreateX","dependencies":[],"futureId":"Create3Controller#ICreateX","futureType":"NAMED_ARTIFACT_CONTRACT_AT","strategy":"basic","strategyConfig":{},"type":"CONTRACT_AT_EXECUTION_STATE_INITIALIZE"} -{"args":["0x3a0ce8115b4913f42c4928d6bc3f554e9a81468b000000000000000000000017","0x608060405234801561000f575f5ffd5b50604051610e6c380380610e6c83398101604081905261002e91610444565b856001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b61006581610172565b505f5b85518110156100a35761009b8682815181106100865761008661051f565b602002602001015160016101c160201b60201c565b600101610068565b505f5b84518110156100e1576100d98582815181106100c4576100c461051f565b6020026020010151600161022060201b60201c565b6001016100a6565b505f5b835181101561011f576101178482815181106101025761010261051f565b6020026020010151600161027760201b60201c565b6001016100e4565b505f5b825181101561015d576101558382815181106101405761014061051f565b602002602001015160016102ce60201b60201c565b600101610122565b5061016781610325565b505050505050610533565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f15091015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610214565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c39101610214565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610214565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b80516001600160a01b0381168114610378575f5ffd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126103a0575f5ffd5b81516001600160401b038111156103b9576103b961037d565b604051600582901b90603f8201601f191681016001600160401b03811182821017156103e7576103e761037d565b604052918252602081850181019290810186841115610404575f5ffd5b6020860192505b8383101561042a5761041c83610362565b81526020928301920161040b565b5095945050505050565b805160ff81168114610378575f5ffd5b5f5f5f5f5f5f60c08789031215610459575f5ffd5b61046287610362565b60208801519096506001600160401b0381111561047d575f5ffd5b61048989828a01610391565b604089015190965090506001600160401b038111156104a6575f5ffd5b6104b289828a01610391565b606089015190955090506001600160401b038111156104cf575f5ffd5b6104db89828a01610391565b608089015190945090506001600160401b038111156104f8575f5ffd5b61050489828a01610391565b92505061051360a08801610434565b90509295509295509295565b634e487b7160e01b5f52603260045260245ffd5b61092c806105405f395ff3fe608060405234801561000f575f5ffd5b50600436106100da575f3560e01c80637d701102116100885780638da5cb5b116100635780638da5cb5b146101ce5780638f72c136146101e8578063d9d8edb9146101fb578063f2fde38b1461021d575f5ffd5b80637d701102146101865780637e8d9637146101a857806384aaa224146101bb575f5ffd5b806361655670116100b8578063616556701461013d578063680038c71461015f578063715018a61461017e575f5ffd5b8063090de7df146100de578063163eed9e146100f35780633d41132f1461012a575b5f5ffd5b6100f16100ec3660046107df565b610230565b005b6101156101013660046108a2565b60036020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100f16101383660046107df565b6102ae565b61011561014b3660046108a2565b60046020525f908152604090205460ff1681565b60055461016c9060ff1681565b60405160ff9091168152602001610121565b6100f1610327565b6101156101943660046108a2565b60026020525f908152604090205460ff1681565b6100f16101b63660046108c2565b61033a565b6100f16101c93660046107df565b61034e565b5f546040516001600160a01b039091168152602001610121565b6100f16101f63660046107df565b6103c7565b6101156102093660046108a2565b60016020525f908152604090205460ff1681565b6100f161022b3660046108a2565b610440565b610238610498565b805182511461025a57604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576102a183828151811061027a5761027a6108e2565b6020026020010151838381518110610294576102946108e2565b60200260200101516104dd565b60010161025c565b505050565b6102b6610498565b80518251146102d857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a95761031f8382815181106102f8576102f86108e2565b6020026020010151838381518110610312576103126108e2565b602002602001015161053c565b6001016102da565b61032f610498565b6103385f610593565b565b610342610498565b61034b816105fa565b50565b610356610498565b805182511461037857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576103bf838281518110610398576103986108e2565b60200260200101518383815181106103b2576103b26108e2565b6020026020010151610637565b60010161037a565b6103cf610498565b80518251146103f157604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a957610438838281518110610411576104116108e2565b602002602001015183838151811061042b5761042b6108e2565b602002602001015161068e565b6001016103f3565b610448610498565b6001600160a01b03811661048f576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61034b81610593565b5f546001600160a01b03163314610338576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610486565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c391015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610530565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610530565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f1509101610530565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610722576107226106e5565b604052919050565b5f67ffffffffffffffff821115610743576107436106e5565b5060051b60200190565b80356001600160a01b0381168114610763575f5ffd5b919050565b5f82601f830112610777575f5ffd5b813561078a6107858261072a565b6106f9565b8082825260208201915060208360051b8601019250858311156107ab575f5ffd5b602085015b838110156107d557803580151581146107c7575f5ffd5b8352602092830192016107b0565b5095945050505050565b5f5f604083850312156107f0575f5ffd5b823567ffffffffffffffff811115610806575f5ffd5b8301601f81018513610816575f5ffd5b80356108246107858261072a565b8082825260208201915060208360051b850101925087831115610845575f5ffd5b6020840193505b8284101561086e5761085d8461074d565b82526020938401939091019061084c565b9450505050602083013567ffffffffffffffff81111561088c575f5ffd5b61089885828601610768565b9150509250929050565b5f602082840312156108b2575f5ffd5b6108bb8261074d565b9392505050565b5f602082840312156108d2575f5ffd5b813560ff811681146108bb575f5ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212204e1a03491d67aedff2296b499cf5fee36529062002d67b286fb08859eac0e13664736f6c634300081c00330000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000e0d76433edd9f5df370561bd0af231e72c83cd3a000000000000000000000000f59eae23353b12ab0561aae1bb74d06623cd3e480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e185b20035753ad9c4c8040fdfeaf05ee2d3454f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000003f4c47e37a94caee31d0b585f54f3ffa1f2294c90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1"],"artifactId":"Create3Controller#ICreateX","contractAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","dependencies":["Create3Controller#ICreateX"],"from":"0x3a0ce8115b4913f42c4928d6bc3f554e9a81468b","functionName":"deployCreate3","futureId":"Create3Controller#ICreateX.deployCreate3","strategy":"basic","strategyConfig":{},"type":"CALL_EXECUTION_STATE_INITIALIZE","value":{"_kind":"bigint","value":"0"}} -{"futureId":"Create3Controller#ICreateX.deployCreate3","networkInteraction":{"data":"0x9c36a2863a0ce8115b4913f42c4928d6bc3f554e9a81468b0000000000000000000000170000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000106c608060405234801561000f575f5ffd5b50604051610e6c380380610e6c83398101604081905261002e91610444565b856001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b61006581610172565b505f5b85518110156100a35761009b8682815181106100865761008661051f565b602002602001015160016101c160201b60201c565b600101610068565b505f5b84518110156100e1576100d98582815181106100c4576100c461051f565b6020026020010151600161022060201b60201c565b6001016100a6565b505f5b835181101561011f576101178482815181106101025761010261051f565b6020026020010151600161027760201b60201c565b6001016100e4565b505f5b825181101561015d576101558382815181106101405761014061051f565b602002602001015160016102ce60201b60201c565b600101610122565b5061016781610325565b505050505050610533565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f15091015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610214565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c39101610214565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610214565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b80516001600160a01b0381168114610378575f5ffd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126103a0575f5ffd5b81516001600160401b038111156103b9576103b961037d565b604051600582901b90603f8201601f191681016001600160401b03811182821017156103e7576103e761037d565b604052918252602081850181019290810186841115610404575f5ffd5b6020860192505b8383101561042a5761041c83610362565b81526020928301920161040b565b5095945050505050565b805160ff81168114610378575f5ffd5b5f5f5f5f5f5f60c08789031215610459575f5ffd5b61046287610362565b60208801519096506001600160401b0381111561047d575f5ffd5b61048989828a01610391565b604089015190965090506001600160401b038111156104a6575f5ffd5b6104b289828a01610391565b606089015190955090506001600160401b038111156104cf575f5ffd5b6104db89828a01610391565b608089015190945090506001600160401b038111156104f8575f5ffd5b61050489828a01610391565b92505061051360a08801610434565b90509295509295509295565b634e487b7160e01b5f52603260045260245ffd5b61092c806105405f395ff3fe608060405234801561000f575f5ffd5b50600436106100da575f3560e01c80637d701102116100885780638da5cb5b116100635780638da5cb5b146101ce5780638f72c136146101e8578063d9d8edb9146101fb578063f2fde38b1461021d575f5ffd5b80637d701102146101865780637e8d9637146101a857806384aaa224146101bb575f5ffd5b806361655670116100b8578063616556701461013d578063680038c71461015f578063715018a61461017e575f5ffd5b8063090de7df146100de578063163eed9e146100f35780633d41132f1461012a575b5f5ffd5b6100f16100ec3660046107df565b610230565b005b6101156101013660046108a2565b60036020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100f16101383660046107df565b6102ae565b61011561014b3660046108a2565b60046020525f908152604090205460ff1681565b60055461016c9060ff1681565b60405160ff9091168152602001610121565b6100f1610327565b6101156101943660046108a2565b60026020525f908152604090205460ff1681565b6100f16101b63660046108c2565b61033a565b6100f16101c93660046107df565b61034e565b5f546040516001600160a01b039091168152602001610121565b6100f16101f63660046107df565b6103c7565b6101156102093660046108a2565b60016020525f908152604090205460ff1681565b6100f161022b3660046108a2565b610440565b610238610498565b805182511461025a57604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576102a183828151811061027a5761027a6108e2565b6020026020010151838381518110610294576102946108e2565b60200260200101516104dd565b60010161025c565b505050565b6102b6610498565b80518251146102d857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a95761031f8382815181106102f8576102f86108e2565b6020026020010151838381518110610312576103126108e2565b602002602001015161053c565b6001016102da565b61032f610498565b6103385f610593565b565b610342610498565b61034b816105fa565b50565b610356610498565b805182511461037857604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a9576103bf838281518110610398576103986108e2565b60200260200101518383815181106103b2576103b26108e2565b6020026020010151610637565b60010161037a565b6103cf610498565b80518251146103f157604051633ac5d5cd60e21b815260040160405180910390fd5b5f5b82518110156102a957610438838281518110610411576104116108e2565b602002602001015183838151811061042b5761042b6108e2565b602002602001015161068e565b6001016103f3565b610448610498565b6001600160a01b03811661048f576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61034b81610593565b5f546001600160a01b03163314610338576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610486565b6001600160a01b0382165f81815260036020908152604091829020805460ff191685151590811790915591519182527ffa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c391015b60405180910390a25050565b6001600160a01b0382165f81815260026020908152604091829020805460ff191685151590811790915591519182527f0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d99249101610530565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6005805460ff191660ff83169081179091556040517f17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358905f90a250565b6001600160a01b0382165f81815260046020908152604091829020805460ff191685151590811790915591519182527fa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c9101610530565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f1509101610530565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610722576107226106e5565b604052919050565b5f67ffffffffffffffff821115610743576107436106e5565b5060051b60200190565b80356001600160a01b0381168114610763575f5ffd5b919050565b5f82601f830112610777575f5ffd5b813561078a6107858261072a565b6106f9565b8082825260208201915060208360051b8601019250858311156107ab575f5ffd5b602085015b838110156107d557803580151581146107c7575f5ffd5b8352602092830192016107b0565b5095945050505050565b5f5f604083850312156107f0575f5ffd5b823567ffffffffffffffff811115610806575f5ffd5b8301601f81018513610816575f5ffd5b80356108246107858261072a565b8082825260208201915060208360051b850101925087831115610845575f5ffd5b6020840193505b8284101561086e5761085d8461074d565b82526020938401939091019061084c565b9450505050602083013567ffffffffffffffff81111561088c575f5ffd5b61089885828601610768565b9150509250929050565b5f602082840312156108b2575f5ffd5b6108bb8261074d565b9392505050565b5f602082840312156108d2575f5ffd5b813560ff811681146108bb575f5ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212204e1a03491d67aedff2296b499cf5fee36529062002d67b286fb08859eac0e13664736f6c634300081c00330000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000e0d76433edd9f5df370561bd0af231e72c83cd3a000000000000000000000000f59eae23353b12ab0561aae1bb74d06623cd3e480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e185b20035753ad9c4c8040fdfeaf05ee2d3454f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000003f4c47e37a94caee31d0b585f54f3ffa1f2294c90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a10000000000000000000000000000000000000000","id":1,"to":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","type":"ONCHAIN_INTERACTION","value":{"_kind":"bigint","value":"0"}},"type":"NETWORK_INTERACTION_REQUEST"} -{"futureId":"Create3Controller#ICreateX.deployCreate3","networkInteractionId":1,"nonce":1,"type":"TRANSACTION_PREPARE_SEND"} -{"futureId":"Create3Controller#ICreateX.deployCreate3","networkInteractionId":1,"nonce":1,"transaction":{"fees":{"maxFeePerGas":{"_kind":"bigint","value":"417439944"},"maxPriorityFeePerGas":{"_kind":"bigint","value":"31378"}},"hash":"0xd93b41e54c8ae4fee27deada2a1d00cc5cedc2258a20d8f8e5e786dbc607d389"},"type":"TRANSACTION_SEND"} -{"futureId":"Create3Controller#ICreateX.deployCreate3","hash":"0xd93b41e54c8ae4fee27deada2a1d00cc5cedc2258a20d8f8e5e786dbc607d389","networkInteractionId":1,"receipt":{"blockHash":"0xec9e3a07ab7b34accac276b961b036b742235edce0e066857171de6c2f6beb0f","blockNumber":24033712,"logs":[{"address":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","data":"0x","logIndex":501,"topics":["0x2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c9067","0x000000000000000000000000bb77d95dfc82899217d15c6e0fa899a17ebaa664","0x8e2b5241fa30452cbf36e924a0aeac3ea10f92148bebd4551e7dfa642e40ccce"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x","logIndex":502,"topics":["0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":503,"topics":["0x77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f150","0x000000000000000000000000e0d76433edd9f5df370561bd0af231e72c83cd3a"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":504,"topics":["0x77c2be228290b8130d2d04fdcbbf8b38325c4b5251dd42162b939d5291b7f150","0x000000000000000000000000f59eae23353b12ab0561aae1bb74d06623cd3e48"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":505,"topics":["0x0329466f8a52b1374ddd654a2b6890da94c718cacde4001884e38cb8570d9924","0x000000000000000000000000e185b20035753ad9c4c8040fdfeaf05ee2d3454f"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":506,"topics":["0xfa088805a5f9d0e7a882a9a4462dbd262d6af56a56eeed6fc4dfc6b6272f75c3","0x0000000000000000000000003f4c47e37a94caee31d0b585f54f3ffa1f2294c9"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":507,"topics":["0xa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c","0x000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x0000000000000000000000000000000000000000000000000000000000000001","logIndex":508,"topics":["0xa8f11f3ae865f4949dc7b3a31787b81ec502e36457f4eaf39fdb7ee7badcb46c","0x000000000000000000000000c76b16fa2fa75d93e08099dc16413d9a083404a1"]},{"address":"0x6002825BabC837776A08799404fA55f15DCedA6b","data":"0x","logIndex":509,"topics":["0x17a16cb66f3fc2ad25dca98c6544248e292af9a9c295fef2b15258b1d01b0358","0x0000000000000000000000000000000000000000000000000000000000000001"]},{"address":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","data":"0x","logIndex":510,"topics":["0x4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b511","0x0000000000000000000000006002825babc837776a08799404fa55f15dceda6b"]}],"status":"SUCCESS"},"type":"TRANSACTION_CONFIRM"} -{"futureId":"Create3Controller#ICreateX.deployCreate3","result":{"type":"SUCCESS"},"type":"CALL_EXECUTION_STATE_COMPLETE"} -{"artifactId":"Create3Controller#ICreateX","dependencies":["Create3Controller#ICreateX.deployCreate3","Create3Controller#ICreateX"],"emitterAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","eventIndex":0,"eventName":"ContractCreation","futureId":"Create3Controller#ICreateX.ContractCreation.newContract.0","nameOrIndex":"newContract","result":"0x6002825BabC837776A08799404fA55f15DCedA6b","strategy":"basic","strategyConfig":{},"txToReadFrom":"0xd93b41e54c8ae4fee27deada2a1d00cc5cedc2258a20d8f8e5e786dbc607d389","type":"READ_EVENT_ARGUMENT_EXECUTION_STATE_INITIALIZE"} -{"artifactId":"Create3Controller#Controller","contractAddress":"0x6002825BabC837776A08799404fA55f15DCedA6b","contractName":"Controller","dependencies":["Create3Controller#ICreateX.ContractCreation.newContract.0"],"futureId":"Create3Controller#Controller","futureType":"NAMED_ARTIFACT_CONTRACT_AT","strategy":"basic","strategyConfig":{},"type":"CONTRACT_AT_EXECUTION_STATE_INITIALIZE"} -{"artifactId":"Create3Settler#ICreateX","contractAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","contractName":"ICreateX","dependencies":[],"futureId":"Create3Settler#ICreateX","futureType":"NAMED_ARTIFACT_CONTRACT_AT","strategy":"basic","strategyConfig":{},"type":"CONTRACT_AT_EXECUTION_STATE_INITIALIZE"} -{"args":["0x3a0ce8115b4913f42c4928d6bc3f554e9a81468b000000000000000000000018","0x610180604052348015610010575f5ffd5b506040516150e63803806150e683398101604081905261002f916102aa565b604080518082018252601681527f4d696d69632050726f746f636f6c20536574746c657200000000000000000000602080830191909152825180840190935260018352603160f81b9083015290826001600160a01b0381166100ab57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6100b4816101c4565b50600180556100c4826002610213565b610120526100d3816003610213565b61014052815160208084019190912060e052815190820120610100524660a05261015f60e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60805250503060c0526001600160a01b0382166101605260405161018290610282565b604051809103905ff08015801561019b573d5f5f3e3d5ffd5b50600480546001600160a01b0319166001600160a01b0392909216919091179055506104859050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f60208351101561022e5761022783610245565b905061023f565b816102398482610373565b5060ff90505b92915050565b5f5f829050601f8151111561026f578260405163305a27a960e01b81526004016100a2919061042d565b805161027a82610462565b179392505050565b6109c58061472183390190565b80516001600160a01b03811681146102a5575f5ffd5b919050565b5f5f604083850312156102bb575f5ffd5b6102c48361028f565b91506102d26020840161028f565b90509250929050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061030357607f821691505b60208210810361032157634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561036e57805f5260205f20601f840160051c8101602085101561034c5750805b601f840160051c820191505b8181101561036b575f8155600101610358565b50505b505050565b81516001600160401b0381111561038c5761038c6102db565b6103a08161039a84546102ef565b84610327565b6020601f8211600181146103d2575f83156103bb5750848201515b5f19600385901b1c1916600184901b17845561036b565b5f84815260208120601f198516915b8281101561040157878501518255602094850194600190920191016103e1565b508482101561041e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80516020808301519190811015610321575f1960209190910360031b1b16919050565b60805160a05160c05160e0516101005161012051610140516101605161421761050a5f395f81816103440152818161049c015281816106cd015281816112770152818161147401528181611585015261223b01525f610bd601525f610ba401525f61294301525f61291b01525f61287601525f6128a001525f6128ca01526142175ff3fe60806040526004361061010c575f3560e01c80638da5cb5b116100a1578063cbc9104511610071578063f2fde38b11610057578063f2fde38b14610314578063f77c479114610333578063fee2b7a714610366575f5ffd5b8063cbc91045146102d6578063f0801868146102f5575f5ffd5b80638da5cb5b14610239578063911929df14610255578063c422661f14610281578063c4bb6af5146102a0575f5ffd5b8063715018a6116100dc578063715018a6146101c057806377c8fb6b146101d45780637f411c79146101f357806384b0196e14610212575f5ffd5b80630fc27cf6146101175780632e5a2458146101385780634987ed2f1461016a5780636ccae054146101a1575f5ffd5b3661011357005b5f5ffd5b348015610122575f5ffd5b50610136610131366004612dc9565b610385565b005b348015610143575f5ffd5b50610157610152366004613366565b610399565b6040519081526020015b60405180910390f35b348015610175575f5ffd5b50600454610189906001600160a01b031681565b6040516001600160a01b039091168152602001610161565b3480156101ac575f5ffd5b506101366101bb3660046133de565b6103af565b3480156101cb575f5ffd5b50610136610465565b3480156101df575f5ffd5b50600554610189906001600160a01b031681565b3480156101fe575f5ffd5b5061013661020d36600461341c565b610478565b34801561021d575f5ffd5b50610226610587565b60405161016197969594939291906135ca565b348015610244575f5ffd5b505f546001600160a01b0316610189565b348015610260575f5ffd5b5061027461026f366004612dc9565b6105e5565b6040516101619190613653565b34801561028c575f5ffd5b5061013661029b366004612dc9565b61068e565b3480156102ab575f5ffd5b506101576102ba366004613665565b600660209081525f928352604080842090915290825290205481565b3480156102e1575f5ffd5b506101366102f036600461368f565b61069f565b348015610300575f5ffd5b5061013661030f36600461341c565b6106a9565b34801561031f575f5ffd5b5061013661032e366004612dc9565b61076e565b34801561033e575f5ffd5b506101897f000000000000000000000000000000000000000000000000000000000000000081565b348015610371575f5ffd5b506101576103803660046136c9565b6107c1565b61038d6107d1565b61039681610816565b50565b5f6103a58484846108ac565b90505b9392505050565b6103b76107d1565b6103bf610948565b6001600160a01b0382166103ff576040517f54328a0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61040a83838361098b565b816001600160a01b0316836001600160a01b03167fed2837b80b3489773c5f1ed30dd2884b4c90dbf7b87428ea03c49b24ef59a8058360405161044f91815260200190565b60405180910390a361046060018055565b505050565b61046d6107d1565b6104765f6109c4565b565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa1580156104e3573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050791906136fb565b6105345760405163a7ac57a960e01b81526001600160a01b03821660048201526024015b60405180910390fd5b5f5a9050610543836001610a20565b5f5a61054f908361371a565b9050806040517fa523ba4900000000000000000000000000000000000000000000000000000000815260040161052b91815260200190565b5f6060805f5f5f6060610598610b9d565b6105a0610bcf565b604080515f808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009b939a50919850469750309650945092509050565b6001600160a01b0381165f90815260076020526040902080546060919061060b90613739565b80601f016020809104026020016040519081016040528092919081815260200182805461063790613739565b80156106825780601f1061065957610100808354040283529160200191610682565b820191905f5260205f20905b81548152906001019060200180831161066557829003601f168201915b50505050509050919050565b6106966107d1565b61039681610bfc565b6103963382610c52565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa158015610714573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061073891906136fb565b6107605760405163a7ac57a960e01b81526001600160a01b038216600482015260240161052b565b61076a825f610a20565b5050565b6107766107d1565b6001600160a01b0381166107b8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f600482015260240161052b565b610396816109c4565b5f6107cb82610ccb565b92915050565b5f546001600160a01b03163314610476576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052b565b6001600160a01b038116610856576040517f42af048a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f93fa00498c991ad852fbb3361afb0e3507720aa9ff69a8386009b76134a6e654905f90a250565b5f7fabd98b1a5498f7ce6ce5fd671f1a1c2ad437b155c4c12281f925612823c72e886108d784610ccb565b83865f01518760200151805190602001206108f58960400151610d6e565b6040805160208101979097528601949094526001600160a01b039092166060850152608084015260a083015260c082015260e0016040516020818303038152906040528051906020012090509392505050565b600260015403610984576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036109b9576104608282610d80565b610460838383610e2b565b5f80546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610a28610948565b5f5b8251811015610b93575f838281518110610a4657610a4661376b565b60200260200101515f015190505f848381518110610a6657610a6661376b565b60200260200101516020015190505f858481518110610a8757610a8761376b565b6020026020010151604001519050610aa183838388610e9f565b6020838101516001600160a01b03165f9081526006825260408082206060870151835290925220439055825160ff16610ae357610ade838361164c565b610b4a565b825160ff165f1901610af957610ade83836119d0565b825160ff1660011901610b1057610ade8383611a90565b82516040517f7cdbcbf100000000000000000000000000000000000000000000000000000000815260ff909116600482015260240161052b565b610b558284336108ac565b6040518581527f120985525cfb78f6c03f96986f9687f49caf81e29350d21fa052e8c0df211ebb9060200160405180910390a2505050600101610a2a565b5061076a60018055565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006002611bb6565b905090565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006003611bb6565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f493e783f4ed464086f434b9d42b1b8fb97375d1fdc91692c73551281526d22be905f90a250565b6001600160a01b0382165f908152600760205260408120610c7291612d60565b6001600160a01b0382165f908152600760205260409020610c9382826137d7565b506040516001600160a01b038316907f5ac98b81e10e07689d7de8e9ec09900564d65647de8d039328d4cbdf575b28f4905f90a25050565b5f7fbd51147cb801db5aa8dc80920aabcbe45ea95c1767e31bc3339f62d8f0d046f9825f015183602001518460400151856060015186608001518760a0015180519060200120610d1e8960c00151611c60565b610d2b8a60e00151611da6565b8a61010001518b6101200151604051602001610d519b9a99989796959493929190613892565b604051602081830303815290604052805190602001209050919050565b5f81604051602001610d51919061390c565b80471015610dc3576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810182905260440161052b565b5f5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610e0d576040519150601f19603f3d011682016040523d82523d5f602084013e610e12565b606091505b509150915081610e2557610e2581611eba565b50505050565b6040516001600160a01b0383811660248301526044820183905261046091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611efc565b60408401516001600160a01b03163014610ef65760408085015190517f30b078bf0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6060840151610f31576040517f58e8d18100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808501516001600160a01b03165f90815260068252604080822060608801518352909252205415610fab57602084015160608501516040517f9756f94d0000000000000000000000000000000000000000000000000000000081526001600160a01b039092166004830152602482015260440161052b565b6005546001600160a01b0316156110e4576020808501516001600160a01b03165f9081526007909152604081208054610fe390613739565b80601f016020809104026020016040519081016040528092919081815260200182805461100f90613739565b801561105a5780601f106110315761010080835404028352916020019161105a565b820191905f5260205f20905b81548152906001019060200180831161103d57829003601f168201915b505050505090505f815111156110e2576005546040517fdbd902180000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063dbd90218906110b59088908590600401613b29565b5f6040518083038186803b1580156110cb575f5ffd5b505afa1580156110dd573d5f5f3e3d5ffd5b505050505b505b5f6110ee85611f81565b9050801561118d57428560800151116111425760808501516040517f95d3dc22000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b8351421080159061118b5784516040517f2c39717b000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b505b8360400151518560c0015151146111d0576040517f9dca144800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8560c0015151811015611273575f8660c0015182815181106111f6576111f661376b565b60200260200101516020015190505f8660400151838151811061121b5761121b61376b565b6020026020010151905081811115611269576040517f8ffa61fa000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161052b565b50506001016111d2565b505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663680038c76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112d1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190613b4d565b90505f8160ff1687610120015111611310578160ff16611317565b8661012001515b90508087610140015151101561136b57610140870151516040517fcd0ce69900000000000000000000000000000000000000000000000000000000815261052b918391600401918252602082015260400190565b5f5f90505f60405180602001604052806113848b610ccb565b905290505f61139a61139583611fd0565b61200d565b90505f5b8a61014001515181101561152f575f6113d5838d610140015184815181106113c8576113c861376b565b6020026020010151612054565b9050846001600160a01b0316816001600160a01b031611611435576040517fce98854c0000000000000000000000000000000000000000000000000000000081526001600160a01b0380871660048301528216602482015260440161052b565b6040517f616556700000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015291955085915f917f000000000000000000000000000000000000000000000000000000000000000090911690636165567090602401602060405180830381865afa1580156114bb573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114df91906136fb565b1590508015611525576040517ff24706660000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505060010161139e565b505f6115486115426113958c8e336108ac565b8a612054565b6040517f163eed9e0000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301529192505f917f0000000000000000000000000000000000000000000000000000000000000000169063163eed9e90602401602060405180830381865afa1580156115ca573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ee91906136fb565b1580156115f9575088155b9050801561163e576040517f100158a80000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505050505050505050505050565b5f8260a001518060200190518101906116659190613c16565b90505f82602001518060200190518101906116809190613d93565b905061168c828261207c565b60208401516004545f916116a9916001600160a01b0316906122ed565b905046835f01510361170b575f5b836040015151811015611709575f846040015182815181106116db576116db61376b565b60200260200101519050611700815f01518860200151865f0151846020015187612371565b506001016116b7565b505b5f611715846123b1565b83516040517f7b4da5de0000000000000000000000000000000000000000000000000000000081529192506001600160a01b031690637b4da5de906117609089908990600401613efe565b5f604051808303815f87803b158015611777575f5ffd5b505af1158015611789573d5f5f3e3d5ffd5b50505050468460200151036119c8575f84606001515167ffffffffffffffff8111156117b7576117b7612de4565b6040519080825280602002602001820160405280156117e0578160200160208202803683370190505b5090505f5b856060015151811015611990575f866060015182815181106118095761180961376b565b602002602001015190505f611821825f01513061246d565b90505f8584815181106118365761183661376b565b602002602001015190508082101561188b576040517ff5ad5ab700000000000000000000000000000000000000000000000000000000815260048101859052602481018390526044810182905260640161052b565b611895818361371a565b8585815181106118a7576118a761376b565b6020026020010181815250505f886040015185815181106118ca576118ca61376b565b60200260200101519050808686815181106118e7576118e761376b565b6020026020010151101561195557848686815181106119085761190861376b565b60209081029190910101516040517ff0a43aa7000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044810182905260640161052b565b611980845f015185604001518888815181106119735761197361376b565b602002602001015161098b565b5050600190920191506117e59050565b506119bb8787836040516020016119a79190613f22565b604051602081830303815290604052612524565b6119c68787856125b7565b505b505050505050565b5f8260a001518060200190518101906119e99190613f34565b90506119f5818361263b565b60208301516004545f91611a12916001600160a01b0316906122ed565b90505f5b826020015151811015611a69575f83602001518281518110611a3a57611a3a61376b565b60200260200101519050611a60815f015187602001518360400151846020015187612371565b50600101611a16565b50604080515f815260208101909152611a859085908590612524565b610e258484836125b7565b5f8260a00151806020019051810190611aa99190613fc0565b9050611aba818385602001516126fc565b5f81602001515167ffffffffffffffff811115611ad957611ad9612de4565b604051908082528060200260200182016040528015611b0c57816020015b6060815260200190600190039081611af75790505b5090505f5b826020015151811015611b93575f83602001518281518110611b3557611b3561376b565b602090810291909101810151878201518151928201516040830151600454939550611b6d946001600160a01b0390941693919061279a565b838381518110611b7f57611b7f61376b565b602090810291909101015250600101611b11565b50611baa8484836040516020016119a7919061410a565b610e25848460016125b7565b606060ff8314611bd057611bc98361282d565b90506107cb565b818054611bdc90613739565b80601f0160208091040260200160405190810160405280929190818152602001828054611c0890613739565b8015611c535780601f10611c2a57610100808354040283529160200191611c53565b820191905f5260205f20905b815481529060010190602001808311611c3657829003601f168201915b5050505050905092915050565b5f5f825167ffffffffffffffff811115611c7c57611c7c612de4565b604051908082528060200260200182016040528015611ca5578160200160208202803683370190505b5090505f5b8351811015611d76577fe44101eb81f50b64d4005e31312ea7bafd6f81357f9dce4fe51d5f2dcb939da4848281518110611ce657611ce661376b565b60200260200101515f0151858381518110611d0357611d0361376b565b602002602001015160200151604051602001611d3b939291909283526001600160a01b03919091166020830152604082015260600190565b60405160208183030381529060405280519060200120828281518110611d6357611d6361376b565b6020908102919091010152600101611caa565b5080604051602001611d88919061390c565b60405160208183030381529060405280519060200120915050919050565b5f5f825167ffffffffffffffff811115611dc257611dc2612de4565b604051908082528060200260200182016040528015611deb578160200160208202803683370190505b5090505f5b8351811015611d76577ff7a396f96f3810db1cfde06de38d32fc29d56b63cf9d9aee2c10c91ef7273bb1848281518110611e2c57611e2c61376b565b60200260200101515f0151858381518110611e4957611e4961376b565b60200260200101516020015180519060200120604051602001611e7f939291909283526020830191909152604082015260600190565b60405160208183030381529060405280519060200120828281518110611ea757611ea761376b565b6020908102919091010152600101611df0565b805115611eca5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f60205f8451602086015f885af180611f1b576040513d5f823e3d81fd5b50505f513d91508115611f32578060011415611f3f565b6001600160a01b0384163b155b15610e25576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b80515f9060ff1615611f9557506001919050565b5f8260a00151806020019051810190611fae9190613c16565b90508060200151815f015103611fc75750600192915050565b51461492915050565b8051604080517f07094367b2db06e3dbb414cf947644f8e08067ea238c5f41cb2fa5fea5be628e6020820152908101919091525f90606001610d51565b5f6107cb61201961286a565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b5f5f5f5f6120628686612993565b92509250925061207282826129dc565b5090949350505050565b81515f904614801590612093575046836020015114155b905080156120b65760405163308a43dd60e11b815246600482015260240161052b565b826060015151826040015151146120f9576040517f1816c20200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8360600151518110156121f2575f8460600151828151811061211f5761211f61376b565b602002602001015190505f81604001519050306001600160a01b0316816001600160a01b03160361216e57604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b5f826020015190505f8660400151858151811061218d5761218d61376b565b60200260200101519050818110156121e2576040517f1fef5f3300000000000000000000000000000000000000000000000000000000815260048101869052602481018290526044810183905260640161052b565b5050600190920191506120fb9050565b5060208301518351146104605781516040517f7d7011020000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201525f917f00000000000000000000000000000000000000000000000000000000000000001690637d70110290602401602060405180830381865afa158015612280573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122a491906136fb565b1590508015610e255782516040517fdb489af40000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6040517f5e07a6e60000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301525f9190841690635e07a6e690602401602060405180830381865afa15801561234d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a891906136fb565b801561239557600454612390906001600160a01b031685878686612adf565b6123aa565b6123aa6001600160a01b038616858585612b38565b5050505050565b606081606001515167ffffffffffffffff8111156123d1576123d1612de4565b6040519080825280602002602001820160405280156123fa578160200160208202803683370190505b50905046826020015103612468575f5b82606001515181101561246657612441836060015182815181106124305761243061376b565b60200260200101515f01513061246d565b8282815181106124535761245361376b565b602090810291909101015260010161240a565b505b919050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036124a357506001600160a01b038116316107cb565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015612500573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc9919061411c565b5f5b8360e0015151811015610e25575f8460e00151828151811061254a5761254a61376b565b60200260200101519050845f015160ff16815f015186602001516001600160a01b03167fee2c98c99b683f71058b8744fea294a411482f07d55c98b392917e9286f22f1388888887602001516040516125a69493929190614133565b60405180910390a450600101612526565b6020830151335f5b8560c00151518110156119c8575f8660c0015182815181106125e3576125e361376b565b60200260200101515f01519050612604816001600160a01b03166103481490565b61263257612632818585896040015186815181106126245761262461376b565b602002602001015189612371565b506001016125bf565b8151461461265e5760405163308a43dd60e11b815246600482015260240161052b565b60208101515115612682576040516393c58e1160e01b815260040160405180910390fd5b5f5b826020015151811015610460575f836020015182815181106126a8576126a861376b565b6020026020010151604001519050306001600160a01b0316816001600160a01b0316036126f357604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b50600101612684565b8251461461271f5760405163308a43dd60e11b815246600482015260240161052b565b60208201515115612743576040516393c58e1160e01b815260040160405180910390fd5b600454612759906001600160a01b0316826122ed565b610460576040517fcaec9bac0000000000000000000000000000000000000000000000000000000081526001600160a01b038216600482015260240161052b565b60606128238663ff883fcf60e01b878787876040516024016127bf949392919061418a565b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612b71565b9695505050505050565b60605f61283983612be3565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156128c257507f000000000000000000000000000000000000000000000000000000000000000046145b156128ec57507f000000000000000000000000000000000000000000000000000000000000000090565b610bca604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f5f83516041036129ca576020840151604085015160608601515f1a6129bc88828585612c23565b9550955095505050506129d5565b505081515f91506002905b9250925092565b5f8260038111156129ef576129ef61377f565b036129f8575050565b6001826003811115612a0c57612a0c61377f565b03612a43576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826003811115612a5757612a5761377f565b03612a91576040517ffce698f70000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6003826003811115612aa557612aa561377f565b0361076a576040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6040516001600160a01b038086166024830152808516604483015283166064820152608481018290526119c89086907ff18d03cc000000000000000000000000000000000000000000000000000000009060a4016127bf565b6040516001600160a01b038481166024830152838116604483015260648201839052610e259186918216906323b872dd90608401610e58565b60605f5f846001600160a01b031684604051612b8d91906141cb565b5f60405180830381855af49150503d805f8114612bc5576040519150601f19603f3d011682016040523d82523d5f602084013e612bca565b606091505b5091509150612bda858383612ceb565b95945050505050565b5f60ff8216601f8111156107cb576040517fb3512b0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115612c5c57505f91506003905082612ce1565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015612cad573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116612cd857505f925060019150829050612ce1565b92505f91508190505b9450945094915050565b606082612d0057612cfb82611eba565b6103a8565b8151158015612d1757506001600160a01b0384163b155b15612d59576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b50806103a8565b508054612d6c90613739565b5f825580601f10612d7b575050565b601f0160209004905f5260205f209081019061039691905b80821115612da6575f8155600101612d93565b5090565b6001600160a01b0381168114610396575f5ffd5b803561246881612daa565b5f60208284031215612dd9575f5ffd5b81356103a881612daa565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b60405290565b6040805190810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051610160810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b6040516080810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612eb457612eb4612de4565b604052919050565b5f67ffffffffffffffff821115612ed557612ed5612de4565b50601f01601f191660200190565b5f82601f830112612ef2575f5ffd5b8135612f05612f0082612ebc565b612e8b565b818152846020838601011115612f19575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f67ffffffffffffffff821115612f4e57612f4e612de4565b5060051b60200190565b5f60608284031215612f68575f5ffd5b612f70612df8565b823581529050602082013567ffffffffffffffff811115612f8f575f5ffd5b612f9b84828501612ee3565b602083015250604082013567ffffffffffffffff811115612fba575f5ffd5b8201601f81018413612fca575f5ffd5b8035612fd8612f0082612f35565b8082825260208201915060208360051b850101925086831115612ff9575f5ffd5b6020840193505b8284101561301b578335825260209384019390910190613000565b60408501525091949350505050565b60ff81168114610396575f5ffd5b80356124688161302a565b5f82601f830112613052575f5ffd5b8135613060612f0082612f35565b8082825260208201915060208360061b860101925085831115613081575f5ffd5b602085015b838110156130cc576040818803121561309d575f5ffd5b6130a5612e21565b81356130b081612daa565b8152602082810135818301529084529290920191604001613086565b5095945050505050565b5f82601f8301126130e5575f5ffd5b81356130f3612f0082612f35565b8082825260208201915060208360051b860101925085831115613114575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613137575f5ffd5b86016040818903601f1901121561314c575f5ffd5b613154612e21565b60208201358152604082013567ffffffffffffffff811115613174575f5ffd5b6131838a602083860101612ee3565b6020830152508085525050602083019250602081019050613119565b5f82601f8301126131ae575f5ffd5b81356131bc612f0082612f35565b8082825260208201915060208360051b8601019250858311156131dd575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613200575f5ffd5b61320f886020838a0101612ee3565b845250602092830192016131e2565b5f610160828403121561322f575f5ffd5b613237612e44565b905061324282613038565b815261325060208301612dbe565b602082015261326160408301612dbe565b6040820152606082810135908201526080808301359082015260a082013567ffffffffffffffff811115613293575f5ffd5b61329f84828501612ee3565b60a08301525060c082013567ffffffffffffffff8111156132be575f5ffd5b6132ca84828501613043565b60c08301525060e082013567ffffffffffffffff8111156132e9575f5ffd5b6132f5848285016130d6565b60e08301525061010082013567ffffffffffffffff811115613315575f5ffd5b61332184828501612ee3565b61010083015250610120828101359082015261014082013567ffffffffffffffff81111561334d575f5ffd5b6133598482850161319f565b6101408301525092915050565b5f5f5f60608486031215613378575f5ffd5b833567ffffffffffffffff81111561338e575f5ffd5b61339a86828701612f58565b935050602084013567ffffffffffffffff8111156133b6575f5ffd5b6133c28682870161321e565b92505060408401356133d381612daa565b809150509250925092565b5f5f5f606084860312156133f0575f5ffd5b83356133fb81612daa565b9250602084013561340b81612daa565b929592945050506040919091013590565b5f6020828403121561342c575f5ffd5b813567ffffffffffffffff811115613442575f5ffd5b8201601f81018413613452575f5ffd5b8035613460612f0082612f35565b8082825260208201915060208360051b850101925086831115613481575f5ffd5b602084015b8381101561355757803567ffffffffffffffff8111156134a4575f5ffd5b85016060818a03601f190112156134b9575f5ffd5b6134c1612df8565b602082013567ffffffffffffffff8111156134da575f5ffd5b6134e98b60208386010161321e565b825250604082013567ffffffffffffffff811115613505575f5ffd5b6135148b602083860101612f58565b602083015250606082013567ffffffffffffffff811115613533575f5ffd5b6135428b602083860101612ee3565b60408301525084525060209283019201613486565b509695505050505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f8151808452602084019350602083015f5b828110156135c05781518652602095860195909101906001016135a2565b5093949350505050565b7fff000000000000000000000000000000000000000000000000000000000000008816815260e060208201525f61360460e0830189613562565b82810360408401526136168189613562565b90508660608401526001600160a01b03861660808401528460a084015282810360c08401526136458185613590565b9a9950505050505050505050565b602081525f6103a86020830184613562565b5f5f60408385031215613676575f5ffd5b823561368181612daa565b946020939093013593505050565b5f6020828403121561369f575f5ffd5b813567ffffffffffffffff8111156136b5575f5ffd5b6136c184828501612ee3565b949350505050565b5f602082840312156136d9575f5ffd5b813567ffffffffffffffff8111156136ef575f5ffd5b6136c18482850161321e565b5f6020828403121561370b575f5ffd5b815180151581146103a8575f5ffd5b818103818111156107cb57634e487b7160e01b5f52601160045260245ffd5b600181811c9082168061374d57607f821691505b60208210810361246657634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b601f82111561046057805f5260205f20601f840160051c810160208510156137b85750805b601f840160051c820191505b818110156123aa575f81556001016137c4565b815167ffffffffffffffff8111156137f1576137f1612de4565b613805816137ff8454613739565b84613793565b6020601f821160018114613837575f83156138205750848201515b5f19600385901b1c1916600184901b1784556123aa565b5f84815260208120601f198516915b828110156138665787850151825560209485019460019092019101613846565b508482101561388357868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8b815260ff8b1660208201526001600160a01b038a1660408201526001600160a01b03891660608201528760808201528660a08201528560c08201528460e0820152836101008201526101606101208201525f6138f3610160830185613562565b9050826101408301529c9b505050505050505050505050565b81515f90829060208501835b82811015613936578151845260209384019390910190600101613918565b509195945050505050565b5f8151808452602084019350602083015f5b828110156135c057815180516001600160a01b031687526020908101518188015260409096019590910190600101613953565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f1985840301885281518051845260208101519050604060208501526139d46040850182613562565b6020998a01999094509290920191506001016139a2565b50909695505050505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f19858403018852613a2f838351613562565b6020988901989093509190910190600101613a13565b805160ff1682525f6020820151613a6760208501826001600160a01b03169052565b506040820151613a8260408501826001600160a01b03169052565b50606082015160608401526080820151608084015260a082015161016060a0850152613ab2610160850182613562565b905060c083015184820360c0860152613acb8282613941565b91505060e083015184820360e0860152613ae58282613986565b915050610100830151848203610100860152613b018282613562565b915050610120830151610120850152610140830151848203610140860152612bda82826139f7565b604081525f613b3b6040830185613a45565b8281036020840152612bda8185613562565b5f60208284031215613b5d575f5ffd5b81516103a88161302a565b5f613b75612f0084612f35565b83815290506020810160608402830185811115613b90575f5ffd5b835b81811015613bee575f60608289031215613baa575f5ffd5b613bb2612df8565b90508151613bbf81612daa565b8152602082810151908201526040820151613bd981612daa565b60408201528352602090920191606001613b92565b5050509392505050565b5f82601f830112613c07575f5ffd5b6103a883835160208501613b68565b5f60208284031215613c26575f5ffd5b815167ffffffffffffffff811115613c3c575f5ffd5b820160808185031215613c4d575f5ffd5b613c55612e68565b8151815260208083015190820152604082015167ffffffffffffffff811115613c7c575f5ffd5b8201601f81018613613c8c575f5ffd5b8051613c9a612f0082612f35565b8082825260208201915060208360061b850101925088831115613cbb575f5ffd5b6020840193505b82841015613d0b576040848a031215613cd9575f5ffd5b613ce1612e21565b8451613cec81612daa565b8152602085810151818301529083526040909401939190910190613cc2565b6040850152505050606082015167ffffffffffffffff811115613d2c575f5ffd5b613d3886828501613bf8565b606083015250949350505050565b5f82601f830112613d55575f5ffd5b8151613d63612f0082612ebc565b818152846020838601011115613d77575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613da3575f5ffd5b815167ffffffffffffffff811115613db9575f5ffd5b820160608185031215613dca575f5ffd5b613dd2612df8565b8151613ddd81612daa565b8152602082015167ffffffffffffffff811115613df8575f5ffd5b613e0486828501613d46565b602083015250604082015167ffffffffffffffff811115613e23575f5ffd5b80830192505084601f830112613e37575f5ffd5b8151613e45612f0082612f35565b8082825260208201915060208360051b860101925087831115613e66575f5ffd5b6020850194505b82851015613e88578451825260209485019490910190613e6d565b6040840152509095945050505050565b805182525f602082015160606020850152613eb66060850182613562565b9050604083015184820360408601528181518084526020840191506020830193505f92505b808310156130cc5783518252602082019150602084019350600183019250613edb565b604081525f613f106040830185613a45565b8281036020840152612bda8185613e98565b602081525f6103a86020830184613590565b5f60208284031215613f44575f5ffd5b815167ffffffffffffffff811115613f5a575f5ffd5b820160408185031215613f6b575f5ffd5b613f73612e21565b81518152602082015167ffffffffffffffff811115613f90575f5ffd5b80830192505084601f830112613fa4575f5ffd5b613fb385835160208501613b68565b6020820152949350505050565b5f60208284031215613fd0575f5ffd5b815167ffffffffffffffff811115613fe6575f5ffd5b820160408185031215613ff7575f5ffd5b613fff612e21565b81518152602082015167ffffffffffffffff81111561401c575f5ffd5b80830192505084601f830112614030575f5ffd5b815161403e612f0082612f35565b8082825260208201915060208360051b86010192508783111561405f575f5ffd5b602085015b838110156140f957805167ffffffffffffffff811115614082575f5ffd5b86016060818b03601f19011215614097575f5ffd5b61409f612df8565b60208201516140ad81612daa565b8152604082015167ffffffffffffffff8111156140c8575f5ffd5b6140d78c602083860101613d46565b6020838101919091526060939093015160408301525084529283019201614064565b506020840152509095945050505050565b602081525f6103a860208301846139f7565b5f6020828403121561412c575f5ffd5b5051919050565b608081525f6141456080830187613a45565b82810360208401526141578187613e98565b9050828103604084015261416b8186613562565b9050828103606084015261417f8185613562565b979650505050505050565b6001600160a01b03851681526001600160a01b0384166020820152608060408201525f6141ba6080830185613562565b905082606083015295945050505050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220fae3247dd7a33c3cd300206de44d56570a43a7690ab25372a739d3c2704b371e64736f6c634300081c00336080604052348015600e575f5ffd5b506109a98061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80635e07a6e614610043578063f18d03cc1461006b578063ff883fcf14610080575b5f5ffd5b610056610051366004610636565b6100a0565b60405190151581526020015b60405180910390f35b61007e61007936600461064f565b6100ec565b005b61009361008e366004610703565b6101cb565b60405161006291906107d3565b5f816001600160a01b03163b5f036100b957505f919050565b6100c2826102b3565b156100cf57506001919050565b6100d882610364565b156100e557506001919050565b505f919050565b6100f5846102b3565b1561017d576040517fbeabacc80000000000000000000000000000000000000000000000000000000081526001600160a01b03848116600483015283811660248301526044820183905285169063beabacc8906064015f604051808303815f87803b158015610162575f5ffd5b505af1158015610174573d5f5f3e3d5ffd5b505050506101c5565b61018684610364565b1561019c57610197848484846103d3565b6101c5565b604051636b94637360e11b81526001600160a01b03851660048201526024015b60405180910390fd5b50505050565b60606101d6856102b3565b1561026d576040517f4ae000410000000000000000000000000000000000000000000000000000000081526001600160a01b03861690634ae0004190610224908790879087906004016107e5565b5f604051808303815f875af115801561023f573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526102669190810190610863565b90506102ab565b61027685610364565b1561028757610266858585856104a2565b604051636b94637360e11b81526001600160a01b03861660048201526024016101bc565b949350505050565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527ff44bac890000000000000000000000000000000000000000000000000000000060048201525f906001600160a01b038316906301ffc9a790602401602060405180830381865afa92505050801561034e575060408051601f3d908101601f1916820190925261034b918101906108a4565b60015b61035957505f919050565b92915050565b919050565b5f816001600160a01b031663e75235b86040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156103bf575060408051601f3d908101601f191682019092526103bc918101906108bd565b60015b6103ca57505f919050565b50600192915050565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b0384161461047f57604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261047a90859085905f6104a2565b61049b565b604080515f81526020810190915261049b9085908490846104a2565b5050505050565b60605f5f866001600160a01b0316635229073f8786885f6040518563ffffffff1660e01b81526004016104d894939291906108d4565b5f604051808303815f875af11580156104f3573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261051a9190810190610928565b9150915084515f146105365761053186838361054b565b610540565b61054082826105c3565b979650505050505050565b6060826105605761055b826105de565b6105bc565b815115801561057757506001600160a01b0384163b155b156105b9576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016101bc565b50805b9392505050565b6060826105d8576105d3826105de565b610359565b50919050565b8051156105ee5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461035f575f5ffd5b5f60208284031215610646575f5ffd5b6105bc82610620565b5f5f5f5f60808587031215610662575f5ffd5b61066b85610620565b935061067960208601610620565b925061068760408601610620565b9396929550929360600135925050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff811182821017156106d4576106d4610697565b604052919050565b5f67ffffffffffffffff8211156106f5576106f5610697565b50601f01601f191660200190565b5f5f5f5f60808587031215610716575f5ffd5b61071f85610620565b935061072d60208601610620565b9250604085013567ffffffffffffffff811115610748575f5ffd5b8501601f81018713610758575f5ffd5b803561076b610766826106dc565b6106ab565b81815288602083850101111561077f575f5ffd5b816020840160208301375f91810160200191909152949793965093946060013593505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6105bc60208301846107a5565b6001600160a01b0384168152606060208201525f61080660608301856107a5565b9050826040830152949350505050565b5f82601f830112610825575f5ffd5b8151610833610766826106dc565b818152846020838601011115610847575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215610873575f5ffd5b815167ffffffffffffffff811115610889575f5ffd5b6102ab84828501610816565b8051801515811461035f575f5ffd5b5f602082840312156108b4575f5ffd5b6105bc82610895565b5f602082840312156108cd575f5ffd5b5051919050565b6001600160a01b0385168152836020820152608060408201525f6108fb60808301856107a5565b90506002831061091957634e487b7160e01b5f52602160045260245ffd5b82606083015295945050505050565b5f5f60408385031215610939575f5ffd5b61094283610895565b9150602083015167ffffffffffffffff81111561095d575f5ffd5b61096985828601610816565b915050925092905056fea264697066735822122063c7f17d2676ae9340ccf0078bc40725749c3c4dd9198f82832bc411d2d1333764736f6c634300081c00330000000000000000000000006002825babc837776a08799404fa55f15dceda6b0000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b"],"artifactId":"Create3Settler#ICreateX","contractAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","dependencies":["Create3Settler#ICreateX"],"from":"0x3a0ce8115b4913f42c4928d6bc3f554e9a81468b","functionName":"deployCreate3","futureId":"Create3Settler#ICreateX.deployCreate3","strategy":"basic","strategyConfig":{},"type":"CALL_EXECUTION_STATE_INITIALIZE","value":{"_kind":"bigint","value":"0"}} -{"futureId":"Create3Settler#ICreateX.deployCreate3","networkInteraction":{"data":"0x9c36a2863a0ce8115b4913f42c4928d6bc3f554e9a81468b00000000000000000000001800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000005126610180604052348015610010575f5ffd5b506040516150e63803806150e683398101604081905261002f916102aa565b604080518082018252601681527f4d696d69632050726f746f636f6c20536574746c657200000000000000000000602080830191909152825180840190935260018352603160f81b9083015290826001600160a01b0381166100ab57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6100b4816101c4565b50600180556100c4826002610213565b610120526100d3816003610213565b61014052815160208084019190912060e052815190820120610100524660a05261015f60e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60805250503060c0526001600160a01b0382166101605260405161018290610282565b604051809103905ff08015801561019b573d5f5f3e3d5ffd5b50600480546001600160a01b0319166001600160a01b0392909216919091179055506104859050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f60208351101561022e5761022783610245565b905061023f565b816102398482610373565b5060ff90505b92915050565b5f5f829050601f8151111561026f578260405163305a27a960e01b81526004016100a2919061042d565b805161027a82610462565b179392505050565b6109c58061472183390190565b80516001600160a01b03811681146102a5575f5ffd5b919050565b5f5f604083850312156102bb575f5ffd5b6102c48361028f565b91506102d26020840161028f565b90509250929050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061030357607f821691505b60208210810361032157634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561036e57805f5260205f20601f840160051c8101602085101561034c5750805b601f840160051c820191505b8181101561036b575f8155600101610358565b50505b505050565b81516001600160401b0381111561038c5761038c6102db565b6103a08161039a84546102ef565b84610327565b6020601f8211600181146103d2575f83156103bb5750848201515b5f19600385901b1c1916600184901b17845561036b565b5f84815260208120601f198516915b8281101561040157878501518255602094850194600190920191016103e1565b508482101561041e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80516020808301519190811015610321575f1960209190910360031b1b16919050565b60805160a05160c05160e0516101005161012051610140516101605161421761050a5f395f81816103440152818161049c015281816106cd015281816112770152818161147401528181611585015261223b01525f610bd601525f610ba401525f61294301525f61291b01525f61287601525f6128a001525f6128ca01526142175ff3fe60806040526004361061010c575f3560e01c80638da5cb5b116100a1578063cbc9104511610071578063f2fde38b11610057578063f2fde38b14610314578063f77c479114610333578063fee2b7a714610366575f5ffd5b8063cbc91045146102d6578063f0801868146102f5575f5ffd5b80638da5cb5b14610239578063911929df14610255578063c422661f14610281578063c4bb6af5146102a0575f5ffd5b8063715018a6116100dc578063715018a6146101c057806377c8fb6b146101d45780637f411c79146101f357806384b0196e14610212575f5ffd5b80630fc27cf6146101175780632e5a2458146101385780634987ed2f1461016a5780636ccae054146101a1575f5ffd5b3661011357005b5f5ffd5b348015610122575f5ffd5b50610136610131366004612dc9565b610385565b005b348015610143575f5ffd5b50610157610152366004613366565b610399565b6040519081526020015b60405180910390f35b348015610175575f5ffd5b50600454610189906001600160a01b031681565b6040516001600160a01b039091168152602001610161565b3480156101ac575f5ffd5b506101366101bb3660046133de565b6103af565b3480156101cb575f5ffd5b50610136610465565b3480156101df575f5ffd5b50600554610189906001600160a01b031681565b3480156101fe575f5ffd5b5061013661020d36600461341c565b610478565b34801561021d575f5ffd5b50610226610587565b60405161016197969594939291906135ca565b348015610244575f5ffd5b505f546001600160a01b0316610189565b348015610260575f5ffd5b5061027461026f366004612dc9565b6105e5565b6040516101619190613653565b34801561028c575f5ffd5b5061013661029b366004612dc9565b61068e565b3480156102ab575f5ffd5b506101576102ba366004613665565b600660209081525f928352604080842090915290825290205481565b3480156102e1575f5ffd5b506101366102f036600461368f565b61069f565b348015610300575f5ffd5b5061013661030f36600461341c565b6106a9565b34801561031f575f5ffd5b5061013661032e366004612dc9565b61076e565b34801561033e575f5ffd5b506101897f000000000000000000000000000000000000000000000000000000000000000081565b348015610371575f5ffd5b506101576103803660046136c9565b6107c1565b61038d6107d1565b61039681610816565b50565b5f6103a58484846108ac565b90505b9392505050565b6103b76107d1565b6103bf610948565b6001600160a01b0382166103ff576040517f54328a0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61040a83838361098b565b816001600160a01b0316836001600160a01b03167fed2837b80b3489773c5f1ed30dd2884b4c90dbf7b87428ea03c49b24ef59a8058360405161044f91815260200190565b60405180910390a361046060018055565b505050565b61046d6107d1565b6104765f6109c4565b565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa1580156104e3573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050791906136fb565b6105345760405163a7ac57a960e01b81526001600160a01b03821660048201526024015b60405180910390fd5b5f5a9050610543836001610a20565b5f5a61054f908361371a565b9050806040517fa523ba4900000000000000000000000000000000000000000000000000000000815260040161052b91815260200190565b5f6060805f5f5f6060610598610b9d565b6105a0610bcf565b604080515f808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009b939a50919850469750309650945092509050565b6001600160a01b0381165f90815260076020526040902080546060919061060b90613739565b80601f016020809104026020016040519081016040528092919081815260200182805461063790613739565b80156106825780601f1061065957610100808354040283529160200191610682565b820191905f5260205f20905b81548152906001019060200180831161066557829003601f168201915b50505050509050919050565b6106966107d1565b61039681610bfc565b6103963382610c52565b5f3360405163d9d8edb960e01b81526001600160a01b0380831660048301529192507f00000000000000000000000000000000000000000000000000000000000000009091169063d9d8edb990602401602060405180830381865afa158015610714573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061073891906136fb565b6107605760405163a7ac57a960e01b81526001600160a01b038216600482015260240161052b565b61076a825f610a20565b5050565b6107766107d1565b6001600160a01b0381166107b8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f600482015260240161052b565b610396816109c4565b5f6107cb82610ccb565b92915050565b5f546001600160a01b03163314610476576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052b565b6001600160a01b038116610856576040517f42af048a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f93fa00498c991ad852fbb3361afb0e3507720aa9ff69a8386009b76134a6e654905f90a250565b5f7fabd98b1a5498f7ce6ce5fd671f1a1c2ad437b155c4c12281f925612823c72e886108d784610ccb565b83865f01518760200151805190602001206108f58960400151610d6e565b6040805160208101979097528601949094526001600160a01b039092166060850152608084015260a083015260c082015260e0016040516020818303038152906040528051906020012090509392505050565b600260015403610984576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036109b9576104608282610d80565b610460838383610e2b565b5f80546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610a28610948565b5f5b8251811015610b93575f838281518110610a4657610a4661376b565b60200260200101515f015190505f848381518110610a6657610a6661376b565b60200260200101516020015190505f858481518110610a8757610a8761376b565b6020026020010151604001519050610aa183838388610e9f565b6020838101516001600160a01b03165f9081526006825260408082206060870151835290925220439055825160ff16610ae357610ade838361164c565b610b4a565b825160ff165f1901610af957610ade83836119d0565b825160ff1660011901610b1057610ade8383611a90565b82516040517f7cdbcbf100000000000000000000000000000000000000000000000000000000815260ff909116600482015260240161052b565b610b558284336108ac565b6040518581527f120985525cfb78f6c03f96986f9687f49caf81e29350d21fa052e8c0df211ebb9060200160405180910390a2505050600101610a2a565b5061076a60018055565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006002611bb6565b905090565b6060610bca7f00000000000000000000000000000000000000000000000000000000000000006003611bb6565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040517f493e783f4ed464086f434b9d42b1b8fb97375d1fdc91692c73551281526d22be905f90a250565b6001600160a01b0382165f908152600760205260408120610c7291612d60565b6001600160a01b0382165f908152600760205260409020610c9382826137d7565b506040516001600160a01b038316907f5ac98b81e10e07689d7de8e9ec09900564d65647de8d039328d4cbdf575b28f4905f90a25050565b5f7fbd51147cb801db5aa8dc80920aabcbe45ea95c1767e31bc3339f62d8f0d046f9825f015183602001518460400151856060015186608001518760a0015180519060200120610d1e8960c00151611c60565b610d2b8a60e00151611da6565b8a61010001518b6101200151604051602001610d519b9a99989796959493929190613892565b604051602081830303815290604052805190602001209050919050565b5f81604051602001610d51919061390c565b80471015610dc3576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810182905260440161052b565b5f5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610e0d576040519150601f19603f3d011682016040523d82523d5f602084013e610e12565b606091505b509150915081610e2557610e2581611eba565b50505050565b6040516001600160a01b0383811660248301526044820183905261046091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611efc565b60408401516001600160a01b03163014610ef65760408085015190517f30b078bf0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6060840151610f31576040517f58e8d18100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808501516001600160a01b03165f90815260068252604080822060608801518352909252205415610fab57602084015160608501516040517f9756f94d0000000000000000000000000000000000000000000000000000000081526001600160a01b039092166004830152602482015260440161052b565b6005546001600160a01b0316156110e4576020808501516001600160a01b03165f9081526007909152604081208054610fe390613739565b80601f016020809104026020016040519081016040528092919081815260200182805461100f90613739565b801561105a5780601f106110315761010080835404028352916020019161105a565b820191905f5260205f20905b81548152906001019060200180831161103d57829003601f168201915b505050505090505f815111156110e2576005546040517fdbd902180000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063dbd90218906110b59088908590600401613b29565b5f6040518083038186803b1580156110cb575f5ffd5b505afa1580156110dd573d5f5f3e3d5ffd5b505050505b505b5f6110ee85611f81565b9050801561118d57428560800151116111425760808501516040517f95d3dc22000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b8351421080159061118b5784516040517f2c39717b000000000000000000000000000000000000000000000000000000008152600481019190915242602482015260440161052b565b505b8360400151518560c0015151146111d0576040517f9dca144800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8560c0015151811015611273575f8660c0015182815181106111f6576111f661376b565b60200260200101516020015190505f8660400151838151811061121b5761121b61376b565b6020026020010151905081811115611269576040517f8ffa61fa000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161052b565b50506001016111d2565b505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663680038c76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112d1573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190613b4d565b90505f8160ff1687610120015111611310578160ff16611317565b8661012001515b90508087610140015151101561136b57610140870151516040517fcd0ce69900000000000000000000000000000000000000000000000000000000815261052b918391600401918252602082015260400190565b5f5f90505f60405180602001604052806113848b610ccb565b905290505f61139a61139583611fd0565b61200d565b90505f5b8a61014001515181101561152f575f6113d5838d610140015184815181106113c8576113c861376b565b6020026020010151612054565b9050846001600160a01b0316816001600160a01b031611611435576040517fce98854c0000000000000000000000000000000000000000000000000000000081526001600160a01b0380871660048301528216602482015260440161052b565b6040517f616556700000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015291955085915f917f000000000000000000000000000000000000000000000000000000000000000090911690636165567090602401602060405180830381865afa1580156114bb573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114df91906136fb565b1590508015611525576040517ff24706660000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505060010161139e565b505f6115486115426113958c8e336108ac565b8a612054565b6040517f163eed9e0000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301529192505f917f0000000000000000000000000000000000000000000000000000000000000000169063163eed9e90602401602060405180830381865afa1580156115ca573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ee91906136fb565b1580156115f9575088155b9050801561163e576040517f100158a80000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161052b565b505050505050505050505050565b5f8260a001518060200190518101906116659190613c16565b90505f82602001518060200190518101906116809190613d93565b905061168c828261207c565b60208401516004545f916116a9916001600160a01b0316906122ed565b905046835f01510361170b575f5b836040015151811015611709575f846040015182815181106116db576116db61376b565b60200260200101519050611700815f01518860200151865f0151846020015187612371565b506001016116b7565b505b5f611715846123b1565b83516040517f7b4da5de0000000000000000000000000000000000000000000000000000000081529192506001600160a01b031690637b4da5de906117609089908990600401613efe565b5f604051808303815f87803b158015611777575f5ffd5b505af1158015611789573d5f5f3e3d5ffd5b50505050468460200151036119c8575f84606001515167ffffffffffffffff8111156117b7576117b7612de4565b6040519080825280602002602001820160405280156117e0578160200160208202803683370190505b5090505f5b856060015151811015611990575f866060015182815181106118095761180961376b565b602002602001015190505f611821825f01513061246d565b90505f8584815181106118365761183661376b565b602002602001015190508082101561188b576040517ff5ad5ab700000000000000000000000000000000000000000000000000000000815260048101859052602481018390526044810182905260640161052b565b611895818361371a565b8585815181106118a7576118a761376b565b6020026020010181815250505f886040015185815181106118ca576118ca61376b565b60200260200101519050808686815181106118e7576118e761376b565b6020026020010151101561195557848686815181106119085761190861376b565b60209081029190910101516040517ff0a43aa7000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044810182905260640161052b565b611980845f015185604001518888815181106119735761197361376b565b602002602001015161098b565b5050600190920191506117e59050565b506119bb8787836040516020016119a79190613f22565b604051602081830303815290604052612524565b6119c68787856125b7565b505b505050505050565b5f8260a001518060200190518101906119e99190613f34565b90506119f5818361263b565b60208301516004545f91611a12916001600160a01b0316906122ed565b90505f5b826020015151811015611a69575f83602001518281518110611a3a57611a3a61376b565b60200260200101519050611a60815f015187602001518360400151846020015187612371565b50600101611a16565b50604080515f815260208101909152611a859085908590612524565b610e258484836125b7565b5f8260a00151806020019051810190611aa99190613fc0565b9050611aba818385602001516126fc565b5f81602001515167ffffffffffffffff811115611ad957611ad9612de4565b604051908082528060200260200182016040528015611b0c57816020015b6060815260200190600190039081611af75790505b5090505f5b826020015151811015611b93575f83602001518281518110611b3557611b3561376b565b602090810291909101810151878201518151928201516040830151600454939550611b6d946001600160a01b0390941693919061279a565b838381518110611b7f57611b7f61376b565b602090810291909101015250600101611b11565b50611baa8484836040516020016119a7919061410a565b610e25848460016125b7565b606060ff8314611bd057611bc98361282d565b90506107cb565b818054611bdc90613739565b80601f0160208091040260200160405190810160405280929190818152602001828054611c0890613739565b8015611c535780601f10611c2a57610100808354040283529160200191611c53565b820191905f5260205f20905b815481529060010190602001808311611c3657829003601f168201915b5050505050905092915050565b5f5f825167ffffffffffffffff811115611c7c57611c7c612de4565b604051908082528060200260200182016040528015611ca5578160200160208202803683370190505b5090505f5b8351811015611d76577fe44101eb81f50b64d4005e31312ea7bafd6f81357f9dce4fe51d5f2dcb939da4848281518110611ce657611ce661376b565b60200260200101515f0151858381518110611d0357611d0361376b565b602002602001015160200151604051602001611d3b939291909283526001600160a01b03919091166020830152604082015260600190565b60405160208183030381529060405280519060200120828281518110611d6357611d6361376b565b6020908102919091010152600101611caa565b5080604051602001611d88919061390c565b60405160208183030381529060405280519060200120915050919050565b5f5f825167ffffffffffffffff811115611dc257611dc2612de4565b604051908082528060200260200182016040528015611deb578160200160208202803683370190505b5090505f5b8351811015611d76577ff7a396f96f3810db1cfde06de38d32fc29d56b63cf9d9aee2c10c91ef7273bb1848281518110611e2c57611e2c61376b565b60200260200101515f0151858381518110611e4957611e4961376b565b60200260200101516020015180519060200120604051602001611e7f939291909283526020830191909152604082015260600190565b60405160208183030381529060405280519060200120828281518110611ea757611ea761376b565b6020908102919091010152600101611df0565b805115611eca5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f60205f8451602086015f885af180611f1b576040513d5f823e3d81fd5b50505f513d91508115611f32578060011415611f3f565b6001600160a01b0384163b155b15610e25576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b80515f9060ff1615611f9557506001919050565b5f8260a00151806020019051810190611fae9190613c16565b90508060200151815f015103611fc75750600192915050565b51461492915050565b8051604080517f07094367b2db06e3dbb414cf947644f8e08067ea238c5f41cb2fa5fea5be628e6020820152908101919091525f90606001610d51565b5f6107cb61201961286a565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b5f5f5f5f6120628686612993565b92509250925061207282826129dc565b5090949350505050565b81515f904614801590612093575046836020015114155b905080156120b65760405163308a43dd60e11b815246600482015260240161052b565b826060015151826040015151146120f9576040517f1816c20200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8360600151518110156121f2575f8460600151828151811061211f5761211f61376b565b602002602001015190505f81604001519050306001600160a01b0316816001600160a01b03160361216e57604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b5f826020015190505f8660400151858151811061218d5761218d61376b565b60200260200101519050818110156121e2576040517f1fef5f3300000000000000000000000000000000000000000000000000000000815260048101869052602481018290526044810183905260640161052b565b5050600190920191506120fb9050565b5060208301518351146104605781516040517f7d7011020000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201525f917f00000000000000000000000000000000000000000000000000000000000000001690637d70110290602401602060405180830381865afa158015612280573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122a491906136fb565b1590508015610e255782516040517fdb489af40000000000000000000000000000000000000000000000000000000081526001600160a01b03909116600482015260240161052b565b6040517f5e07a6e60000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301525f9190841690635e07a6e690602401602060405180830381865afa15801561234d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a891906136fb565b801561239557600454612390906001600160a01b031685878686612adf565b6123aa565b6123aa6001600160a01b038616858585612b38565b5050505050565b606081606001515167ffffffffffffffff8111156123d1576123d1612de4565b6040519080825280602002602001820160405280156123fa578160200160208202803683370190505b50905046826020015103612468575f5b82606001515181101561246657612441836060015182815181106124305761243061376b565b60200260200101515f01513061246d565b8282815181106124535761245361376b565b602090810291909101015260010161240a565b505b919050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038416036124a357506001600160a01b038116316107cb565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015612500573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc9919061411c565b5f5b8360e0015151811015610e25575f8460e00151828151811061254a5761254a61376b565b60200260200101519050845f015160ff16815f015186602001516001600160a01b03167fee2c98c99b683f71058b8744fea294a411482f07d55c98b392917e9286f22f1388888887602001516040516125a69493929190614133565b60405180910390a450600101612526565b6020830151335f5b8560c00151518110156119c8575f8660c0015182815181106125e3576125e361376b565b60200260200101515f01519050612604816001600160a01b03166103481490565b61263257612632818585896040015186815181106126245761262461376b565b602002602001015189612371565b506001016125bf565b8151461461265e5760405163308a43dd60e11b815246600482015260240161052b565b60208101515115612682576040516393c58e1160e01b815260040160405180910390fd5b5f5b826020015151811015610460575f836020015182815181106126a8576126a861376b565b6020026020010151604001519050306001600160a01b0316816001600160a01b0316036126f357604051630b9ab59960e01b81526001600160a01b038216600482015260240161052b565b50600101612684565b8251461461271f5760405163308a43dd60e11b815246600482015260240161052b565b60208201515115612743576040516393c58e1160e01b815260040160405180910390fd5b600454612759906001600160a01b0316826122ed565b610460576040517fcaec9bac0000000000000000000000000000000000000000000000000000000081526001600160a01b038216600482015260240161052b565b60606128238663ff883fcf60e01b878787876040516024016127bf949392919061418a565b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612b71565b9695505050505050565b60605f61283983612be3565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156128c257507f000000000000000000000000000000000000000000000000000000000000000046145b156128ec57507f000000000000000000000000000000000000000000000000000000000000000090565b610bca604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b5f5f5f83516041036129ca576020840151604085015160608601515f1a6129bc88828585612c23565b9550955095505050506129d5565b505081515f91506002905b9250925092565b5f8260038111156129ef576129ef61377f565b036129f8575050565b6001826003811115612a0c57612a0c61377f565b03612a43576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826003811115612a5757612a5761377f565b03612a91576040517ffce698f70000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6003826003811115612aa557612aa561377f565b0361076a576040517fd78bce0c0000000000000000000000000000000000000000000000000000000081526004810182905260240161052b565b6040516001600160a01b038086166024830152808516604483015283166064820152608481018290526119c89086907ff18d03cc000000000000000000000000000000000000000000000000000000009060a4016127bf565b6040516001600160a01b038481166024830152838116604483015260648201839052610e259186918216906323b872dd90608401610e58565b60605f5f846001600160a01b031684604051612b8d91906141cb565b5f60405180830381855af49150503d805f8114612bc5576040519150601f19603f3d011682016040523d82523d5f602084013e612bca565b606091505b5091509150612bda858383612ceb565b95945050505050565b5f60ff8216601f8111156107cb576040517fb3512b0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115612c5c57505f91506003905082612ce1565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015612cad573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116612cd857505f925060019150829050612ce1565b92505f91508190505b9450945094915050565b606082612d0057612cfb82611eba565b6103a8565b8151158015612d1757506001600160a01b0384163b155b15612d59576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161052b565b50806103a8565b508054612d6c90613739565b5f825580601f10612d7b575050565b601f0160209004905f5260205f209081019061039691905b80821115612da6575f8155600101612d93565b5090565b6001600160a01b0381168114610396575f5ffd5b803561246881612daa565b5f60208284031215612dd9575f5ffd5b81356103a881612daa565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b60405290565b6040805190810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051610160810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b6040516080810167ffffffffffffffff81118282101715612e1b57612e1b612de4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612eb457612eb4612de4565b604052919050565b5f67ffffffffffffffff821115612ed557612ed5612de4565b50601f01601f191660200190565b5f82601f830112612ef2575f5ffd5b8135612f05612f0082612ebc565b612e8b565b818152846020838601011115612f19575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f67ffffffffffffffff821115612f4e57612f4e612de4565b5060051b60200190565b5f60608284031215612f68575f5ffd5b612f70612df8565b823581529050602082013567ffffffffffffffff811115612f8f575f5ffd5b612f9b84828501612ee3565b602083015250604082013567ffffffffffffffff811115612fba575f5ffd5b8201601f81018413612fca575f5ffd5b8035612fd8612f0082612f35565b8082825260208201915060208360051b850101925086831115612ff9575f5ffd5b6020840193505b8284101561301b578335825260209384019390910190613000565b60408501525091949350505050565b60ff81168114610396575f5ffd5b80356124688161302a565b5f82601f830112613052575f5ffd5b8135613060612f0082612f35565b8082825260208201915060208360061b860101925085831115613081575f5ffd5b602085015b838110156130cc576040818803121561309d575f5ffd5b6130a5612e21565b81356130b081612daa565b8152602082810135818301529084529290920191604001613086565b5095945050505050565b5f82601f8301126130e5575f5ffd5b81356130f3612f0082612f35565b8082825260208201915060208360051b860101925085831115613114575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613137575f5ffd5b86016040818903601f1901121561314c575f5ffd5b613154612e21565b60208201358152604082013567ffffffffffffffff811115613174575f5ffd5b6131838a602083860101612ee3565b6020830152508085525050602083019250602081019050613119565b5f82601f8301126131ae575f5ffd5b81356131bc612f0082612f35565b8082825260208201915060208360051b8601019250858311156131dd575f5ffd5b602085015b838110156130cc57803567ffffffffffffffff811115613200575f5ffd5b61320f886020838a0101612ee3565b845250602092830192016131e2565b5f610160828403121561322f575f5ffd5b613237612e44565b905061324282613038565b815261325060208301612dbe565b602082015261326160408301612dbe565b6040820152606082810135908201526080808301359082015260a082013567ffffffffffffffff811115613293575f5ffd5b61329f84828501612ee3565b60a08301525060c082013567ffffffffffffffff8111156132be575f5ffd5b6132ca84828501613043565b60c08301525060e082013567ffffffffffffffff8111156132e9575f5ffd5b6132f5848285016130d6565b60e08301525061010082013567ffffffffffffffff811115613315575f5ffd5b61332184828501612ee3565b61010083015250610120828101359082015261014082013567ffffffffffffffff81111561334d575f5ffd5b6133598482850161319f565b6101408301525092915050565b5f5f5f60608486031215613378575f5ffd5b833567ffffffffffffffff81111561338e575f5ffd5b61339a86828701612f58565b935050602084013567ffffffffffffffff8111156133b6575f5ffd5b6133c28682870161321e565b92505060408401356133d381612daa565b809150509250925092565b5f5f5f606084860312156133f0575f5ffd5b83356133fb81612daa565b9250602084013561340b81612daa565b929592945050506040919091013590565b5f6020828403121561342c575f5ffd5b813567ffffffffffffffff811115613442575f5ffd5b8201601f81018413613452575f5ffd5b8035613460612f0082612f35565b8082825260208201915060208360051b850101925086831115613481575f5ffd5b602084015b8381101561355757803567ffffffffffffffff8111156134a4575f5ffd5b85016060818a03601f190112156134b9575f5ffd5b6134c1612df8565b602082013567ffffffffffffffff8111156134da575f5ffd5b6134e98b60208386010161321e565b825250604082013567ffffffffffffffff811115613505575f5ffd5b6135148b602083860101612f58565b602083015250606082013567ffffffffffffffff811115613533575f5ffd5b6135428b602083860101612ee3565b60408301525084525060209283019201613486565b509695505050505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f8151808452602084019350602083015f5b828110156135c05781518652602095860195909101906001016135a2565b5093949350505050565b7fff000000000000000000000000000000000000000000000000000000000000008816815260e060208201525f61360460e0830189613562565b82810360408401526136168189613562565b90508660608401526001600160a01b03861660808401528460a084015282810360c08401526136458185613590565b9a9950505050505050505050565b602081525f6103a86020830184613562565b5f5f60408385031215613676575f5ffd5b823561368181612daa565b946020939093013593505050565b5f6020828403121561369f575f5ffd5b813567ffffffffffffffff8111156136b5575f5ffd5b6136c184828501612ee3565b949350505050565b5f602082840312156136d9575f5ffd5b813567ffffffffffffffff8111156136ef575f5ffd5b6136c18482850161321e565b5f6020828403121561370b575f5ffd5b815180151581146103a8575f5ffd5b818103818111156107cb57634e487b7160e01b5f52601160045260245ffd5b600181811c9082168061374d57607f821691505b60208210810361246657634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b601f82111561046057805f5260205f20601f840160051c810160208510156137b85750805b601f840160051c820191505b818110156123aa575f81556001016137c4565b815167ffffffffffffffff8111156137f1576137f1612de4565b613805816137ff8454613739565b84613793565b6020601f821160018114613837575f83156138205750848201515b5f19600385901b1c1916600184901b1784556123aa565b5f84815260208120601f198516915b828110156138665787850151825560209485019460019092019101613846565b508482101561388357868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8b815260ff8b1660208201526001600160a01b038a1660408201526001600160a01b03891660608201528760808201528660a08201528560c08201528460e0820152836101008201526101606101208201525f6138f3610160830185613562565b9050826101408301529c9b505050505050505050505050565b81515f90829060208501835b82811015613936578151845260209384019390910190600101613918565b509195945050505050565b5f8151808452602084019350602083015f5b828110156135c057815180516001600160a01b031687526020908101518188015260409096019590910190600101613953565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f1985840301885281518051845260208101519050604060208501526139d46040850182613562565b6020998a01999094509290920191506001016139a2565b50909695505050505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156139eb57601f19858403018852613a2f838351613562565b6020988901989093509190910190600101613a13565b805160ff1682525f6020820151613a6760208501826001600160a01b03169052565b506040820151613a8260408501826001600160a01b03169052565b50606082015160608401526080820151608084015260a082015161016060a0850152613ab2610160850182613562565b905060c083015184820360c0860152613acb8282613941565b91505060e083015184820360e0860152613ae58282613986565b915050610100830151848203610100860152613b018282613562565b915050610120830151610120850152610140830151848203610140860152612bda82826139f7565b604081525f613b3b6040830185613a45565b8281036020840152612bda8185613562565b5f60208284031215613b5d575f5ffd5b81516103a88161302a565b5f613b75612f0084612f35565b83815290506020810160608402830185811115613b90575f5ffd5b835b81811015613bee575f60608289031215613baa575f5ffd5b613bb2612df8565b90508151613bbf81612daa565b8152602082810151908201526040820151613bd981612daa565b60408201528352602090920191606001613b92565b5050509392505050565b5f82601f830112613c07575f5ffd5b6103a883835160208501613b68565b5f60208284031215613c26575f5ffd5b815167ffffffffffffffff811115613c3c575f5ffd5b820160808185031215613c4d575f5ffd5b613c55612e68565b8151815260208083015190820152604082015167ffffffffffffffff811115613c7c575f5ffd5b8201601f81018613613c8c575f5ffd5b8051613c9a612f0082612f35565b8082825260208201915060208360061b850101925088831115613cbb575f5ffd5b6020840193505b82841015613d0b576040848a031215613cd9575f5ffd5b613ce1612e21565b8451613cec81612daa565b8152602085810151818301529083526040909401939190910190613cc2565b6040850152505050606082015167ffffffffffffffff811115613d2c575f5ffd5b613d3886828501613bf8565b606083015250949350505050565b5f82601f830112613d55575f5ffd5b8151613d63612f0082612ebc565b818152846020838601011115613d77575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613da3575f5ffd5b815167ffffffffffffffff811115613db9575f5ffd5b820160608185031215613dca575f5ffd5b613dd2612df8565b8151613ddd81612daa565b8152602082015167ffffffffffffffff811115613df8575f5ffd5b613e0486828501613d46565b602083015250604082015167ffffffffffffffff811115613e23575f5ffd5b80830192505084601f830112613e37575f5ffd5b8151613e45612f0082612f35565b8082825260208201915060208360051b860101925087831115613e66575f5ffd5b6020850194505b82851015613e88578451825260209485019490910190613e6d565b6040840152509095945050505050565b805182525f602082015160606020850152613eb66060850182613562565b9050604083015184820360408601528181518084526020840191506020830193505f92505b808310156130cc5783518252602082019150602084019350600183019250613edb565b604081525f613f106040830185613a45565b8281036020840152612bda8185613e98565b602081525f6103a86020830184613590565b5f60208284031215613f44575f5ffd5b815167ffffffffffffffff811115613f5a575f5ffd5b820160408185031215613f6b575f5ffd5b613f73612e21565b81518152602082015167ffffffffffffffff811115613f90575f5ffd5b80830192505084601f830112613fa4575f5ffd5b613fb385835160208501613b68565b6020820152949350505050565b5f60208284031215613fd0575f5ffd5b815167ffffffffffffffff811115613fe6575f5ffd5b820160408185031215613ff7575f5ffd5b613fff612e21565b81518152602082015167ffffffffffffffff81111561401c575f5ffd5b80830192505084601f830112614030575f5ffd5b815161403e612f0082612f35565b8082825260208201915060208360051b86010192508783111561405f575f5ffd5b602085015b838110156140f957805167ffffffffffffffff811115614082575f5ffd5b86016060818b03601f19011215614097575f5ffd5b61409f612df8565b60208201516140ad81612daa565b8152604082015167ffffffffffffffff8111156140c8575f5ffd5b6140d78c602083860101613d46565b6020838101919091526060939093015160408301525084529283019201614064565b506020840152509095945050505050565b602081525f6103a860208301846139f7565b5f6020828403121561412c575f5ffd5b5051919050565b608081525f6141456080830187613a45565b82810360208401526141578187613e98565b9050828103604084015261416b8186613562565b9050828103606084015261417f8185613562565b979650505050505050565b6001600160a01b03851681526001600160a01b0384166020820152608060408201525f6141ba6080830185613562565b905082606083015295945050505050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220fae3247dd7a33c3cd300206de44d56570a43a7690ab25372a739d3c2704b371e64736f6c634300081c00336080604052348015600e575f5ffd5b506109a98061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80635e07a6e614610043578063f18d03cc1461006b578063ff883fcf14610080575b5f5ffd5b610056610051366004610636565b6100a0565b60405190151581526020015b60405180910390f35b61007e61007936600461064f565b6100ec565b005b61009361008e366004610703565b6101cb565b60405161006291906107d3565b5f816001600160a01b03163b5f036100b957505f919050565b6100c2826102b3565b156100cf57506001919050565b6100d882610364565b156100e557506001919050565b505f919050565b6100f5846102b3565b1561017d576040517fbeabacc80000000000000000000000000000000000000000000000000000000081526001600160a01b03848116600483015283811660248301526044820183905285169063beabacc8906064015f604051808303815f87803b158015610162575f5ffd5b505af1158015610174573d5f5f3e3d5ffd5b505050506101c5565b61018684610364565b1561019c57610197848484846103d3565b6101c5565b604051636b94637360e11b81526001600160a01b03851660048201526024015b60405180910390fd5b50505050565b60606101d6856102b3565b1561026d576040517f4ae000410000000000000000000000000000000000000000000000000000000081526001600160a01b03861690634ae0004190610224908790879087906004016107e5565b5f604051808303815f875af115801561023f573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526102669190810190610863565b90506102ab565b61027685610364565b1561028757610266858585856104a2565b604051636b94637360e11b81526001600160a01b03861660048201526024016101bc565b949350505050565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527ff44bac890000000000000000000000000000000000000000000000000000000060048201525f906001600160a01b038316906301ffc9a790602401602060405180830381865afa92505050801561034e575060408051601f3d908101601f1916820190925261034b918101906108a4565b60015b61035957505f919050565b92915050565b919050565b5f816001600160a01b031663e75235b86040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156103bf575060408051601f3d908101601f191682019092526103bc918101906108bd565b60015b6103ca57505f919050565b50600192915050565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b0384161461047f57604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261047a90859085905f6104a2565b61049b565b604080515f81526020810190915261049b9085908490846104a2565b5050505050565b60605f5f866001600160a01b0316635229073f8786885f6040518563ffffffff1660e01b81526004016104d894939291906108d4565b5f604051808303815f875af11580156104f3573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261051a9190810190610928565b9150915084515f146105365761053186838361054b565b610540565b61054082826105c3565b979650505050505050565b6060826105605761055b826105de565b6105bc565b815115801561057757506001600160a01b0384163b155b156105b9576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016101bc565b50805b9392505050565b6060826105d8576105d3826105de565b610359565b50919050565b8051156105ee5780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461035f575f5ffd5b5f60208284031215610646575f5ffd5b6105bc82610620565b5f5f5f5f60808587031215610662575f5ffd5b61066b85610620565b935061067960208601610620565b925061068760408601610620565b9396929550929360600135925050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff811182821017156106d4576106d4610697565b604052919050565b5f67ffffffffffffffff8211156106f5576106f5610697565b50601f01601f191660200190565b5f5f5f5f60808587031215610716575f5ffd5b61071f85610620565b935061072d60208601610620565b9250604085013567ffffffffffffffff811115610748575f5ffd5b8501601f81018713610758575f5ffd5b803561076b610766826106dc565b6106ab565b81815288602083850101111561077f575f5ffd5b816020840160208301375f91810160200191909152949793965093946060013593505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6105bc60208301846107a5565b6001600160a01b0384168152606060208201525f61080660608301856107a5565b9050826040830152949350505050565b5f82601f830112610825575f5ffd5b8151610833610766826106dc565b818152846020838601011115610847575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215610873575f5ffd5b815167ffffffffffffffff811115610889575f5ffd5b6102ab84828501610816565b8051801515811461035f575f5ffd5b5f602082840312156108b4575f5ffd5b6105bc82610895565b5f602082840312156108cd575f5ffd5b5051919050565b6001600160a01b0385168152836020820152608060408201525f6108fb60808301856107a5565b90506002831061091957634e487b7160e01b5f52602160045260245ffd5b82606083015295945050505050565b5f5f60408385031215610939575f5ffd5b61094283610895565b9150602083015167ffffffffffffffff81111561095d575f5ffd5b61096985828601610816565b915050925092905056fea264697066735822122063c7f17d2676ae9340ccf0078bc40725749c3c4dd9198f82832bc411d2d1333764736f6c634300081c00330000000000000000000000006002825babc837776a08799404fa55f15dceda6b0000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b0000000000000000000000000000000000000000000000000000","id":1,"to":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","type":"ONCHAIN_INTERACTION","value":{"_kind":"bigint","value":"0"}},"type":"NETWORK_INTERACTION_REQUEST"} -{"futureId":"Create3Settler#ICreateX.deployCreate3","networkInteractionId":1,"nonce":2,"type":"TRANSACTION_PREPARE_SEND"} -{"futureId":"Create3Settler#ICreateX.deployCreate3","networkInteractionId":1,"nonce":2,"transaction":{"fees":{"maxFeePerGas":{"_kind":"bigint","value":"330105689"},"maxPriorityFeePerGas":{"_kind":"bigint","value":"26357"}},"hash":"0x70b453c357ad30c3d8629a9795b891150a72afbb169ef93c00443b162e394ae7"},"type":"TRANSACTION_SEND"} -{"futureId":"Create3Settler#ICreateX.deployCreate3","hash":"0x70b453c357ad30c3d8629a9795b891150a72afbb169ef93c00443b162e394ae7","networkInteractionId":1,"receipt":{"blockHash":"0xbe6d3f3c5213120d5e05c0738a66be132a51a3bc5299761bf64677d52a453ad4","blockNumber":24033735,"logs":[{"address":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","data":"0x","logIndex":442,"topics":["0x2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c9067","0x0000000000000000000000001a68cee754105b3f806c92f6f59e3175f0b0fc2c","0xd5d101dbdd8374fa4f730d105879acbe6c9855ed7be06a019574ba923a935d7d"]},{"address":"0x609d831C0068844e11eF85a273c7F356212Fd6D1","data":"0x","logIndex":443,"topics":["0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000003a0ce8115b4913f42c4928d6bc3f554e9a81468b"]},{"address":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","data":"0x","logIndex":444,"topics":["0x4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b511","0x000000000000000000000000609d831c0068844e11ef85a273c7f356212fd6d1"]}],"status":"SUCCESS"},"type":"TRANSACTION_CONFIRM"} -{"futureId":"Create3Settler#ICreateX.deployCreate3","result":{"type":"SUCCESS"},"type":"CALL_EXECUTION_STATE_COMPLETE"} -{"artifactId":"Create3Settler#ICreateX","dependencies":["Create3Settler#ICreateX.deployCreate3","Create3Settler#ICreateX"],"emitterAddress":"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed","eventIndex":0,"eventName":"ContractCreation","futureId":"Create3Settler#ICreateX.ContractCreation.newContract.0","nameOrIndex":"newContract","result":"0x609d831C0068844e11eF85a273c7F356212Fd6D1","strategy":"basic","strategyConfig":{},"txToReadFrom":"0x70b453c357ad30c3d8629a9795b891150a72afbb169ef93c00443b162e394ae7","type":"READ_EVENT_ARGUMENT_EXECUTION_STATE_INITIALIZE"} -{"artifactId":"Create3Settler#Settler","contractAddress":"0x609d831C0068844e11eF85a273c7F356212Fd6D1","contractName":"Settler","dependencies":["Create3Settler#ICreateX.ContractCreation.newContract.0"],"futureId":"Create3Settler#Settler","futureType":"NAMED_ARTIFACT_CONTRACT_AT","strategy":"basic","strategyConfig":{},"type":"CONTRACT_AT_EXECUTION_STATE_INITIALIZE"} \ No newline at end of file diff --git a/packages/svm/idls/controller.json b/packages/svm/idls/controller.json index 0e14ac3..e4494a5 100644 --- a/packages/svm/idls/controller.json +++ b/packages/svm/idls/controller.json @@ -25,7 +25,7 @@ "writable": true, "signer": true, "relations": [ - "controller_settings" + "global_settings" ] }, { @@ -33,22 +33,18 @@ "writable": true }, { - "name": "controller_settings", + "name": "global_settings", "pda": { "seeds": [ { "kind": "const", "value": [ - 99, - 111, - 110, - 116, - 114, - 111, + 103, 108, + 111, + 98, + 97, 108, - 101, - 114, 45, 115, 101, @@ -84,41 +80,43 @@ ] }, { - "name": "initialize", + "name": "create_entity_registry", "discriminator": [ - 175, - 175, - 109, - 31, - 13, - 152, - 155, - 237 + 59, + 253, + 73, + 126, + 171, + 188, + 172, + 156 ], "accounts": [ { - "name": "deployer", + "name": "admin", "writable": true, - "signer": true + "signer": true, + "relations": [ + "global_settings" + ] }, { - "name": "controller_settings", - "writable": true, + "name": "entity_registry", + "writable": true + }, + { + "name": "global_settings", "pda": { "seeds": [ { "kind": "const", "value": [ - 99, - 111, - 110, - 116, - 114, - 111, + 103, 108, + 111, + 98, + 97, 108, - 101, - 114, 45, 115, 101, @@ -140,50 +138,51 @@ ], "args": [ { - "name": "admin", + "name": "entity_type", + "type": { + "defined": { + "name": "EntityType" + } + } + }, + { + "name": "entity_pubkey", "type": "pubkey" } ] }, { - "name": "set_admin", + "name": "initialize", "discriminator": [ - 251, - 163, - 0, - 52, - 91, - 194, - 187, - 92 + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 ], "accounts": [ { - "name": "admin", + "name": "deployer", "writable": true, - "signer": true, - "relations": [ - "controller_settings" - ] + "signer": true }, { - "name": "controller_settings", + "name": "global_settings", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ - 99, - 111, - 110, - 116, - 114, - 111, + 103, 108, + 111, + 98, + 97, 108, - 101, - 114, 45, 115, 101, @@ -197,26 +196,30 @@ } ] } + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" } ], "args": [ { - "name": "new_admin", + "name": "admin", "type": "pubkey" } ] }, { - "name": "set_allowed_entity", + "name": "set_admin", "discriminator": [ - 140, - 26, - 199, - 225, - 147, - 47, - 14, - 29 + 251, + 163, + 0, + 52, + 91, + 194, + 187, + 92 ], "accounts": [ { @@ -224,30 +227,23 @@ "writable": true, "signer": true, "relations": [ - "controller_settings" + "global_settings" ] }, { - "name": "entity_registry", - "writable": true - }, - { - "name": "controller_settings", + "name": "global_settings", + "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ - 99, - 111, - 110, - 116, - 114, - 111, + 103, 108, + 111, + 98, + 97, 108, - 101, - 114, 45, 115, 101, @@ -261,23 +257,11 @@ } ] } - }, - { - "name": "system_program", - "address": "11111111111111111111111111111111" } ], "args": [ { - "name": "entity_type", - "type": { - "defined": { - "name": "EntityType" - } - } - }, - { - "name": "entity_pubkey", + "name": "new_admin", "type": "pubkey" } ] diff --git a/packages/svm/programs/controller/Cargo.toml b/packages/svm/programs/controller/Cargo.toml index 29e05b6..bd4a3c1 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" +license = "GPL-3.0-only" [lib] crate-type = ["cdylib", "lib"] From 391eb43c379db9cec815a9cffe9ab28cfa0494ea Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Tue, 6 Jan 2026 15:11:48 -0300 Subject: [PATCH 38/39] Correct controller.test.ts file --- packages/svm/tests/controller.test.ts | 453 +++++++++++++------------- 1 file changed, 222 insertions(+), 231 deletions(-) diff --git a/packages/svm/tests/controller.test.ts b/packages/svm/tests/controller.test.ts index f38dcaa..9288c40 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,136 +70,179 @@ 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.controllerSettings.fetch(deployerSdk.getControllerSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(admin.publicKey.toString()) - }) + const settings = await program.account.controllerSettings.fetch(deployerSdk.getControllerSettingsPubkey()) + 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) + it('can set admin', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) - const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) - expect(settings.admin.toString()).to.be.eq(otherAdmin.publicKey.toString()) - }) + const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) + 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() - 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 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') }) + }) - 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) + 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) - expectTransactionError(res, 'Only admin can call this instruction') - }) + 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()) }) - 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) + 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.Validator, validator) - ) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Axia, axia) + ) - expect(entityRegistry.entityType).to.deep.include({ validator: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(validator.toString()) - }) + expect(entityRegistry.entityType).to.deep.include({ axia: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.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 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.Axia, axia) - ) + const entityRegistry = await program.account.entityRegistry.fetch( + adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + ) - expect(entityRegistry.entityType).to.deep.include({ axia: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(axia.toString()) - }) + expect(entityRegistry.entityType).to.deep.include({ solver: {} }) + expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.toString()) + }) - it('should create entity registry successfully (solver)', async () => { - const ix = await adminSdk.setAllowedEntityIx(EntityType.Solver, solver) - await makeTxSignAndSend(adminProvider, ix) + it('should change admin for next tests', async () => { + const ix = await adminSdk.setAdmin(otherAdmin.publicKey) + await makeTxSignAndSend(adminProvider, ix) - const entityRegistry = await program.account.entityRegistry.fetch( - adminSdk.getEntityRegistryPubkey(EntityType.Solver, solver) + const settings = await program.account.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) + 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) ) + expect.fail('Entity registry should not exist after closing') + } catch (error: any) { + expect(error.message).to.include('Account does not exist') + } + }) - expect(entityRegistry.entityType).to.deep.include({ solver: {} }) - expect(entityRegistry.entityPubkey.toString()).to.be.eq(solver.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 change admin for next tests', async () => { - const ix = await adminSdk.setAdmin(otherAdmin.publicKey) - await makeTxSignAndSend(adminProvider, ix) + 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.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) 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) @@ -214,24 +257,62 @@ describe('Controller Program', () => { } }) - it('should create entity registry successfully (axia)', async () => { - const ix = await adminSdk.setAllowedEntityIx(EntityType.Axia, axia) - await makeTxSignAndSend(adminProvider, ix) + 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( - 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: {} }) @@ -239,149 +320,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.controllerSettings.fetch(adminSdk.getControllerSettingsPubkey()) - 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) - - 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 another axia registry', async () => { + const ix = await otherAdminSdk.setAllowedEntityIx(EntityType.Axia, axia2) + await makeTxSignAndSend(otherAdminProvider, ix) - 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()) }) }) }) From ce6dba55702d8ddba49b29813e9449d2ef43a884 Mon Sep 17 00:00:00 2001 From: GuidoDipietro Date: Wed, 7 Jan 2026 12:58:19 -0300 Subject: [PATCH 39/39] Code review: remove batch capabilities from claim_stale_proposal --- .../src/instructions/claim_stale_intent.rs | 10 +--- .../src/instructions/claim_stale_proposal.rs | 40 ++++--------- .../src/instructions/create_proposal.rs | 2 +- packages/svm/sdks/settler/Settler.ts | 14 ++--- packages/svm/tests/settler.test.ts | 60 +++---------------- 5 files changed, 24 insertions(+), 102 deletions(-) diff --git a/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs b/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs index d5b397e..9a3f6f1 100644 --- a/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs +++ b/packages/svm/programs/settler/src/instructions/claim_stale_intent.rs @@ -11,17 +11,11 @@ pub struct ClaimStaleIntent<'info> { mut, close = creator, has_one = creator @ SettlerError::IncorrectIntentCreator, + constraint = Clock::get()?.unix_timestamp as u64 > intent.deadline @ SettlerError::IntentNotYetExpired )] pub intent: Box>, } -pub fn claim_stale_intent(ctx: Context) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; - - require!( - ctx.accounts.intent.deadline < now, - SettlerError::IntentNotYetExpired - ); - +pub fn claim_stale_intent(_ctx: Context) -> Result<()> { Ok(()) } diff --git a/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs b/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs index 75bf93f..3ec7674 100644 --- a/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/claim_stale_proposal.rs @@ -5,37 +5,17 @@ use crate::{errors::SettlerError, state::Proposal}; #[derive(Accounts)] pub struct ClaimStaleProposal<'info> { #[account(mut)] - pub proposal_creator: Signer<'info>, - // - // remaining_accounts (N): - // - // #[account( - // mut, - // close = proposal_creator, - // has_one = proposal_creator @ SettlerError::IncorrectProposalCreator - // )] - // pub proposal_n: Box>, -} - -pub fn claim_stale_proposal<'info>( - ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposal<'info>>, -) -> Result<()> { - let now = Clock::get()?.unix_timestamp as u64; - let proposal_creator = ctx.accounts.proposal_creator.to_account_info(); - - for account_info in ctx.remaining_accounts { - let proposal: Box> = - Box::new(Account::::try_from(account_info)?); + pub creator: Signer<'info>, - require_keys_eq!( - proposal.creator, - proposal_creator.key(), - SettlerError::IncorrectProposalCreator - ); - require!(now > proposal.deadline, SettlerError::ProposalNotYetExpired); - - proposal.close(ctx.accounts.proposal_creator.to_account_info())?; - } + #[account( + mut, + close = creator, + has_one = creator @ SettlerError::IncorrectProposalCreator, + constraint = Clock::get()?.unix_timestamp as u64 > proposal.deadline @ SettlerError::ProposalNotYetExpired + )] + pub proposal: Box>, +} +pub fn claim_stale_proposal(_ctx: Context) -> Result<()> { Ok(()) } diff --git a/packages/svm/programs/settler/src/instructions/create_proposal.rs b/packages/svm/programs/settler/src/instructions/create_proposal.rs index d62a1d8..598df6a 100644 --- a/packages/svm/programs/settler/src/instructions/create_proposal.rs +++ b/packages/svm/programs/settler/src/instructions/create_proposal.rs @@ -27,7 +27,7 @@ pub struct CreateProposal<'info> { seeds = [b"fulfilled-intent", intent.hash.as_ref()], bump )] - /// This PDA must be uninitialized + /// This PDA must be uninitialized (checked by SystemAccount type) pub fulfilled_intent: SystemAccount<'info>, #[account( diff --git a/packages/svm/sdks/settler/Settler.ts b/packages/svm/sdks/settler/Settler.ts index 8ad89ba..48d07ee 100644 --- a/packages/svm/sdks/settler/Settler.ts +++ b/packages/svm/sdks/settler/Settler.ts @@ -146,7 +146,7 @@ export default class SettlerSDK { const ix = await this.program.methods .addInstructionsToProposal(parsedInstructions, finalize) .accountsPartial({ - proposalCreator: this.getSignerKey(), + creator: this.getSignerKey(), proposal: this.getProposalKey(intentHashHex, solver), }) .instruction() @@ -155,21 +155,15 @@ export default class SettlerSDK { } async claimStaleProposalIx( - intentHashesHex: string[], + intentHashHex: string, solverPubkey?: web3.PublicKey ): Promise { const ix = await this.program.methods .claimStaleProposal() .accountsPartial({ - proposalCreator: this.getSignerKey(), + creator: this.getSignerKey(), + proposal: this.getProposalKey(intentHashHex, solverPubkey), }) - .remainingAccounts( - intentHashesHex.map((intentHashHex) => ({ - pubkey: this.getProposalKey(intentHashHex, solverPubkey), - isWritable: true, - isSigner: false, - })) - ) .instruction() return ix diff --git a/packages/svm/tests/settler.test.ts b/packages/svm/tests/settler.test.ts index e98a070..ad86680 100644 --- a/packages/svm/tests/settler.test.ts +++ b/packages/svm/tests/settler.test.ts @@ -1731,7 +1731,7 @@ describe('Settler Program', () => { Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0 const proposalCreatorBalanceBefore = Number(provider.client.getBalance(proposalBefore.creator)) || 0 - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalIx(intentHash) await makeTxSignAndSend(solverProvider, ix) const proposalBalanceAfter = @@ -1751,52 +1751,6 @@ describe('Settler Program', () => { expect(proposalBalanceAfter).to.be.eq(0) }) - it('should claim multiple stale proposals', async () => { - const now = Number(client.getClock().unixTimestamp) - const deadline = now + STALE_CLAIM_DELAY - const intentHashes = await Promise.all( - Array.from({ length: 20 }, async () => await createTestProposalWithDeadline(deadline)) - ) - - for (const intentHash of intentHashes) { - const proposalBefore = await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect(proposalBefore).to.not.be.null - } - - warpSeconds(provider, STALE_CLAIM_DELAY_PLUS_ONE) - - const proposalBalancesBefore = intentHashes.reduce( - (acc, intentHash) => - acc + Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0, - 0 - ) - const proposalCreatorBalanceBefore = Number(provider.client.getBalance(solver.publicKey)) || 0 - - const ix = await solverSdk.claimStaleProposalIx(intentHashes) - await makeTxSignAndSend(solverProvider, ix) - - const proposalBalancesAfter = intentHashes.reduce( - (acc, intentHash) => - acc + Number(provider.client.getBalance(sdk.getProposalKey(intentHash, solver.publicKey))) || 0, - 0 - ) - const proposalCreatorBalanceAfter = Number(provider.client.getBalance(solver.publicKey)) || 0 - - for (const intentHash of intentHashes) { - try { - await program.account.proposal.fetch(sdk.getProposalKey(intentHash, solver.publicKey)) - expect.fail('Proposal account should be closed') - } catch (error: any) { - expect(error.message).to.include(`Account does not exist`) - } - } - - expect(proposalCreatorBalanceAfter).to.be.eq( - proposalCreatorBalanceBefore + proposalBalancesBefore - ACCOUNT_CLOSE_FEE - ) - expect(proposalBalancesAfter).to.be.eq(0) - }) - it('cannot claim proposal if deadline has not passed', async () => { const now = Number(client.getClock().unixTimestamp) const deadline = now + LONG_DEADLINE @@ -1804,7 +1758,7 @@ describe('Settler Program', () => { warpSeconds(provider, WARP_TIME_SHORT) - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalIx(intentHash) const res = await makeTxSignAndSend(solverProvider, ix) expectTransactionError(res, `Proposal not yet expired`) @@ -1817,7 +1771,7 @@ describe('Settler Program', () => { warpSeconds(provider, MEDIUM_DEADLINE) - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalIx(intentHash) const res = await makeTxSignAndSend(solverProvider, ix) expectTransactionError(res, `Proposal not yet expired`) @@ -1830,7 +1784,7 @@ describe('Settler Program', () => { warpSeconds(provider, EXPIRATION_TEST_DELAY_PLUS_ONE) - const ix = await maliciousSdk.claimStaleProposalIx([intentHash], solver.publicKey) + const ix = await maliciousSdk.claimStaleProposalIx(intentHash, solver.publicKey) const res = await makeTxSignAndSend(maliciousProvider, ix) expectTransactionError(res, `Signer must be proposal creator`) @@ -1839,7 +1793,7 @@ describe('Settler Program', () => { it('cannot claim non-existent proposal', async () => { const intentHash = generateIntentHash() - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalIx(intentHash) const res = await makeTxSignAndSend(solverProvider, ix) expectTransactionError(res, `AccountNotInitialized`) @@ -1852,11 +1806,11 @@ describe('Settler Program', () => { warpSeconds(provider, DOUBLE_CLAIM_DELAY_PLUS_ONE) - const ix = await solverSdk.claimStaleProposalIx([intentHash]) + const ix = await solverSdk.claimStaleProposalIx(intentHash) await makeTxSignAndSend(solverProvider, ix) client.expireBlockhash() - const ix2 = await solverSdk.claimStaleProposalIx([intentHash]) + const ix2 = await solverSdk.claimStaleProposalIx(intentHash) const res = await makeTxSignAndSend(solverProvider, ix2) const errorMsg = res.toString()