{"record":{"id":"cc8e11d24bcc50e7","repo":"linera-io/linera-protocol","slug":"owner-should-be-different-from-spender","errorCode":null,"errorMessage":"owner should be different from spender","messagePattern":"owner should be different from spender","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-base/src/identifiers.rs","lineNumber":212,"sourceCode":"        }\n    }\n}\n\n/// A pair of owner and spender accounts for managing allowances.\n#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Allocative)]\npub struct OwnerSpender {\n    /// Account to withdraw from\n    pub owner: AccountOwner,\n    /// Account to do the withdrawing\n    pub spender: AccountOwner,\n}\n\nimpl OwnerSpender {\n    /// Creates a new `OwnerSpender` pair.\n    /// Panics if owner and spender are the same.\n    pub fn new(owner: AccountOwner, spender: AccountOwner) -> Self {\n        if owner == spender {\n            panic!(\"owner should be different from spender\");\n        }\n        Self { owner, spender }\n    }\n}\n\n/// The unique identifier (UID) of a chain. This is currently computed as the hash value\n/// of a [`ChainDescription`].\n#[derive(\n    Eq,\n    PartialEq,\n    Ord,\n    PartialOrd,\n    Copy,\n    Clone,\n    Hash,\n    Serialize,\n    Deserialize,\n    WitLoad,","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-base/src/identifiers.rs#L194-L230","documentation":"OwnerSpender models a token-allowance pair: the owner account that holds funds and a different spender account authorized to withdraw. OwnerSpender::new panics by design when owner == spender, because a self-allowance is meaningless in the approval model. This is a Rust panic, not a Result — it crashes the calling thread/task.","triggerScenarios":"Constructing OwnerSpender::new(owner, spender) with the same AccountOwner in both roles: an app defaulting spender to the caller's own account, a UI that lets a user pick themselves as spender, or deserialized data where both fields ended up identical.","commonSituations":"Building approval/allowance flows (approve + transfer_from style) where the caller is also the funds owner; tests constructing the pair with two copies of the same constant address; copy-paste where owner was pasted into the spender field.","solutions":["Validate owner != spender before calling new and return a proper error instead of letting it panic.","In UIs, prevent selecting the owner's own account as spender (filter it out).","In approval flows, take the spender from a distinct party (e.g. the app's/service's account), never from the caller by default."],"exampleFix":"// before\nlet pair = OwnerSpender::new(owner, owner); // panic!\n\n// after\nif owner == spender { return Err(\"spender must differ from owner\"); }\nlet pair = OwnerSpender::new(owner, spender);","handlingStrategy":"validation","validationCode":"fn build_pair(owner: AccountOwner, spender: AccountOwner) -> Result<OwnerSpender, String> {\n    if owner == spender {\n        return Err(\"spender must be different from owner\".into());\n    }\n    Ok(OwnerSpender::new(owner, spender))\n}","typeGuard":"fn distinct_accounts(owner: &AccountOwner, spender: &AccountOwner) -> bool {\n    owner != spender\n}","tryCatchPattern":null,"preventionTips":["Validate owner != spender at the API/UI boundary before constructing the pair.","In approval UIs, exclude the owner's own account from the spender selection list.","Unit-test the guard so the panic path is never reachable in production."],"tags":["rust","allowance","accounts","panic","validation"],"backgroundTag":"invalid-account-pair","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}