Skip to content
Merged
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
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion creator-keys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ edition = "2021"
publish = false

[lib]
crate-type = ["cdylib"]
crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }

115 changes: 115 additions & 0 deletions creator-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,38 @@

use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, String};

pub mod fee {
use soroban_sdk::contracttype;

/// Basis points per 100% (10000 = 100%).
pub const BPS_MAX: u32 = 10_000;

#[derive(Clone)]
#[contracttype]
pub struct FeeConfig {
pub creator_bps: u32,
pub protocol_bps: u32,
}

/// Computes the fee split for a given total amount.
///
/// Returns `(creator_amount, protocol_amount)`. Remainder from integer division
/// is assigned to the creator. Ensures creator_amount + protocol_amount == total.
pub fn compute_fee_split(total: i128, _creator_bps: u32, protocol_bps: u32) -> (i128, i128) {
if total <= 0 {
return (0, 0);
}
let protocol_amount = (total * protocol_bps as i128) / BPS_MAX as i128;
let creator_amount = total - protocol_amount;
(creator_amount, protocol_amount)
}
}

#[derive(Clone)]
#[contracttype]
pub enum DataKey {
Creator(Address),
FeeConfig,
}

#[derive(Clone)]
Expand Down Expand Up @@ -57,4 +85,91 @@ impl CreatorKeysContract {
let key = DataKey::Creator(creator);
env.storage().persistent().get(&key)
}

pub fn set_fee_config(env: Env, admin: Address, creator_bps: u32, protocol_bps: u32) {
admin.require_auth();
if creator_bps + protocol_bps != fee::BPS_MAX {
panic!("creator_bps + protocol_bps must equal 10000");
}
let config = fee::FeeConfig {
creator_bps,
protocol_bps,
};
env.storage().persistent().set(&DataKey::FeeConfig, &config);
}

pub fn get_fee_config(env: Env) -> Option<fee::FeeConfig> {
env.storage().persistent().get(&DataKey::FeeConfig)
}

pub fn compute_fees_for_payment(env: Env, total: i128) -> (i128, i128) {
let config: fee::FeeConfig = env
.storage()
.persistent()
.get(&DataKey::FeeConfig)
.unwrap_or_else(|| panic!("fee config not set"));
fee::compute_fee_split(total, config.creator_bps, config.protocol_bps)
}
}

#[cfg(test)]
mod tests {
use super::fee;

#[test]
fn test_fee_split_90_10_1000() {
let (creator, protocol) = fee::compute_fee_split(1000, 9000, 1000);
assert_eq!(creator, 900);
assert_eq!(protocol, 100);
assert_eq!(creator + protocol, 1000);
}

#[test]
fn test_fee_split_100_creator() {
let (creator, protocol) = fee::compute_fee_split(1000, 10000, 0);
assert_eq!(creator, 1000);
assert_eq!(protocol, 0);
assert_eq!(creator + protocol, 1000);
}

#[test]
fn test_fee_split_100_protocol() {
let (creator, protocol) = fee::compute_fee_split(1000, 0, 10000);
assert_eq!(creator, 0);
assert_eq!(protocol, 1000);
assert_eq!(creator + protocol, 1000);
}

#[test]
fn test_fee_split_remainder_to_creator() {
// 999 * 1000 / 10000 = 99 (protocol floor), creator gets remainder
let (creator, protocol) = fee::compute_fee_split(999, 9000, 1000);
assert_eq!(creator, 900);
assert_eq!(protocol, 99);
assert_eq!(creator + protocol, 999);
}

#[test]
fn test_fee_split_zero_total() {
let (creator, protocol) = fee::compute_fee_split(0, 9000, 1000);
assert_eq!(creator, 0);
assert_eq!(protocol, 0);
}

#[test]
fn test_fee_split_dust_total_one() {
// 1 * 1000 / 10000 = 0 protocol, creator gets full amount
let (creator, protocol) = fee::compute_fee_split(1, 9000, 1000);
assert_eq!(creator, 1);
assert_eq!(protocol, 0);
assert_eq!(creator + protocol, 1);
}

#[test]
fn test_fee_split_balance_conservation() {
for total in [100_i128, 1, 999, 10000, 1234567] {
let (creator, protocol) = fee::compute_fee_split(total, 9000, 1000);
assert_eq!(creator + protocol, total, "total={}", total);
}
}
}
Loading
Loading