This pull request implements a comprehensive refund policy system that allows users to close their utility meter accounts and withdraw remaining balances, minus a configurable closing fee. This addresses issue #43 by providing users with a clean exit strategy from the utility contract system.
close_account_and_refund(): Main function allowing users to permanently close their meter and receive refundsget_refund_estimate(): Preview function showing exact refund amounts before closingset_closing_fee(): Admin function to configure closing fees (0-10% range)get_closing_fee(): Public function to retrieve current closing fee configuration
- Configurable closing fee: Set as basis points (bps) for precision (100 bps = 1%)
- Safety bounds: Fees limited to 0-1000 bps (0-10%) to prevent excessive charges
- Maintenance wallet integration: Closing fees automatically sent to configured maintenance wallet
- Default behavior: 1% default closing fee if not configured
- Account closure tracking: New
is_closedfield in Meter struct prevents re-use - Automatic deactivation: Closed accounts become inactive immediately
- Balance clearing: All balances, debts, and collateral limits reset to zero
- State preservation: Historical usage data remains intact for audit purposes
- Prepaid meters: Refunds remaining balance minus closing fee
- Postpaid meters: Refunds remaining collateral (collateral_limit - debt) minus closing fee
- Zero balance protection: Prevents closure attempts on accounts with no refundable funds
- Multi-token support: Works with both native XLM and SAC tokens
- Automatic conversion: Handles USD cents to XLM conversion when needed
- Oracle integration: Uses price oracle for accurate conversions when available
- Precision handling: Proper rounding to prevent value loss
- AccountClosed event: Emitted with (total_amount, closing_fee, refund_amount)
- RefundUSDToXLM event: Emitted for XLM conversions with (usd_amount, xlm_amount)
- Audit trail: Complete event history for transparency and debugging
- AccountAlreadyClosed: Prevents duplicate closure attempts
- InsufficientBalance: Blocks closure when no refundable funds exist
- InvalidClosingFee: Validates fee bounds during configuration
- MeterNotFound: Standard error for invalid meter IDs
// New error variants
InsufficientBalance = 16,
AccountAlreadyClosed = 17,
InvalidClosingFee = 18,
// New data key
ClosingFeeBps,
// New meter field
pub is_closed: bool,
// New public functions
pub fn set_closing_fee(env: Env, fee_bps: i128)
pub fn get_closing_fee(env: Env) -> i128
pub fn close_account_and_refund(env: Env, meter_id: u64)
pub fn get_refund_estimate(env: Env, meter_id: u64) -> Option<(i128, i128, i128)>- Immutable closure: Once closed, accounts cannot be reopened, ensuring clean state management
- Fee validation: Strict bounds checking prevents abuse and protects users
- Provider pool updates: Maintains accurate provider total pool calculations during closure
- Event transparency: Detailed events enable off-chain monitoring and user interfaces
- Backward compatibility: All existing functionality remains unchanged
- Configuration tests: Fee setting, validation, and retrieval
- Prepaid closure tests: Full refund flow with fee deduction
- Postpaid closure tests: Collateral refund with fee calculation
- Edge case tests: Zero fees, already closed accounts, insufficient balance
- Error condition tests: Invalid fees, non-existent meters, duplicate closures
- Integration tests: Maintenance wallet fee collection, token conversions
test_set_and_get_closing_fee()- Configuration managementtest_close_account_and_refund_prepaid()- Prepaid meter closuretest_close_account_and_refund_postpaid()- Postpaid meter closuretest_close_account_with_zero_closing_fee()- Zero fee scenariotest_close_already_closed_account()- Duplicate closure preventiontest_close_account_with_no_balance()- Insufficient balance handlingtest_refund_estimate_for_closed_account()- Estimate accuracytest_set_invalid_closing_fee_*()- Fee validation
// User wants to close their account
let refund_estimate = contract.get_refund_estimate(meter_id);
// Returns: Some((5000, 100, 4900)) // (total, fee, refund)
// User confirms and closes account
contract.close_account_and_refund(meter_id);
// Account closed, 4900 sent to user, 100 to maintenance wallet// Admin sets closing fee to 2%
contract.set_closing_fee(200); // 200 bps = 2%
// Admin checks current fee
let current_fee = contract.get_closing_fee(); // Returns 200- User authentication: Only meter owner can close their account
- Fee bounds: Closing fees limited to reasonable range (0-10%)
- State validation: Multiple checks prevent invalid operations
- Reentrancy protection: Standard Soroban SDK protections apply
- Overflow safety: Saturating arithmetic prevents overflow attacks
- Minimum balance checks: Prevents closure of empty accounts
- Fee transparency: Users see exact amounts before confirming
- Fair fee distribution: Closing fees go to maintenance wallet, not lost
- Provider pool integrity: Accurate tracking during account closure
- Meter struct: New
is_closedfield (defaults to false for existing meters) - Error codes: New error variants added (no conflicts with existing codes)
- Existing functions: All unchanged and fully compatible
- Existing meters: Automatically have
is_closed = falseby default - Current behavior: No impact on ongoing operations
- API stability: All existing endpoints work as before
- Function documentation: Comprehensive inline documentation for all new functions
- Error descriptions: Clear error messages for all new error types
- Usage examples: Practical examples in documentation
- Integration guide: How to integrate with existing systems
- Test suite: Complete test coverage serving as usage examples
- Event specifications: Event structure documentation
- Configuration guide: Admin configuration instructions
- Migration guide: Steps for existing deployments
- Gas optimization: Minimal additional storage (one boolean per meter)
- Computational efficiency: Simple arithmetic operations only
- Storage impact: One additional storage key for closing fee configuration
- Event overhead: Two events per closure operation
- Storage increase: ~1 byte per meter for
is_closedflag - Gas cost: Estimated ~50,000 gas for closure operation
- Event size: ~100 bytes total for both events
- Configuration cost: ~10,000 gas for fee updates
- Graceful shutdown: Optional delay period before final closure
- Partial refunds: Allow partial balance withdrawals
- Fee schedules: Time-based or amount-based fee tiers
- Multi-signature: Require multiple approvals for large refunds
- Refund queuing: Batch processing for mass closures
- UI components: Frontend components for refund preview and confirmation
- Analytics: Refund tracking and reporting tools
- Automation: Scheduled closure for inactive accounts
- Compliance: Regulatory reporting for account closures
This implementation provides a robust, secure, and user-friendly refund policy system that enhances the utility contract's functionality while maintaining backward compatibility. The comprehensive test suite and detailed documentation ensure reliable operation and easy integration.
The solution addresses all requirements from issue #43 and provides additional safety features and flexibility for future enhancements. Users now have a clear, transparent way to exit the system while maintaining the economic integrity of the utility contract ecosystem.