Skip to content

Commit 3288f82

Browse files
Dodecahedr0xbmuddha
authored andcommitted
feat: check risk strategies
1 parent c0554ec commit 3288f82

7 files changed

Lines changed: 178 additions & 7 deletions

File tree

config.example.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ risk-server-url = "http://127.0.0.1:3001"
222222
# Env: MBV_CHAINLINK__RISK__REQUEST_TIMEOUT
223223
request-timeout = "5s"
224224

225+
# Which post-delegation action signers to risk check.
226+
# "all-signers": check every signer of every action (conservative).
227+
# "relevant-programs": only check when an action involves the SPL Token,
228+
# ephemeral SPL (eATA/ESPL), or Magic program.
229+
# Default: "relevant-programs"
230+
# Env: MBV_CHAINLINK__RISK__CHECK_STRATEGY
231+
check-strategy = "relevant-programs"
232+
225233
# ==============================================================================
226234
# Leader Administration (Optional)
227235
# ==============================================================================

magicblock-aml/src/lib.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//! asks the server whether a set of addresses is risky.
77
88
use futures_util::future::try_join_all;
9-
use magicblock_config::config::RiskConfig;
9+
use magicblock_config::config::{AmlCheckStrategy, RiskConfig};
1010
use reqwest::{Client, redirect};
1111
use serde::Deserialize;
1212
use thiserror::Error;
@@ -68,6 +68,7 @@ fn validate_base_url(base_url: &str) -> RiskResult<()> {
6868
pub struct RiskService {
6969
client: Client,
7070
base_url: String,
71+
check_strategy: AmlCheckStrategy,
7172
}
7273

7374
impl RiskService {
@@ -90,7 +91,17 @@ impl RiskService {
9091
.build()
9192
.map_err(RiskError::ClientBuild)?;
9293

93-
Ok(Some(Self { client, base_url }))
94+
Ok(Some(Self {
95+
client,
96+
base_url,
97+
check_strategy: config.check_strategy,
98+
}))
99+
}
100+
101+
/// The configured strategy for deciding which post-delegation action
102+
/// signers to risk check.
103+
pub fn check_strategy(&self) -> AmlCheckStrategy {
104+
self.check_strategy
94105
}
95106

96107
/// Asks the risk server about each address concurrently, returning
@@ -234,6 +245,7 @@ mod tests {
234245
enabled: true,
235246
risk_server_url,
236247
request_timeout: Duration::from_secs(2),
248+
check_strategy: AmlCheckStrategy::AllSigners,
237249
}
238250
}
239251

magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ use engine::Engine;
2222
use keeper::MissingAccount;
2323
use lru::LruCache;
2424
use magicblock_aml::RiskService;
25-
use magicblock_config::config::AllowedProgram;
25+
use magicblock_config::config::{AllowedProgram, AmlCheckStrategy};
2626
use magicblock_core::token_programs::{
27-
ASSOCIATED_TOKEN_PROGRAM_ID, EATA_PROGRAM_ID, TOKEN_PROGRAM_ID, is_ata,
28-
normalize_native_token_account_for_local_clone,
27+
ASSOCIATED_TOKEN_PROGRAM_ID, EATA_PROGRAM_ID, TOKEN_2022_PROGRAM_ID,
28+
TOKEN_PROGRAM_ID, is_ata, normalize_native_token_account_for_local_clone,
2929
};
3030
use magicblock_metrics::metrics::{
3131
self, AccountFetchContext, AccountFetchReason, BankPrecheckOutcome,
@@ -333,6 +333,42 @@ fn log_companion_fetch_failure<E: std::fmt::Display + ?Sized>(
333333
);
334334
}
335335

336+
/// Programs whose presence in a post-delegation action triggers a risk check
337+
/// under [`AmlCheckStrategy::RelevantPrograms`]: SPL Token (legacy and 2022),
338+
/// the ephemeral SPL / eATA program (ESPL), and the Magic program.
339+
const RISK_RELEVANT_PROGRAMS: [Pubkey; 4] = [
340+
TOKEN_PROGRAM_ID,
341+
TOKEN_2022_PROGRAM_ID,
342+
EATA_PROGRAM_ID,
343+
magicblock_magic_program_api::ID,
344+
];
345+
346+
/// Decides whether the configured [`AmlCheckStrategy`] requires risk checking
347+
/// the signers of these post-delegation actions.
348+
fn delegation_actions_require_risk_check(
349+
strategy: AmlCheckStrategy,
350+
delegation_actions: &DelegationActions,
351+
) -> bool {
352+
match strategy {
353+
AmlCheckStrategy::AllSigners => true,
354+
AmlCheckStrategy::RelevantPrograms => delegation_actions
355+
.iter()
356+
.any(instruction_involves_risk_relevant_program),
357+
}
358+
}
359+
360+
/// Returns true when a risk-relevant program is invoked by the instruction or
361+
/// referenced by any of its accounts (e.g. as the target of a CPI).
362+
fn instruction_involves_risk_relevant_program(
363+
instruction: &solana_instruction::Instruction,
364+
) -> bool {
365+
RISK_RELEVANT_PROGRAMS.contains(&instruction.program_id)
366+
|| instruction
367+
.accounts
368+
.iter()
369+
.any(|meta| RISK_RELEVANT_PROGRAMS.contains(&meta.pubkey))
370+
}
371+
336372
impl<T, U> FetchCloner<T, U>
337373
where
338374
T: ChainRpcClient,
@@ -1570,6 +1606,13 @@ where
15701606
return Ok(());
15711607
};
15721608

