Skip to content

Commit 827e284

Browse files
authored
Merge pull request #719 from boseshittu2323-design/fix/nft-contract-corrupted-merge
fix(nft-contract): repair corrupted merges in lib.rs and storage.rs
2 parents 1d424a3 + 849e3b0 commit 827e284

7 files changed

Lines changed: 277 additions & 232 deletions

File tree

contracts/nft-contract/src/lib.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ pub enum Error {
8282
/// The asset contract address is not on the admin-approved allow-list.
8383
UnsupportedAsset = 9,
8484
/// Provided WASM hash is all zeros — cannot upgrade to a no-op contract.
85+
InvalidWasmHash = 19,
86+
/// Clip signature verification failed — caller is not the clip owner.
87+
InvalidSignature = 20,
8588
InvalidWasmHash = 10,
8689
/// Clip signature verification failed — caller is not the clip owner.
8790
InvalidSignature = 11,
@@ -109,7 +112,7 @@ pub enum Error {
109112
/// XLM token SAC address has not been configured (Issue #676).
110113
XlmTokenNotConfigured = 18,
111114
/// No royalties have accrued yet — nothing to claim.
112-
InsufficientBalance = 15,
115+
InsufficientBalance = 21,
113116
}
114117

115118
#[contractimpl]
@@ -757,10 +760,16 @@ impl ClipsNftContract {
757760
let admin = storage::get_admin(&env).ok_or(Error::NotInitialized)?;
758761
admin.require_auth();
759762

763+
if bps > storage::ROYALTY_BPS_MAX {
764+
return Err(Error::InvalidRoyaltyBps);
765+
}
766+
760767
storage::set_platform_fee(&env, &recipient, bps);
761768
Ok(())
762769
}
763770

771+
/// Record a royalty payment made off-chain (in stroops) for `token_id`,
772+
/// emitting a `royalty_paid` event for indexers.
764773
/// Record the royalty payment owed on `token_id`. Emits `royalty_paid`
765774
/// with the amount in stroops. `payer` must authorize the call.
766775
pub fn pay_royalty(
@@ -787,6 +796,22 @@ impl ClipsNftContract {
787796
Ok(())
788797
}
789798

799+
pub fn set_token_royalty_bps(env: Env, token_id: u64, bps: u32) -> Result<(), Error> {
800+
let admin = storage::get_admin(&env).ok_or(Error::NotInitialized)?;
801+
admin.require_auth();
802+
803+
if !storage::has_token(&env, token_id) {
804+
return Err(Error::TokenNotFound);
805+
}
806+
807+
if bps > storage::ROYALTY_BPS_MAX {
808+
return Err(Error::InvalidRoyaltyBps);
809+
}
810+
811+
storage::set_token_royalty_bps(&env, token_id, bps);
812+
Ok(())
813+
}
814+
790815
/// Return the currently configured default platform recipient + fee, if any.
791816
pub fn get_default_platform_fee(env: Env) -> Option<(Address, u32)> {
792817
storage::get_platform_fee(&env)
@@ -1038,6 +1063,8 @@ impl ClipsNftContract {
10381063
admin.require_auth();
10391064
storage::set_xlm_token_address(&env, &xlm_token);
10401065
Ok(())
1066+
}
1067+
10411068
/// Return a paginated slice of token IDs owned by `owner`.
10421069
///
10431070
/// `limit` – maximum number of token IDs to return (capped at 100).
@@ -1057,6 +1084,8 @@ impl ClipsNftContract {
10571084
i += 1;
10581085
}
10591086
result
1087+
}
1088+
10601089
/// Accumulate royalties for a token. Called after each royalty payment so
10611090
/// that the owed balance grows until the creator calls `claim_royalties`.
10621091
///

contracts/nft-contract/src/storage.rs

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -227,24 +227,6 @@ pub fn is_approved_for_all(env: &Env, owner: &Address, operator: &Address) -> bo
227227
}
228228

229229
// ── Default royalty BPS ──────────────────────────────────────────────────────
230-
// ── Issue #675: Operator approvals ─────────────────────────────────────────
231-
232-
/// Set operator approval for all tokens owned by `owner`.
233-
/// Allows `operator` to transfer any of owner's NFTs via `transfer_from`.
234-
pub fn set_approval_for_all(env: &Env, owner: &Address, operator: &Address, approved: bool) {
235-
let key = (Symbol::new(env, "op_appr"), owner.clone(), operator.clone());
236-
if approved {
237-
env.storage().persistent().set(&key, &true);
238-
} else {
239-
env.storage().persistent().remove(&key);
240-
}
241-
}
242-
243-
/// Check whether `operator` is approved to manage all of `owner`'s NFTs.
244-
pub fn is_approved_for_all(env: &Env, owner: &Address, operator: &Address) -> bool {
245-
let key = (Symbol::new(env, "op_appr"), owner.clone(), operator.clone());
246-
env.storage().persistent().get(&key).unwrap_or(false)
247-
}
248230

249231
pub fn set_default_royalty_bps(env: &Env, bps: u32) {
250232
env.storage()
@@ -537,48 +519,6 @@ pub fn get_xlm_token_address(env: &Env) -> Option<Address> {
537519
.get(&Symbol::new(env, XLM_TOKEN_KEY))
538520
}
539521

540-
// ── Issue #676: Emergency withdraw timelock ─────────────────────────────────
541-
542-
/// 24 hours expressed in seconds.
543-
pub const WITHDRAW_TIMELOCK_SECS: u64 = 86_400;
544-
545-
const WITHDRAW_UNLOCK_KEY: &str = "wdraw_unlock";
546-
const XLM_TOKEN_KEY: &str = "xlm_token";
547-
548-
/// Persist the timestamp after which `withdraw_xlm` may execute.
549-
pub fn set_withdraw_unlock_time(env: &Env, unlock_time: u64) {
550-
env.storage()
551-
.instance()
552-
.set(&Symbol::new(env, WITHDRAW_UNLOCK_KEY), &unlock_time);
553-
}
554-
555-
/// Retrieve the pending unlock timestamp, or `None` when not initiated.
556-
pub fn get_withdraw_unlock_time(env: &Env) -> Option<u64> {
557-
env.storage()
558-
.instance()
559-
.get(&Symbol::new(env, WITHDRAW_UNLOCK_KEY))
560-
}
561-
562-
/// Clear the timelock after a successful withdrawal so it cannot be
563-
/// re-used without a fresh `initiate_withdraw` call.
564-
pub fn clear_withdraw_unlock_time(env: &Env) {
565-
env.storage()
566-
.instance()
567-
.remove(&Symbol::new(env, WITHDRAW_UNLOCK_KEY));
568-
}
569-
570-
/// Persist the XLM Stellar Asset Contract (SAC) address.
571-
pub fn set_xlm_token_address(env: &Env, address: &Address) {
572-
env.storage()
573-
.instance()
574-
.set(&Symbol::new(env, XLM_TOKEN_KEY), address);
575-
}
576-
577-
/// Retrieve the configured XLM SAC address.
578-
pub fn get_xlm_token_address(env: &Env) -> Option<Address> {
579-
env.storage()
580-
.instance()
581-
.get(&Symbol::new(env, XLM_TOKEN_KEY))
582522
// ── Royalty accumulation and claiming ───────────────────────────────────────
583523

584524
/// Store accumulated royalties for a token (amount owed to creator).

contracts/nft-contract/src/test.rs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ fn s(env: &Env, v: &str) -> String {
2525
String::from_str(env, v)
2626
}
2727

28+
/// Batch-mint `count` tokens (IDs `1..=count`) to `owner` and return the
29+
/// minted token IDs, for tests exercising pagination over a large collection.
30+
fn batch_mint_n(
31+
env: &Env,
32+
client: &ClipsNftContractClient<'static>,
33+
owner: &Address,
34+
count: u64,
35+
) -> Vec<u64> {
36+
let mut token_ids = Vec::new(env);
37+
let mut clip_ids = Vec::new(env);
38+
let mut uris = Vec::new(env);
39+
let mut soulbound = Vec::new(env);
40+
for i in 1..=count {
41+
token_ids.push_back(i);
42+
clip_ids.push_back(s(env, "clip"));
43+
uris.push_back(s(env, "uri"));
44+
soulbound.push_back(false);
45+
}
46+
client.batch_mint(owner, &token_ids, &clip_ids, &uris, &soulbound);
47+
token_ids
48+
}
49+
2850
// ─────────────────────────────────────────────────────────────
2951
// Initialization
3052
// ─────────────────────────────────────────────────────────────
@@ -1440,9 +1462,7 @@ fn test_get_user_tokens_pagination_first_page() {
14401462

14411463
for i in 1..=5 {
14421464
let token_id = i;
1443-
let clip_id = format!("clip_{}", i);
1444-
let uri = format!("uri_{}", i);
1445-
client.mint(&owner, &token_id, &s(&env, &clip_id), &s(&env, &uri), &false);
1465+
client.mint(&owner, &token_id, &s(&env, "clip"), &s(&env, "uri"), &false);
14461466
}
14471467

14481468
let page1 = client.get_user_tokens(&owner, &2, &0);
@@ -1462,9 +1482,7 @@ fn test_get_user_tokens_pagination_second_page() {
14621482

14631483
for i in 1..=5 {
14641484
let token_id = i;
1465-
let clip_id = format!("clip_{}", i);
1466-
let uri = format!("uri_{}", i);
1467-
client.mint(&owner, &token_id, &s(&env, &clip_id), &s(&env, &uri), &false);
1485+
client.mint(&owner, &token_id, &s(&env, "clip"), &s(&env, "uri"), &false);
14681486
}
14691487

14701488
let page2 = client.get_user_tokens(&owner, &2, &2);
@@ -1484,9 +1502,7 @@ fn test_get_user_tokens_pagination_last_page_partial() {
14841502

14851503
for i in 1..=5 {
14861504
let token_id = i;
1487-
let clip_id = format!("clip_{}", i);
1488-
let uri = format!("uri_{}", i);
1489-
client.mint(&owner, &token_id, &s(&env, &clip_id), &s(&env, &uri), &false);
1505+
client.mint(&owner, &token_id, &s(&env, "clip"), &s(&env, "uri"), &false);
14901506
}
14911507

14921508
let last_page = client.get_user_tokens(&owner, &2, &4);
@@ -1520,9 +1536,7 @@ fn test_get_user_tokens_limit_exceeds_total() {
15201536

15211537
for i in 1..=3 {
15221538
let token_id = i;
1523-
let clip_id = format!("clip_{}", i);
1524-
let uri = format!("uri_{}", i);
1525-
client.mint(&owner, &token_id, &s(&env, &clip_id), &s(&env, &uri), &false);
1539+
client.mint(&owner, &token_id, &s(&env, "clip"), &s(&env, "uri"), &false);
15261540
}
15271541

15281542
let result = client.get_user_tokens(&owner, &100, &0);
@@ -1554,11 +1568,7 @@ fn test_get_user_tokens_limit_capped_at_100() {
15541568
client.initialize(&admin);
15551569

15561570
// Mint 50 tokens (batch limit is 50)
1557-
let token_ids: Vec<u64> = (1..=50).collect();
1558-
let clip_ids: Vec<String> = (1..=50).map(|i| String::from_str(&env, &format!("clip_{}", i))).collect();
1559-
let uris: Vec<String> = (1..=50).map(|i| String::from_str(&env, &format!("uri_{}", i))).collect();
1560-
let soulbound: Vec<bool> = (1..=50).map(|_| false).collect();
1561-
client.batch_mint(&owner, &token_ids, &clip_ids, &uris, &soulbound);
1571+
batch_mint_n(&env, &client, &owner, 50);
15621572

15631573
// Request 200 (should be capped to 100, but only 50 exist)
15641574
let result = client.get_user_tokens(&owner, &200, &0);
@@ -1575,11 +1585,7 @@ fn test_get_user_tokens_large_collection_multi_page() {
15751585
client.initialize(&admin);
15761586

15771587
// Mint 50 tokens via batch
1578-
let token_ids: Vec<u64> = (1..=50).collect();
1579-
let clip_ids: Vec<String> = (1..=50).map(|i| String::from_str(&env, &format!("clip_{}", i))).collect();
1580-
let uris: Vec<String> = (1..=50).map(|i| String::from_str(&env, &format!("uri_{}", i))).collect();
1581-
let soulbound: Vec<bool> = (1..=50).map(|_| false).collect();
1582-
client.batch_mint(&owner, &token_ids, &clip_ids, &uris, &soulbound);
1588+
batch_mint_n(&env, &client, &owner, 50);
15831589

15841590
// Page 1
15851591
let page1 = client.get_user_tokens(&owner, &20, &0);

src/clips/nft-mint.service.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ const royaltyConfigMock = {
132132
getCreatorRoyaltyBps: jest.fn().mockReturnValue(1000),
133133
getPlatformWallet: jest.fn().mockReturnValue('GDV76E6XN6A3Q3WXVZ4KPRQ7L6E6XN6A3Q3WXVZ4KPRQ7L6E6XN6'),
134134
buildRoyaltyMap: jest.fn(),
135+
getRoyaltyAsset: jest.fn().mockReturnValue({ code: 'native' }),
135136
};
136137

137138
describe('NftMintService.uploadMetadataToIPFS', () => {

src/clips/nft-mint.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,8 @@ export class NftMintService {
577577
? 'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA'
578578
: 'CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC';
579579
}
580+
581+
private buildMetadata(clip: {
580582
id: number;
581583
title: string | null;
582584
caption: string | null;

src/nft/nft-ownership.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ export class NftOwnershipService {
147147
this.logger.error(`Failed to check token existence for token ${tokenId}`, error);
148148
return false;
149149
}
150+
}
151+
152+
/**
150153
* Get a paginated slice of token IDs owned by a wallet address.
151154
*
152155
* Uses offset-based pagination: `cursor` is the 0-based index into the

0 commit comments

Comments
 (0)