fungible |
name, symbol in set_metadata |
instance entry; name() / symbol() return |
Oversized metadata inflates every instance read and the getters' return value. |
rwa::identity_verification::claim_issuer |
SigningKey.public_key (only an is_empty check) |
inside the Pairs(SigningKey) ledger key |
The key exceeds 250 B from a public key of about 115 bytes (est.); allow_key then fails. A 65-byte secp256k1 key sits at about 204 B. |
rwa::identity_verification::identity_claims |
Claim { signature, data, uri } |
Claim(id) entry; the whole claim is a #[topic] of ClaimAdded / ClaimRemoved / ClaimChanged; get_claim return |
A large claim breaks the events limit on add, change, and removal. |
rwa::identity_verification::identity_claims |
ClaimsByTopic(topic), one id per issuer per topic |
entry; get_claim_ids_by_topic return |
The getter exceeds the events-plus-return limit at about 410 ids (est.). |
accounts::policies::weighted_threshold |
signer_weights map, not tied to MAX_SIGNERS |
WeightedInstalled event; example getter |
Install exceeds the events limit at about 200 entries (est.). |
governance::governor |
VoteCast.reason; propose targets, functions, args |
VoteCast and ProposalCreated events (the latter also carries up to 4,096 B of description) |
Large inputs break the events limit on voting and proposing. |
governance::timelock |
Operation.args; the target's return value, passed through by execute |
events; top-level return value |
A target returning a large value makes execute fail as a transaction. |
fee-abstraction |
target_args; the target's return value, passed through by forward |
ForwardExecuted event; top-level return value |
Same as timelock. |
zk-email::dkim_registry |
input Vec of set_dkim_public_key_hashes |
2 footprint entries, 1 write, and 1 event of about 190 B per item |
The call exceeds the events limit at about 80 items (est.). |
tokens::confidential::verifier |
verification key Bytes on register and update |
instance entry; VerificationKeyRegistered embeds it, VerificationKeyUpdated embeds old and new |
About 3.6 KB of events per update at the real 1,760 B key; larger keys scale linearly. |
tokens::confidential |
proof Bytes |
UltraHonk verifier CPU |
No test in stellar-tokens verifies a real proof, so the CPU cost at realistic sizes is unmeasured. |
contract-utils::crypto::merkle |
proof length in sorted verify |
CPU |
verify_with_index caps at a bare 32 (merkle.rs:93); verify has no cap. |
rwa root and compliance modules |
every batch_* input Vec |
per item: hook chain, writes, events |
The documented 35 / 99 item figures (rwa/mod.rs:95-104) come from an on-demand benchmark outside the workspace, not a test. |
Summary
Several library bounds allow a structure to grow past a per-transaction Stellar network limit before the bound itself is reached. Past that point, the library still accepts writes, but the operation that reads or rewrites the structure fails on Mainnet. Separately, a number of caller-supplied values have no length or count bound at all.
The events-plus-return-value limit is enforced by stellar-core at apply time, after the top-level call returns: it sums the contract events, then adds the encoded return value, and fails the operation with
INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDEDif the total is over.simulateTransactioncomputes the same total but only uses it to price the fee, so a call that exceeds the limit simulates successfully, then fails once submitted and is still charged. stellar/stellar-rpc#964 reports this for events; the open fix, stellar/stellar-rpc#992, checks only the events, so an oversized return value would still pass simulation. Cross-contract reads are unaffected because only the top-level return value counts.Findings were taken on
v0.9.0atdf602b61. Sizes were measured in the SDK test host unless marked "est.", which means a hand-computed XDR size.Bounds that break before they are reached
accounts::policies::spending_limitMAX_HISTORY_ENTRIES= 1,000 history entriesenforcerewrites the wholeAccountContextentry, which exceeds the 65,536 B entry limit. Every spend authorized through the policy fails until old entries leave the period window.rwa::compliance::modules::initial_lockup_periodMAX_LOCKS= 512 locks per wallet (41,040 B)get_locked_detailsas a transaction exceeds the 16,384 B events-plus-return limit.rwa::compliance::modules::initial_lockup_periodMAX_LOCKS= 512migrate_locksappends to the destination without checkingMAX_LOCKS(storage.rs:500-518), so the destination can exceed the bound and, at about 82 KB (est.), the entry limit. The migration write fails.rwa::identity_verification::claim_topics_and_issuersMAX_CLAIM_TOPICS×MAX_ISSUERS= 15 × 50 (30,312 B)get_claim_topics_and_issuersas a transaction exceeds the events-plus-return limit.rwa::identity_verification::identity_registry_storageMAX_COUNTRY_ENTRIES= 15 max-size entries (24,672 B)get_country_data_entriesas a transaction exceeds the events-plus-return limit.rwa::identity_verification::identity_registry_storageMAX_COUNTRY_ENTRIES= 15add_identity,add_country_data_entries, andremove_identityemit one event per entry carrying the wholeCountryData, about 1,796 B each. The call exceeds the events limit. An identity built up to 10 or more entries across several calls can no longer be removed.rwa::extensions::doc_managerBUCKET_SIZE= 50 documents with 200-byte URIs (19,012 B)get_documentsas a transaction exceeds the events-plus-return limit.Values with no bound
fungiblename,symbolinset_metadataname()/symbol()returnrwa::identity_verification::claim_issuerSigningKey.public_key(only anis_emptycheck)Pairs(SigningKey)ledger keyallow_keythen fails. A 65-byte secp256k1 key sits at about 204 B.rwa::identity_verification::identity_claimsClaim { signature, data, uri }Claim(id)entry; the whole claim is a#[topic]ofClaimAdded/ClaimRemoved/ClaimChanged;get_claimreturnrwa::identity_verification::identity_claimsClaimsByTopic(topic), one id per issuer per topicget_claim_ids_by_topicreturnaccounts::policies::weighted_thresholdsigner_weightsmap, not tied toMAX_SIGNERSWeightedInstalledevent; example gettergovernance::governorVoteCast.reason;proposetargets, functions, argsVoteCastandProposalCreatedevents (the latter also carries up to 4,096 B of description)governance::timelockOperation.args; the target's return value, passed through byexecuteexecutefail as a transaction.fee-abstractiontarget_args; the target's return value, passed through byforwardForwardExecutedevent; top-level return valuezk-email::dkim_registryVecofset_dkim_public_key_hashestokens::confidential::verifierByteson register and updateVerificationKeyRegisteredembeds it,VerificationKeyUpdatedembeds old and newtokens::confidentialBytesstellar-tokensverifies a real proof, so the CPU cost at realistic sizes is unmeasured.contract-utils::crypto::merkleverifyverify_with_indexcaps at a bare32(merkle.rs:93);verifyhas no cap.rwaroot and compliance modulesbatch_*inputVecrwa/mod.rs:95-104) come from an on-demand benchmark outside the workspace, not a test.Proposed fixes
spending_limit: lowerMAX_HISTORY_ENTRIESbelow 816, or split the history across several entries (MAX_HISTORY_ENTRIESis unreachable, soHistoryCapacityExceededcan never be returned #847).initial_lockup_period: enforceMAX_LOCKSinmigrate_locks; lowerMAX_LOCKSto about 150 or paginateget_locked_details.claim_topics_and_issuers: haveverify_identityuse the per-topic getter instead of the full map, and either lower the bounds or paginateget_claim_topics_and_issuers.identity_registry_storage: emit one event per call instead of one per entry; lowerMAX_COUNTRY_ENTRIESto 6 or paginate the entries getter.doc_manager:BUCKET_SIZE25,MAX_BUCKETS200, which puts a full bucket at 9,512 B. Existing deployments need a migration because bucket indices derive fromBUCKET_SIZE.Every new or changed bound should ship with a test that fills it to the maximum with maximum-sized items and checks the result against the network limits, so the bound and the network limit cannot drift apart again.