Context
StellarWrapContract::extend_ttl(user, period) takes no authorization, checks has(...) before each extension, and unconditionally ends with:
e.storage().instance().extend_ttl(ttl, ttl);
Problem
Permissionless TTL extension is a deliberate and reasonable design choice — anyone should be able to keep a user's records alive. But the current shape has two rough edges:
- Calling it with a
(user, period) that does not exist performs three has lookups, extends nothing, and still extends the contract instance TTL — so an attacker can drive instance-rent writes with calls that accomplish nothing else.
- There is no batch form. Keeping N historical periods alive costs N transactions, which is precisely the "expiry risk" scenario the doc comment on
extend_ttl warns about.
Proposed change
- Return early (or panic with
WrapNotFound) when no matching entry exists, before touching instance storage.
- Add
extend_ttl_batch(user, periods: Vec<u64>) with a bounded length so a renewal bot can cover a user in one transaction.
Acceptance criteria
Context
StellarWrapContract::extend_ttl(user, period)takes no authorization, checkshas(...)before each extension, and unconditionally ends with:Problem
Permissionless TTL extension is a deliberate and reasonable design choice — anyone should be able to keep a user's records alive. But the current shape has two rough edges:
(user, period)that does not exist performs threehaslookups, extends nothing, and still extends the contract instance TTL — so an attacker can drive instance-rent writes with calls that accomplish nothing else.extend_ttlwarns about.Proposed change
WrapNotFound) when no matching entry exists, before touching instance storage.extend_ttl_batch(user, periods: Vec<u64>)with a bounded length so a renewal bot can cover a user in one transaction.Acceptance criteria
extend_ttlon a non-existent record does not extend the instance TTL.lib.rsis updated to describe the batch entrypoint.