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
1 change: 1 addition & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
| [CAP-0072](cap-0072.md) | Contract signers for Stellar accounts | Dmytro Kozhevin | Draft |
| [CAP-0073](cap-0073.md) | Allow SAC to create G-account balances | Dmytro Kozhevin | Draft |
| [CAP-0077](cap-0077.md) | Ability to freeze ledger keys via network configuration | Dmytro Kozhevin | Draft |
| [CAP-0078](cap-0078.md) | Host functions for performing limited TTL extensions | Dmytro Kozhevin | Draft |

### Rejected Proposals
| Number | Title | Author | Status |
Expand Down
204 changes: 204 additions & 0 deletions core/cap-0078.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
```
CAP: 0078
Title: Host functions for performing limited TTL extensions
Working Group:
Owner: Dmytro Kozhevin <@dmkozh>
Authors: Dmytro Kozhevin <@dmkozh>
Consulted:
Status: Draft
Created: 2025-12-16
Discussion: https://github.com/orgs/stellar/discussions/1825
Protocol version: 26
```

## Simple Summary

Add a way to limit the maximum TTL extension for contract data and code entries.

## Working Group

As specified in the Preamble.

## Motivation

The existing TTL extension host functions (such as `extend_contract_data_ttl`, `extend_contract_instance_and_code_ttl`) provide a way to extend contract data or code entry TTL by specifying the new value of TTL that the entry has to have and a minimum TTL extension threshold for reducing the frequency of TTL extensions in case if the function is called often. This interface allows developers to specify TTL extension policies such as 'if an entry TTL is less than 29 days, extend it to have 30 days TTL'.

While the current approach allows distributing the fees among the contract users to some degree, in case if contract usage patterns are uneven, some users may end up paying much more than the others. For example, with the policy example above a user may end up paying for TTL extension anywhere between 1 day and 30 days. More fine-grained control over TTL extension would help the developers that want to prioritize the rent fee stability and fairness.

### Goals Alignment

This CAP is aligned with the following Stellar Network Goals:

- The Stellar Network should make it easy for developers of Stellar projects to create highly usable products

## Abstract

New host functions `extend_contract_data_ttl_v2` and `extend_contract_instance_and_code_ttl_v2` are introduced for extending the contract data and contract instance and/or code TTLs. The new host functions are similar to the old versions as they allows extending TTL of a contract data or code entry to the target value. Unlike the existing functions, these additionally provide an explicit way to specify the minimum TTL extension (instead of the `threshold` parameter in the old interface), and the maximum TTL extension (not available in the old interface).

## Specification

### New host functions

The diff is based on commit `30ab5d1e2a642f18f3ae94cdb3a2798c3123f049` of `rs-soroban-env`.

```diff mddiffcheck.ignore=true
diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json
index 945ada2c..96efb4c8 100644
--- a/soroban-env-common/env.json
+++ b/soroban-env-common/env.json
@@ -1514,6 +1514,64 @@
"return": "AddressObject",
"docs": "Creates the contract instance on behalf of `deployer`. Created contract must be created from a Wasm that has a constructor. `deployer` must authorize this call via Soroban auth framework, i.e. this calls `deployer.require_auth` with respective arguments. `wasm_hash` must be a hash of the contract code that has already been uploaded on this network. `salt` is used to create a unique contract id. `constructor_args` are forwarded into created contract's constructor (`__constructor`) function. Returns the address of the created contract.",
"min_supported_protocol": 22
+ },
+ {
+ "export": "f",
+ "name": "extend_contract_data_ttl_v2",
+ "args": [
+ {
+ "name": "k",
+ "type": "Val"
+ },
+ {
+ "name": "t",
+ "type": "StorageType"
+ },
+ {
+ "name": "extend_to",
+ "type": "U32Val"
+ },
+ {
+ "name": "min_extension",
+ "type": "U32Val"
+ },
+ {
+ "name": "max_extension",
+ "type": "U32Val"
+ }
+ ],
+ "return": "Void",
+ "docs": "Extend the contract data entry's TTL to be up to `extend_to` ledgers, where TTL is defined as `entry_live_until_ledger_seq - current_ledger_seq`. The TTL extension only actually happens if it exceeds `min_extension`, otherwise this function is a no-op. The amount of extension ledgers will not exceed `max_extension` ledgers. If attempting to extend the entry past the maximum allowed value (defined as the current ledger + `max_entry_ttl` - 1), and the entry is `Persistent`, its new `live_until_ledger_seq` will be clamped to the max; if the entry is `Temporary`, the function traps.",
+ "min_supported_protocol": 26
+ },
+ {
+ "export": "g",
+ "name": "extend_contract_instance_and_code_ttl_v2",
+ "args": [
+ {
+ "name": "contract",
+ "type": "AddressObject"
+ },
+ {
+ "name": "extension_scope",
+ "type": "ContractTTLExtension"
+ },
+ {
+ "name": "extend_to",
+ "type": "U32Val"
+ },
+ {
+ "name": "min_extension",
+ "type": "U32Val"
+ },
+ {
+ "name": "max_extension",
+ "type": "U32Val"
+ }
+ ],
+ "return": "Void",
+ "docs": "Extend the contract instance and/or corresponding code entry TTL to be up to `extend_to` ledgers, where TTL is defined as `entry_live_until_ledger_seq - current_ledger_seq`. `extension_scope` defines whether contract instance, code, or both will be extended. The TTL extension only actually happens if it exceeds `min_extension`, otherwise this function is a no-op. The amount of extension ledgers will not exceed `max_extension` ledgers. If attempting to extend an entry past the maximum allowed value (defined as the current ledger + `max_entry_ttl` - 1), its new `live_until_ledger_seq` will be clamped to the max.",
+ "min_supported_protocol": 26
}
]
},
diff --git a/soroban-env-common/src/storage_type.rs b/soroban-env-common/src/storage_type.rs
index d72886e3..3f2d0d48 100644
--- a/soroban-env-common/src/storage_type.rs
+++ b/soroban-env-common/src/storage_type.rs
@@ -32,3 +32,13 @@ impl TryFrom<StorageType> for ContractDataDurability {
}

declare_wasmi_marshal_for_enum!(StorageType);
+
+
+#[repr(u64)]
+#[derive(Debug, FromPrimitive, PartialEq, Eq, Clone, Copy)]
+pub enum ContractTTLExtension {
+ InstanceAndCode = 0,
+ Instance = 1,
+ Code = 2,
+}
+declare_wasmi_marshal_for_enum!(ContractTTLExtension);
```

