Skip to content

Commit eb26026

Browse files
authored
Merge pull request #172 from Benalex8797/feature/platform-fee-calculator
Feature/platform fee calculator
2 parents f87c508 + 0e93322 commit eb26026

File tree

4 files changed

+542
-0
lines changed

4 files changed

+542
-0
lines changed
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# Platform Fee Calculator
2+
3+
## Overview
4+
Internal helper function to calculate platform fees based on basis points for the Nevo crowdfunding platform.
5+
6+
## Function Signature
7+
8+
```rust
9+
fn calculate_platform_fee(amount: i128, fee_bps: u32) -> i128
10+
```
11+
12+
## Parameters
13+
14+
- `amount` (i128): The donation/contribution amount to calculate the fee from
15+
- Must be non-negative
16+
- Supports large amounts up to i128::MAX / 10,001
17+
18+
- `fee_bps` (u32): Fee percentage in basis points
19+
- Range: 0 to 10,000 (0% to 100%)
20+
- Common values:
21+
- 100 bps = 1%
22+
- 250 bps = 2.5%
23+
- 500 bps = 5%
24+
- 1,000 bps = 10%
25+
26+
## Returns
27+
28+
- `i128`: The calculated fee amount (rounded down)
29+
30+
## Basis Points Explained
31+
32+
Basis points (bps) are a unit of measure used in finance to describe percentages:
33+
- 1 basis point = 0.01%
34+
- 100 basis points = 1%
35+
- 10,000 basis points = 100%
36+
37+
### Examples:
38+
- 250 bps = 2.5%
39+
- 500 bps = 5%
40+
- 1,000 bps = 10%
41+
42+
## Calculation Formula
43+
44+
```
45+
fee = (amount × fee_bps) / 10,000
46+
```
47+
48+
## Safety Features
49+
50+
### Overflow Protection
51+
- Uses checked arithmetic to prevent overflow
52+
- Validates inputs before calculation
53+
- Panics with descriptive messages on invalid inputs
54+
55+
### Input Validation
56+
- Amount must be non-negative
57+
- Fee basis points must be10,000 (100%)
58+
- Checks for potential overflow before multiplication
59+
60+
## Usage Examples
61+
62+
### Basic Usage
63+
64+
```rust
65+
// Calculate 2.5% fee on 10,000 tokens
66+
let fee = CrowdfundingContract::calculate_platform_fee(10_000, 250);
67+
assert_eq!(fee, 250); // 2.5% of 10,000 = 250
68+
```
69+
70+
### Realistic Scenarios
71+
72+
```rust
73+
// Small donation: $50 with 2.5% fee
74+
let fee = CrowdfundingContract::calculate_platform_fee(5_000, 250);
75+
assert_eq!(fee, 125); // $1.25 fee
76+
77+
// Medium donation: $1,000 with 2.5% fee
78+
let fee = CrowdfundingContract::calculate_platform_fee(100_000, 250);
79+
assert_eq!(fee, 2_500); // $25 fee
80+
81+
// Large donation: $10,000 with 2.5% fee
82+
let fee = CrowdfundingContract::calculate_platform_fee(1_000_000, 250);
83+
assert_eq!(fee, 25_000); // $250 fee
84+
```
85+
86+
### Stellar XLM Amounts
87+
88+
Stellar XLM has 7 decimal places (1 XLM = 10,000,000 stroops):
89+
90+
```rust
91+
// 100 XLM donation with 2.5% fee
92+
let amount_stroops = 1_000_000_000; // 100 XLM
93+
let fee = CrowdfundingContract::calculate_platform_fee(amount_stroops, 250);
94+
assert_eq!(fee, 25_000_000); // 2.5 XLM fee
95+
```
96+
97+
## Test Coverage
98+
99+
The implementation includes comprehensive tests covering:
100+
101+
### Standard Cases
102+
- Zero amount and zero fee
103+
- Various percentage calculations (1%, 2.5%, 5%, 10%, 100%)
104+
- Small and large amounts
105+
- Rounding behavior
106+
107+
### Edge Cases
108+
- Maximum safe amounts
109+
- Overflow scenarios
110+
- Negative amount validation
111+
- Invalid basis points validation
112+
113+
### Realistic Scenarios
114+
- Community pools ($500 - $1,000)
115+
- Education funds ($5,000 - $10,000)
116+
- Medical campaigns ($50,000 - $100,000)
117+
- Disaster relief ($1,000,000+)
118+
119+
### Mathematical Properties
120+
- Proportionality: doubling amount doubles fee
121+
- Additivity: fee(a) + fee(b) = fee(a+b)
122+
- Consistency: deterministic results
123+
- Precision: proper rounding behavior
124+
125+
## Error Handling
126+
127+
### Panics
128+
129+
The function panics in the following cases:
130+
131+
1. **Negative Amount**
132+
```rust
133+
// Panics: "amount must be non-negative"
134+
calculate_platform_fee(-1000, 250);
135+
```
136+
137+
2. **Invalid Basis Points**
138+
```rust
139+
// Panics: "fee_bps must be <= 10,000 (100%)"
140+
calculate_platform_fee(1000, 10_001);
141+
```
142+
143+
3. **Overflow**
144+
```rust
145+
// Panics: "fee calculation would overflow"
146+
calculate_platform_fee(i128::MAX, 10_000);
147+
```
148+
149+
## Integration with Contract
150+
151+
### Current Usage
152+
153+
The function is currently an internal helper and can be integrated into:
154+
155+
1. **Donation Processing**
156+
```rust
157+
fn donate(...) {
158+
// Calculate platform fee
159+
let fee = Self::calculate_platform_fee(amount, 250); // 2.5%
160+
let net_amount = amount - fee;
161+
162+
// Transfer net amount to pool
163+
// Track fee separately
164+
}
165+
```
166+
167+
2. **Pool Contributions**
168+
```rust
169+
fn contribute(...) {
170+
let fee = Self::calculate_platform_fee(amount, fee_bps);
171+
// Process contribution with fee deduction
172+
}
173+
```
174+
175+
3. **Fee Reporting**
176+
```rust
177+
fn get_estimated_fee(amount: i128) -> i128 {
178+
Self::calculate_platform_fee(amount, 250)
179+
}
180+
```
181+
182+
## Performance Considerations
183+
184+
- **Time Complexity**: O(1) - constant time calculation
185+
- **Space Complexity**: O(1) - no additional memory allocation
186+
- **Gas Efficiency**: Minimal operations (multiply, divide, compare)
187+
188+
## Best Practices
189+
190+
1. **Always validate user input** before calling this function
191+
2. **Use consistent basis points** across the platform (e.g., 250 bps)
192+
3. **Document fee structure** clearly to users
193+
4. **Test with realistic amounts** from your use case
194+
5. **Consider rounding** when displaying fees to users
195+
196+
## Future Enhancements
197+
198+
Potential improvements:
199+
200+
1. **Configurable Fee Tiers**
201+
- Different fees based on amount ranges
202+
- Volume discounts for large donations
203+
204+
2. **Dynamic Fee Adjustment**
205+
- Admin-controlled fee percentage
206+
- Time-based fee variations
207+
208+
3. **Fee Caps**
209+
- Maximum fee amount regardless of donation size
210+
- Minimum fee thresholds
211+
212+
4. **Fee Distribution**
213+
- Split fees between platform and pool creators
214+
- Referral fee sharing
215+
216+
## Testing
217+
218+
Run the test suite:
219+
220+
```bash
221+
cd contract/contract
222+
cargo test calculate_platform_fee
223+
```
224+
225+
Expected output:
226+
```
227+
running 25 tests
228+
test platform_fee_test::test_calculate_platform_fee_zero_amount ... ok
229+
test platform_fee_test::test_calculate_platform_fee_standard_case ... ok
230+
test platform_fee_test::test_calculate_platform_fee_large_amount ... ok
231+
...
232+
test result: ok. 25 passed; 0 failed; 0 ignored
233+
```
234+
235+
## CI/CD Integration
236+
237+
The function is automatically tested in the CI pipeline:
238+
- Formatting checks with `cargo fmt`
239+
- Unit tests with `cargo test`
240+
- Build verification with `cargo build --release`
241+
242+
## References
243+
244+
- [Basis Points - Investopedia](https://www.investopedia.com/terms/b/basispoint.asp)
245+
- [Soroban Smart Contracts](https://soroban.stellar.org/)
246+
- [Stellar XLM Precision](https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/assets)

contract/contract/src/crowdfunding.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,51 @@ use crate::interfaces::crowdfunding::CrowdfundingTrait;
1919
#[contract]
2020
pub struct CrowdfundingContract;
2121

22+
// Internal helper functions
23+
impl CrowdfundingContract {
24+
/// Calculate platform fee based on basis points.
25+
///
26+
/// # Arguments
27+
/// * `amount` - The donation amount to calculate fee from
28+
/// * `fee_bps` - Fee in basis points (e.g., 250 for 2.5%)
29+
///
30+
/// # Returns
31+
/// The calculated fee amount
32+
///
33+
/// # Panics
34+
/// Panics if the calculation would overflow
35+
///
36+
/// # Examples
37+
/// ```
38+
/// // 2.5% fee (250 basis points) on 10,000 tokens
39+
/// let fee = calculate_platform_fee(10_000, 250);
40+
/// assert_eq!(fee, 250); // 2.5% of 10,000 = 250
41+
/// ```
42+
pub(crate) fn calculate_platform_fee(amount: i128, fee_bps: u32) -> i128 {
43+
// Basis points: 10,000 bps = 100%
44+
const BPS_DENOMINATOR: i128 = 10_000;
45+
46+
// Validate inputs
47+
assert!(amount >= 0, "amount must be non-negative");
48+
assert!(fee_bps <= 10_000, "fee_bps must be <= 10,000 (100%)");
49+
50+
// Use checked multiplication to prevent overflow
51+
// Formula: (amount * fee_bps) / 10,000
52+
let fee_bps_i128 = fee_bps as i128;
53+
54+
// Check for potential overflow before multiplication
55+
if amount > 0 && fee_bps_i128 > i128::MAX / amount {
56+
panic!("fee calculation would overflow");
57+
}
58+
59+
let numerator = amount
60+
.checked_mul(fee_bps_i128)
61+
.expect("fee calculation overflow");
62+
63+
numerator / BPS_DENOMINATOR
64+
}
65+
}
66+
2267
#[contractimpl]
2368
#[allow(clippy::too_many_arguments)]
2469
impl CrowdfundingTrait for CrowdfundingContract {

contract/contract/test/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod close_private_pool_test;
44
mod create_pool;
55
mod crowdfunding_test;
66
mod min_contribution_test;
7+
mod platform_fee_test;
78
mod reentrancy_tests;
89
mod renounce_admin_test;
910
mod update_pool_metadata_test;

0 commit comments

Comments
 (0)