Skip to content
Open
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
79 changes: 79 additions & 0 deletions creator-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,85 @@ impl CreatorKeysContract {
Ok(profile.supply)
}

pub fn transfer_key(
env: Env,
creator: Address,
from: Address,
to: Address,
amount: u32,
) -> Result<(), ContractError> {
from.require_auth();
assert_not_paused(&env)?;

if amount == 0 {
return Err(ContractError::NotPositiveAmount);
}

let mut profile: CreatorProfile = read_registered_creator_profile(&env, &creator)?;

if from == to {
let balance_key = constants::storage::key_balance(&creator, &from);
let current_balance: u32 = env.storage().persistent().get(&balance_key).unwrap_or(0);
if current_balance < amount {
return Err(ContractError::InsufficientBalance);
}

settle_holder_dividends(&env, &creator, &from, current_balance)?;
return Ok(());
}

let from_balance_key = constants::storage::key_balance(&creator, &from);
let to_balance_key = constants::storage::key_balance(&creator, &to);

let from_balance: u32 = env
.storage()
.persistent()
.get(&from_balance_key)
.unwrap_or(0);
if from_balance < amount {
return Err(ContractError::InsufficientBalance);
}

let to_balance: u32 = env.storage().persistent().get(&to_balance_key).unwrap_or(0);

// Settle both sides before mutating balances so pending dividends reflect
// the pre-transfer ownership distribution.
settle_holder_dividends(&env, &creator, &from, from_balance)?;
settle_holder_dividends(&env, &creator, &to, to_balance)?;

let new_from_balance = from_balance
.checked_sub(amount)
.ok_or(ContractError::SellUnderflow)?;
let new_to_balance = to_balance
.checked_add(amount)
.ok_or(ContractError::Overflow)?;

if new_from_balance == 0 {
profile.holder_count = profile
.holder_count
.checked_sub(1)
.ok_or(ContractError::SellUnderflow)?;
}

if to_balance == 0 {
profile.holder_count = profile
.holder_count
.checked_add(1)
.ok_or(ContractError::Overflow)?;
}

let key = constants::storage::creator(&creator);
env.storage().persistent().set(&key, &profile);
env.storage()
.persistent()
.set(&from_balance_key, &new_from_balance);
env.storage()
.persistent()
.set(&to_balance_key, &new_to_balance);

Ok(())
}

/// Halts all state-changing operations (buy, sell, register_creator).
///
/// Only the protocol admin may call this. Emits a `ProtocolPaused` event.
Expand Down
85 changes: 85 additions & 0 deletions creator-keys/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,91 @@ fn test_get_creator_holder_count_counts_unique_holders() {
assert_eq!(second_read, 2);
}

#[test]
fn test_holder_count_decrements_on_full_transfer() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register(CreatorKeysContract, ());
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
client.set_key_price(&admin, &100);

let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
client.register_creator(&creator, &handle);

let sender = Address::generate(&env);
let recipient = Address::generate(&env);

client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &recipient, &100, &None);

client.transfer_key(&creator, &sender, &recipient, &1);

let profile = client.get_creator(&creator);
assert_eq!(profile.holder_count, 1);
assert_eq!(client.get_key_balance(&creator, &sender), 0);
assert_eq!(client.get_key_balance(&creator, &recipient), 2);
}

#[test]
fn test_holder_count_increments_on_first_receive() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register(CreatorKeysContract, ());
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
client.set_key_price(&admin, &100);

let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
client.register_creator(&creator, &handle);

let sender = Address::generate(&env);
let recipient = Address::generate(&env);

client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &sender, &100, &None);

client.transfer_key(&creator, &sender, &recipient, &1);

let profile = client.get_creator(&creator);
assert_eq!(profile.holder_count, 2);
assert_eq!(client.get_key_balance(&creator, &sender), 1);
assert_eq!(client.get_key_balance(&creator, &recipient), 1);
}

#[test]
fn test_holder_count_unchanged_on_partial_transfer() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register(CreatorKeysContract, ());
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
client.set_key_price(&admin, &100);

let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
client.register_creator(&creator, &handle);

let sender = Address::generate(&env);
let recipient = Address::generate(&env);

client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &recipient, &100, &None);

client.transfer_key(&creator, &sender, &recipient, &1);

let profile = client.get_creator(&creator);
assert_eq!(profile.holder_count, 2);
assert_eq!(client.get_key_balance(&creator, &sender), 1);
assert_eq!(client.get_key_balance(&creator, &recipient), 2);
}

#[test]
fn test_get_creator_fails_if_not_registered() {
let env = Env::default();
Expand Down
Loading
Loading