diff --git a/SECURITY.md b/SECURITY.md index af7e3cf5..30db21be 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -139,7 +139,7 @@ The Revenue Pool contract (`contracts/revenue_pool`) operates under the followin - *Mitigation:* The deployment process must verify the official Stellar USDC (or appropriate wrapped USDC) contract address before initialization. The `init` function guards against re-initialization. - **Operational Griefing (Balances):** Anyone can effectively transfer USDC to the revenue pool. If an attacker sends unsolicited funds, it increases the `balance()` but does not disrupt the `distribute` logic, as distribution is explicitly controlled by the admin. - - *Mitigation:* The pool does not rely on strict balance equality invariants for its core operations, mitigating balance-based operational griefing. Off-chain monitoring should track `receive_payment` events and native token transfers to reconcile expected vs. actual balances. + - *Mitigation:* The pool does not rely on strict balance equality invariants for its core operations, mitigating balance-based operational griefing. The `receive_payment` entrypoint is admin-only and event-only (no token movement), so indexers should reconcile `receive_payment` logs with actual token transfers. ### Input Validation diff --git a/contracts/revenue_pool/src/lib.rs b/contracts/revenue_pool/src/lib.rs index 1bc2fc23..47c2deaa 100644 --- a/contracts/revenue_pool/src/lib.rs +++ b/contracts/revenue_pool/src/lib.rs @@ -144,7 +144,7 @@ impl RevenuePool { /// /// # Arguments /// * `env` - The environment running the contract. - /// * `caller` - Must be admin (or could be extended to allow vault to call). + /// * `caller` - Must be the current admin. /// * `amount` - Amount received (for event logging). /// * `from_vault` - Optional; true if the source was the vault. /// @@ -152,7 +152,8 @@ impl RevenuePool { /// * If the caller is not the current admin (`"unauthorized: caller is not admin"`). /// /// # Events - /// Emits a `receive_payment` event with `caller` as a topic, and a tuple of `(amount, from_vault)` as data. + /// Emits a `receive_payment` event with `caller` as a topic, and a tuple of + /// `(amount, from_vault)` as data. pub fn receive_payment(env: Env, caller: Address, amount: i128, from_vault: bool) { caller.require_auth(); let admin = Self::get_admin(env.clone()); diff --git a/contracts/revenue_pool/src/test.rs b/contracts/revenue_pool/src/test.rs index c666fb8c..7e2004b9 100644 --- a/contracts/revenue_pool/src/test.rs +++ b/contracts/revenue_pool/src/test.rs @@ -263,6 +263,41 @@ fn receive_payment_emits_event() { assert_eq!(amount_and_source, (250, true)); } +#[test] +#[should_panic(expected = "unauthorized: caller is not admin")] +fn receive_payment_non_admin_panics() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let attacker = Address::generate(&env); + let (_, client) = create_pool(&env); + let (usdc, _, _) = create_usdc(&env, &admin); + + client.init(&admin, &usdc); + client.receive_payment(&attacker, &250, &true); +} + +#[test] +fn receive_payment_is_event_only_and_does_not_move_tokens() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let developer = Address::generate(&env); + let (pool_addr, client) = create_pool(&env); + let (usdc_address, usdc_client, usdc_admin) = create_usdc(&env, &admin); + + client.init(&admin, &usdc_address); + fund_pool(&usdc_admin, &pool_addr, 500); + + let before_pool = usdc_client.balance(&pool_addr); + let before_developer = usdc_client.balance(&developer); + + client.receive_payment(&admin, &250, &true); + + assert_eq!(usdc_client.balance(&pool_addr), before_pool); + assert_eq!(usdc_client.balance(&developer), before_developer); +} + #[test] fn batch_distribute_success() { let env = Env::default();