1609+
if !delegation_actions_require_risk_check(
1610+
risk_service.check_strategy(),
1611+
delegation_actions,
1612+
) {
1613+
return Ok(());
1614+
}
1615+
15731616
let mut signers = delegation_actions
15741617
.iter()
15751618
.flat_map(|instruction| {

magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,89 @@ fn clone_request_classification() {
8282
ChainlinkCloneIntent::ActionDependency
8383
);
8484
}
85+
86+
mod aml_check_strategy {
87+
use solana_instruction::AccountMeta;
88+
89+
use super::*;
90+
91+
fn action_for_program(program_id: Pubkey) -> Instruction {
92+
Instruction::new_with_bytes(program_id, &[], vec![])
93+
}
94+
95+
fn action_referencing(program_id: Pubkey, account: Pubkey) -> Instruction {
96+
Instruction::new_with_bytes(
97+
program_id,
98+
&[],
99+
vec![AccountMeta::new_readonly(account, false)],
100+
)
101+
}
102+
103+
#[test]
104+
fn all_signers_strategy_always_requires_check() {
105+
// An action that touches no risk-relevant program still gets checked.
106+
let actions: DelegationActions =
107+
vec![action_for_program(Pubkey::new_unique())].into();
108+
assert!(delegation_actions_require_risk_check(
109+
AmlCheckStrategy::AllSigners,
110+
&actions,
111+
));
112+
}
113+
114+
#[test]
115+
fn relevant_programs_strategy_skips_unrelated_actions() {
116+
let actions: DelegationActions = vec![
117+
action_for_program(Pubkey::new_unique()),
118+
action_for_program(Pubkey::new_unique()),
119+
]
120+
.into();
121+
assert!(!delegation_actions_require_risk_check(
122+
AmlCheckStrategy::RelevantPrograms,
123+
&actions,
124+
));
125+
}
126+
127+
#[test]
128+
fn relevant_programs_strategy_matches_each_relevant_program() {
129+
for program in [
130+
TOKEN_PROGRAM_ID,
131+
TOKEN_2022_PROGRAM_ID,
132+
EATA_PROGRAM_ID,
133+
magicblock_magic_program_api::ID,
134+
] {
135+
let actions: DelegationActions =
136+
vec![action_for_program(program)].into();
137+
assert!(
138+
delegation_actions_require_risk_check(
139+
AmlCheckStrategy::RelevantPrograms,
140+
&actions,
141+
),
142+
"program {program} invoked as program_id should require check",
143+
);
144+
145+
// Referenced as a CPI target account, not the invoked program.
146+
let actions: DelegationActions =
147+
vec![action_referencing(Pubkey::new_unique(), program)].into();
148+
assert!(
149+
delegation_actions_require_risk_check(
150+
AmlCheckStrategy::RelevantPrograms,
151+
&actions,
152+
),
153+
"program {program} referenced as account should require check",
154+
);
155+
}
156+
}
157+
158+
#[test]
159+
fn relevant_programs_strategy_matches_when_any_action_is_relevant() {
160+
let actions: DelegationActions = vec![
161+
action_for_program(Pubkey::new_unique()),
162+
action_for_program(EATA_PROGRAM_ID),
163+
]
164+
.into();
165+
assert!(delegation_actions_require_risk_check(
166+
AmlCheckStrategy::RelevantPrograms,
167+
&actions,
168+
));
169+
}
170+
}

magicblock-chainlink/tests/10_aml_undelegation.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use dlp_api::{
1616
};
1717
use magicblock_aml::RiskService;
1818
use magicblock_chainlink::testing::{context::TestContext, init_logger};
19-
use magicblock_config::config::RiskConfig;
19+
use magicblock_config::config::{AmlCheckStrategy, RiskConfig};
2020
use solana_account::{Account, AccountMode};
2121
use solana_pubkey::Pubkey;
2222
use tokio::task::JoinHandle;
@@ -107,6 +107,7 @@ fn risk_config(risk_server_url: String) -> RiskConfig {
107107
enabled: true,
108108
risk_server_url,
109109
request_timeout: Duration::from_secs(2),
110+
check_strategy: AmlCheckStrategy::AllSigners,
110111
}
111112
}
112113

