Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions contracts/examples/plugin-policy-example-reverts/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![no_std]
use smart_account_interfaces::{SmartAccountPlugin, SmartAccountPolicy};
use smart_account_interfaces::{PluginRejection, SmartAccountPlugin, SmartAccountPolicy};
use soroban_sdk::{
auth::{Context, ContractContext},
contract, contractimpl, contracttype, symbol_short, Address, Env, Symbol, TryFromVal, Vec,
Expand Down Expand Up @@ -41,7 +41,7 @@ impl SmartAccountPlugin for PluginPolicyContractReverts {
source.require_auth();
}

fn on_auth(env: &Env, source: Address, contexts: Vec<Context>) {
fn on_auth(env: &Env, source: Address, contexts: Vec<Context>) -> Result<(), PluginRejection> {
source.require_auth();
// Increment the internal counter
let current_counter: u32 = env.storage().instance().get(&AUTH_COUNTER_KEY).unwrap_or(0);
Expand All @@ -60,6 +60,8 @@ impl SmartAccountPlugin for PluginPolicyContractReverts {
counter: new_counter,
},
);

Ok(())
}
}

Expand Down
6 changes: 4 additions & 2 deletions contracts/examples/plugin-policy-example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![no_std]
use smart_account_interfaces::{SmartAccountPlugin, SmartAccountPolicy};
use smart_account_interfaces::{PluginRejection, SmartAccountPlugin, SmartAccountPolicy};
use soroban_sdk::{
auth::{Context, ContractContext},
contract, contractimpl, contracttype, symbol_short, Address, Env, Symbol, TryFromVal, Vec,
Expand Down Expand Up @@ -36,7 +36,7 @@ impl SmartAccountPlugin for PluginPolicyContract {
source.require_auth();
}

fn on_auth(env: &Env, source: Address, contexts: Vec<Context>) {
fn on_auth(env: &Env, source: Address, contexts: Vec<Context>) -> Result<(), PluginRejection> {
source.require_auth();
// Increment the internal counter
let current_counter: u32 = env.storage().instance().get(&AUTH_COUNTER_KEY).unwrap_or(0);
Expand All @@ -55,6 +55,8 @@ impl SmartAccountPlugin for PluginPolicyContract {
counter: new_counter,
},
);

Ok(())
}
}

Expand Down
11 changes: 11 additions & 0 deletions contracts/smart-account-interfaces/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
use soroban_sdk::contracterror;

/// Dedicated rejection signal for the plugin `on_auth` interface.
///
/// Plugins return `Err(PluginRejection::Rejected)` to intentionally block
/// authorization. Any panic is treated as a technical failure and skipped.
#[contracterror]
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u32)]
pub enum PluginRejection {
Rejected = 1,
}

#[contracterror]
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u32)]
Expand Down
2 changes: 1 addition & 1 deletion contracts/smart-account-interfaces/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ pub use auth::types::{
SignerKey, SignerPolicy, SignerRole, SpendTrackerKey, SpendingTracker, TokenTransferPolicy,
WebauthnSigner,
};
pub use error::SmartAccountError;
pub use error::{PluginRejection, SmartAccountError};
pub use plugin::{SmartAccountPlugin, SmartAccountPluginClient};
3 changes: 2 additions & 1 deletion contracts/smart-account-interfaces/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::error::PluginRejection;
use soroban_sdk::{auth::Context, contractclient, Address, Env, Vec};

#[contractclient(name = "SmartAccountPluginClient")]
pub trait SmartAccountPlugin {
fn on_install(env: &Env, source: Address);
fn on_uninstall(env: &Env, source: Address);
fn on_auth(env: &Env, source: Address, contexts: Vec<Context>);
fn on_auth(env: &Env, source: Address, contexts: Vec<Context>) -> Result<(), PluginRejection>;
}
31 changes: 22 additions & 9 deletions contracts/smart-account/src/auth/core/authorizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::error::Error;
use crate::events::PluginAuthFailedEvent;
use smart_account_interfaces::SmartAccountPluginClient;
use smart_account_interfaces::{Signer, SignerKey, SignerRole};
use soroban_sdk::{auth::Context, crypto::Hash, Env, Vec};
use soroban_sdk::{Address, Map, String, Symbol};
use soroban_sdk::auth::Context;
use soroban_sdk::{crypto::Hash, Address, Env, InvokeError, Map, String, Symbol, Vec};
use storage::Storage;

pub struct Authorizer;
Expand Down Expand Up @@ -92,10 +92,10 @@ impl Authorizer {
let res = SmartAccountPluginClient::new(env, &plugin)
.try_on_auth(&env.current_contract_address(), auth_contexts);
match res {
// Plugin executed successfully
// Plugin approved (new-style Ok(()) or old-style void return —
// both produce Void at the ABI level).
Ok(Ok(_)) => {}
// Plugin return value conversion failure (ABI mismatch)
// Treat as technical failure: log and continue
// Return value conversion failure (ABI mismatch) — skip.
Ok(Err(_)) => {
env.events().publish(
(TOPIC_PLUGIN, &plugin, VERB_AUTH_FAILED),
Expand All @@ -105,7 +105,7 @@ impl Authorizer {
},
);
}
// Plugin intentionally rejected (contracterror / panic_with_error!)
// Plugin explicitly rejected via Err(PluginRejection::Rejected).
Err(Ok(_)) => {
env.events().publish(
(TOPIC_PLUGIN, &plugin, VERB_AUTH_FAILED),
Expand All @@ -116,9 +116,22 @@ impl Authorizer {
);
return Err(Error::PluginOnAuthFailed);
}
// Plugin had a technical failure (panic!, host trap, TTL expiry)
// Non-blocking: log and continue to next plugin
Err(Err(_)) => {
// Old-style rejection: panic_with_error! with a contract error
// code that doesn't match PluginRejection. Treat as rejection
// to preserve backwards compatibility with existing plugins.
Err(Err(InvokeError::Contract(_))) => {
env.events().publish(
(TOPIC_PLUGIN, &plugin, VERB_AUTH_FAILED),
PluginAuthFailedEvent {
plugin: plugin.clone(),
error: String::from_str(env, "Plugin rejected authorization"),
},
);
return Err(Error::PluginOnAuthFailed);
}
// Technical failure: bare panic!, missing function, host trap,
// budget exhaustion, expired TTL. Non-blocking — skip.
Err(Err(InvokeError::Abort)) => {
env.events().publish(
(TOPIC_PLUGIN, &plugin, VERB_AUTH_FAILED),
PluginAuthFailedEvent {
Expand Down
Loading
Loading