### Semantics

#### `extend_contract_data_ttl_v2` host function

`extend_contract_data_ttl_v2` adds 0 or more ledgers to the `liveUntilLedgerSeq` of the contract data entry defined by the ID of the current contract, key `k` passed in the argument and storage type `t`. The computation of TTL extension involves the following definitions:

- `liveUntilLedgerSeq` is the last ledger sequence number for which the entry is still considered to be alive, after that it is considered expired.
- TTL is defined as `TTL = liveUntilLedgerSeq - currentLedgerSeq`, where `currentLedgerSeq` is the sequence number of the ledger where transaction is executed.
- TTL extension is defined as `TTL_ext = TTL_new - TTL_curr = liveUntilLedgerSeq_new - liveUntilLedgerSeq_curr`, where `_curr/_new` are the values before and after executing the function respectively.

With these definitions, the extension algorithm is defined as follows:

- If `t` is `StorageType::Instance`, the function traps
- If `min_extension > extend_to`, or `max_extension < min_extension`, the function traps
- If `extend_to <= TTL_curr` the function returns without performing any changes
- Otherwise, compute the initial TTL extension `TTL_ext_init = TTL_curr - extend_to`
- If `TTL_ext_init < min_extension`, the function returns without performing any changes
- Otherwise, compute the final extension `TTL_ext_final = min(TTL_ext_init, max_extension)`
- If `TTL_ext_final` exceeds `maxEntryTTL` State Archival network setting value:
- If storage type `t` is `Persistent`, then `TTL_ext_final` is assigned to be `maxEntryTTL`
- If storage type `t` is `Temporary`, then function traps
- `liveUntilLedgerSeq` of the entry is set to be `liveUntilLedgerSeq + TTL_ext_final`

#### `extend_contract_instance_and_code_ttl_v2` host function

`extend_contract_instance_and_code_ttl_v2` TTL extension semantics are the same as for `extend_contract_data_ttl_v2`. The only difference is how the ledger keys to extend are specified.
`contract` argument identifies the contract ID associated with the ledger entries. `extension_scope` is an enum argument that identifies whether `contract`'s instance (`ContractTTLExtension::Instance`), code (`ContractTTLExtension::Code`), or both (`ContractTTLExtension::InstanceAndCode`) will get extended.

If a built-in contract instance is being extended (currently, only Stellar Asset contract is built-in), then the code extension requests are ignored without raising an error.

## Design Rationale

### Minimum and maximum extension arguments

Minimum extension argument is preserved from the old TTL extension functions and is just renamed from `threshold` for consistency. Its role is still to reduce the extension frequency, which reduces the user fees (as every TTL update incurs the write fee) and the ledger write load.

Maximum extension argument addresses the extension strategy mentioned in the 'Motivation' section. Developers may set extension strategies like 'extend TTL to 30 days with min extension of 1 day and max extension of 1 day', which would result in any user extending the entry TTL by just 1 day as long as its current TTL is anywhere between 0 and 29 days.

### Impact of maximum extension

Extension strategies that rely on `max_extension` may result in relatively more frequent updates of the TTL entries. For example, with the strategy described in the previous section up to 30 extensions may happen subsequently if the entry TTL has almost expired, while without `max_extension` only 1 extension would happen. However, for the reasonable strategies the absolute difference is not too significant compared to the overall scale of ledger writes, and it's already possible to create spammy strategies even without utilizing `max_extension` by just setting the `min_extension` threshold to 1 ledger.

### `max_extension` for temporary entries

For most of the use cases setting `max_extension` less than `extend_to` for temporary entry would be a mistake, as typically lifetime of temporary entries is very sensitive and must be set precisely (for example, temporary nonce entries must not be archived until the respective signature has expired). Protocol could make setting `max_extension` lower than `extend_to` an error, but there may be a small fraction of use cases where the ability to use lower `max_extension` is actually desired. Additional harness for reducing the error probability can be added at the SDK layer.

### `extend_contract_instance_and_code_ttl_v2` using enum to identify extension

The protocol has already accumulated several functions for extending both contract code and instance, and one of code and instance separately. In order to limit the host interface bloat and also to reduce the number of the necessary host function imports, all these variants were condensed into a single function with an additional enum argument to specify the scope of the extension.

## Protocol Upgrade Transition

### Backwards Incompatibilities

This CAP does not introduce any backward incompatibilities. The existing TTL extension host functions will still be supported in all the future protocols as to not break the existing contracts.

### Resource Utilization

Heavy use of `max_extension` may lead to increase of TTL writes for the protocols that use it, but the overall expected impact should be low. TTL write fees can be increased if necessary in order to encourage lowering the TTL write frequency.

## Security Concerns

This doesn't introduce any new risks.

## Test Cases

TBD

## Implementation

TBD
Loading