magicblock-config/src/config/chain.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,23 @@ impl Default for ChainLinkConfig {
7373
}
7474
}
7575

76+
/// Strategy for deciding which post-delegation action signers get AML/risk
77+
/// checked.
78+
#[derive(
79+
Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Default,
80+
)]
81+
#[serde(rename_all = "kebab-case")]
82+
pub enum AmlCheckStrategy {
83+
/// Check every signer of every post-delegation action, regardless of which
84+
/// programs the action invokes.
85+
AllSigners,
86+
/// Only check signers when a post-delegation action involves the SPL Token,
87+
/// ephemeral SPL (eATA/ESPL), or Magic program. Actions touching none of
88+
/// these programs skip the risk check entirely.
89+
#[default]
90+
RelevantPrograms,
91+
}
92+
7693
/// Configuration for checking address risk against the risk server. The risk
7794
/// server owns the upstream provider credentials, caching, and threshold; the
7895
/// validator is a thin client.
@@ -86,6 +103,8 @@ pub struct RiskConfig {
86103
/// Request timeout for risk server calls.
87104
#[serde(with = "humantime")]
88105
pub request_timeout: Duration,
106+
/// Which post-delegation action signers to risk check.
107+
pub check_strategy: AmlCheckStrategy,
89108
}
90109

91110
impl Default for RiskConfig {
@@ -96,6 +115,7 @@ impl Default for RiskConfig {
96115
request_timeout: Duration::from_secs(
97116
consts::DEFAULT_RISK_REQUEST_TIMEOUT_SEC,
98117
),
118+
check_strategy: AmlCheckStrategy::default(),
99119
}
100120
}
101121
}

magicblock-config/src/config/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ pub mod program;
1010

1111
pub use aperture::ApertureConfig;
1212
pub use chain::{
13-
AdminConfig, AllowedProgram, ChainLinkConfig, CommittorConfig, RiskConfig,
13+
AdminConfig, AllowedProgram, AmlCheckStrategy, ChainLinkConfig,
14+
CommittorConfig, RiskConfig,
1415
};
1516
pub use engine::{EngineConfig, FollowerReplication, LeaderReplication};
1617
pub use grpc::GrpcConfig;

0 commit comments

Comments
 (0)