Skip to content
Closed
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
116 changes: 107 additions & 9 deletions contracts/globe-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,8 @@ impl GlobeWallet {
return Err(WalletError::InvalidAssetCode);
}

let is_native = asset.code == String::from_str(&env, "XLM");
let is_native =
Self::codes_match_case_insensitive(&asset.code, &String::from_str(&env, "XLM"));
if is_native {
if asset.issuer.is_some() {
return Err(WalletError::InvalidAssetInfo);
Expand Down Expand Up @@ -1672,12 +1673,15 @@ mod tests {
let user = Address::generate(&env);
for i in 0..GlobeWallet::MAX_ASSETS {
let code = String::from_str(&env, &std::format!("ASSET{}", i));
let asset = AssetInfo { code, issuer: None };
let asset = AssetInfo {
code,
issuer: Some(Address::generate(&env)),
};
client.add_asset(&user, &asset);
}
let extra = AssetInfo {
code: String::from_str(&env, "EXTRA"),
issuer: None,
issuer: Some(Address::generate(&env)),
};
assert_eq!(
client.try_add_asset(&user, &extra),
Expand Down Expand Up @@ -1734,7 +1738,10 @@ mod tests {
let user = Address::generate(&env);
for i in 0..3 {
let code = String::from_str(&env, &std::format!("ASSET{}", i));
let asset = AssetInfo { code, issuer: None };
let asset = AssetInfo {
code,
issuer: Some(Address::generate(&env)),
};
client.add_asset(&user, &asset);
}
let removed = client.migrate_user_assets(&admin, &user);
Expand Down Expand Up @@ -1880,8 +1887,10 @@ mod tests {
// Use a fabricated hash that was never uploaded via upload_contract_wasm
let never_uploaded_hash = BytesN::from_array(&env, &[42u8; 32]);

// propose_upgrade should succeed even with an invalid hash
assert_eq!(client.try_propose_upgrade(&admin, &never_uploaded_hash, &0u32), Ok(()));
assert_eq!(
client.try_propose_upgrade(&admin, &never_uploaded_hash, &0u32),
Ok(Ok(()))
);

// The proposal is stored
let cid = id.clone();
Expand Down Expand Up @@ -1937,7 +1946,7 @@ mod tests {

#[test]
fn test_add_and_list_guardians() {
let (env, _admin, guardians, client) = setup_with_guardians(3);
let (_env, _admin, guardians, client) = setup_with_guardians(3);
let stored = client.guardians();
assert_eq!(stored.len(), 3);
for i in 0..3 {
Expand Down Expand Up @@ -2571,6 +2580,81 @@ mod tests {
);
}

#[test]
fn test_lowercase_and_mixed_case_xlm_with_issuer_rejected_before_native_registered() {
let (env, _cid, _admin, client) = setup();
let user = Address::generate(&env);
let fake_issuer = Address::generate(&env);

// Lowercase "xlm" with issuer must be rejected outright as InvalidAssetInfo
let fake_xlm = AssetInfo {
code: String::from_str(&env, "xlm"),
issuer: Some(fake_issuer.clone()),
};
assert_eq!(
client.try_add_asset(&user, &fake_xlm),
Err(Ok(WalletError::InvalidAssetInfo))
);

// Mixed-case "Xlm" and "xLm" with issuer must also be rejected
let fake_xlm_mixed1 = AssetInfo {
code: String::from_str(&env, "Xlm"),
issuer: Some(fake_issuer.clone()),
};
assert_eq!(
client.try_add_asset(&user, &fake_xlm_mixed1),
Err(Ok(WalletError::InvalidAssetInfo))
);

let fake_xlm_mixed2 = AssetInfo {
code: String::from_str(&env, "xLm"),
issuer: Some(fake_issuer),
};
assert_eq!(
client.try_add_asset(&user, &fake_xlm_mixed2),
Err(Ok(WalletError::InvalidAssetInfo))
);

// Registry is clean: no squatting occurred, user has 0 assets
assert_eq!(client.get_assets(&user).len(), 0);

// The user can now register the real, native XLM successfully
let real_xlm = AssetInfo {
code: String::from_str(&env, "XLM"),
issuer: None,
};
assert_eq!(client.try_add_asset(&user, &real_xlm), Ok(Ok(())));
assert_eq!(client.get_assets(&user).len(), 1);

// Subsequent duplicate attempt with case-variant is rejected as AssetAlreadyAdded
let duplicate_xlm = AssetInfo {
code: String::from_str(&env, "xlm"),
issuer: None,
};
assert_eq!(
client.try_add_asset(&user, &duplicate_xlm),
Err(Ok(WalletError::AssetAlreadyAdded))
);
}

#[test]
fn test_native_xlm_registration_happy_path() {
let (env, _cid, _admin, client) = setup();
let user = Address::generate(&env);

// Native XLM registers normally when no squat exists
let native_xlm = AssetInfo {
code: String::from_str(&env, "XLM"),
issuer: None,
};
assert_eq!(client.try_add_asset(&user, &native_xlm), Ok(Ok(())));

let assets = client.get_assets(&user);
assert_eq!(assets.len(), 1);
assert_eq!(assets.get(0).unwrap().code, String::from_str(&env, "XLM"));
assert_eq!(assets.get(0).unwrap().issuer, None);
}

// ── TTL Extension ───────────────────────────────────────────────────
//
// Proactive extend_ttl on every write keeps UserAssets and SpendLimit
Expand All @@ -2580,11 +2664,18 @@ mod tests {

#[test]
fn test_user_assets_ttl_extension_after_long_idle_period() {
let (env, _cid, _admin, client) = setup();
let (env, cid, _admin, client) = setup();
let user = Address::generate(&env);
client.add_asset(&user, &xlm(&env));
client.add_asset(&user, &usdc(&env));

// Keep contract instance alive so only the persistent entry TTL is under test
env.as_contract(&cid, || {
env.storage()
.instance()
.extend_ttl(PERSISTENT_TTL_THRESHOLD, PERSISTENT_TTL_EXTEND_TO);
});

// Jump well past the default persistent-entry TTL (4096 ledgers).
// Without extend_ttl, the entry's default TTL would have expired
// and the archived entry would not be readable without a restore.
Expand All @@ -2597,11 +2688,18 @@ mod tests {

#[test]
fn test_spend_limit_ttl_extension_after_long_idle_period() {
let (env, _cid, _admin, client) = setup();
let (env, cid, _admin, client) = setup();
let user = Address::generate(&env);
let code = String::from_str(&env, "XLM");
client.set_spend_limit(&user, &code, &1_000_000_i128);

// Keep contract instance alive so only the persistent entry TTL is under test
env.as_contract(&cid, || {
env.storage()
.instance()
.extend_ttl(PERSISTENT_TTL_THRESHOLD, PERSISTENT_TTL_EXTEND_TO);
});

// Jump well past default persistent-entry TTL.
env.ledger().with_mut(|l| l.sequence_number += 50_000);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@
],
"data": {
"error": {
"contract": 3
"contract": 1003
}
}
}
Expand All @@ -369,7 +369,7 @@
},
{
"error": {
"contract": 3
"contract": 1003
}
}
],
Expand All @@ -394,7 +394,7 @@
},
{
"error": {
"contract": 3
"contract": 1003
}
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -662,4 +662,4 @@
"failed_call": false
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,43 @@
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
}
},
{
"key": {
"vec": [
{
"symbol": "GuardianMembership"
}
]
},
"val": {
"map": [
{
"key": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
},
"val": {
"bool": true
}
},
{
"key": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
},
"val": {
"bool": true
}
},
{
"key": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
},
"val": {
"bool": true
}
}
]
}
},
{
"key": {
"vec": [
Expand Down
Loading