diff --git a/docs/build/guides/archival/create-restoration-footprint-js.mdx b/docs/build/guides/archival/create-restoration-footprint-js.mdx index e2943b03cd..01259c817d 100644 --- a/docs/build/guides/archival/create-restoration-footprint-js.mdx +++ b/docs/build/guides/archival/create-restoration-footprint-js.mdx @@ -67,12 +67,12 @@ async function createRestorationFootprint( readWrite: [contractDataXDR], }), instructions: 0, - readBytes: 0, + diskReadBytes: 0, writeBytes: 0, }), resourceFee: xdr.Int64.fromString("0"), // @ts-ignore - ext: new xdr.ExtensionPoint(0), + ext: new xdr.SorobanTransactionDataExt(0), }); // Restore transaction with created restoration footprint const restoreTx: Transaction = new TransactionBuilder(account, { fee: fee }) @@ -107,10 +107,16 @@ Next up we are preparing the (Soroban) transaction data with: - `resources`: Specifies the resources needed for the transaction. - `footprint`: Defines which parts of the ledger will be read and written. -- `instructions`, `readBytes`, `writeBytes`: Sets the resource limits (all set to 0 here). +- `instructions`, `diskReadBytes`, `writeBytes`: Sets the resource limits (all set to 0 here). - `resourceFee`: Sets the resource fee to 0 (placeholder). - `ext`: Extension point for future use (set to 0). Note that for restoration footprints we only need to fill in `readWrite`. +:::caution + +The `diskReadBytes`, `writeBytes`, and `resourceFee` values above are zeroed **placeholders** to keep the example focused on footprint construction. A real restoration will not succeed with these left at `0` — Core compares the restored entry's serialized size against the declared resources and rejects the operation (`RESTORE_FOOTPRINT_RESOURCE_LIMIT_EXCEEDED`) when they are too low. Before signing, populate valid resource values by running the transaction through [simulation](../../../learn/fundamentals/contract-development/contract-interactions/transaction-simulation.mdx) (for example with `server.prepareTransaction`), which fills in the required resources and fee for you. + +::: + The transaction can now be submitted to the Stellar (test) network & signed to restore the specified contract’s data. diff --git a/docs/build/guides/archival/extend-persistent-entry-js.mdx b/docs/build/guides/archival/extend-persistent-entry-js.mdx index ba8a634b41..2856d5cd04 100644 --- a/docs/build/guides/archival/extend-persistent-entry-js.mdx +++ b/docs/build/guides/archival/extend-persistent-entry-js.mdx @@ -3,136 +3,283 @@ title: Extend a persistent entry and contract using the JavaScript SDK description: How to extend a persistent entry and contract using the JavaScript SDK --- -Persistent storage provides a durable mechanism to retain data on the network over a long period. User data, contract instances and their corresponding code (wasm hash) are stored as instance storage with persistent durability to ensure that they remain available, and are managed efficiently by the network's archival system. +Persistent storage gives you a durable place to keep contract data on the network for a long time. Anything you can't cheaply recreate (user balances, configuration, and the like) belongs there. A contract's instance entry and the Wasm code entry it points to are separate ledger entries, but they live under the same archival rules, so they need the same care. -Each entry made to the persistent storage is stored as a key-value store. An entry consists of: +Every persistent entry on the ledger is a key-value pair made up of: -- A key (identifier for the data) -- A value (the data itself) -- A Time-To-Live (TTL) that determines how long the data remains on the network +- a key (the identifier for the data), +- a value (the data itself), and +- a [Time To Live](../../../learn/fundamentals/contract-development/storage/state-archival.mdx#ttl) (TTL), which is how many ledgers remain until the entry is no longer live. (The absolute cutoff is stored on the entry itself as its [`liveUntilLedger`](../../../learn/fundamentals/contract-development/storage/state-archival.mdx#live-until-ledger).) -When the TTL expires, the data is then archived on the network and is no longer accessible unless it is unarchived. To prevent data and contracts from being inaccessible, the TTL can be extended before it expires. +Once an entry's TTL runs out, the network archives it. Archived persistent data isn't lost (it can be [restored](./restore-data-js.mdx)), but nothing can read it until that happens. Keeping the TTL topped up before it expires is the cheaper, less exciting path, and that's what we'll walk through here. -## Extension Process +By the end of this guide you'll have four helper functions you can drop into a project: one that extends a single persistent entry, one for a contract's instance, one for the Wasm code that instance points to, and one that extends all three together in a single operation. -1. Create the Ledger key for the entry to be extended. -2. Build a transaction that includes the `extendFootprintTtl` operation with your target TTL and attach the Soroban transaction data. -3. Prepare the transaction, sign it with your secret key, and submit it to the network. +## The extension process -### Extending a persistent entry +1. Build the ledger key for the entry you want to extend. +2. Build a transaction containing an `extendFootprintTtl` operation with your target TTL, and attach the Soroban transaction data. +3. Prepare the transaction (this runs simulation to fill in the resources and fee for you), sign it with your secret key, and submit it to the network. -```javascript -const StellarSdk = require("@stellar/stellar-sdk"); -const { Server } = require("@stellar/stellar-sdk/rpc"); +:::note -async function extendPersistentEntryTTL(contractId, storageKey, sourceKeypair) { +`extendFootprintTtl` sets a _floor_, not an exact value. Per the [`ExtendFootprintTTLOp` semantics](../../../learn/fundamentals/contract-development/storage/state-archival.mdx#extendfootprintttlop), the operation guarantees each entry in the read-only footprint lives for _at least_ `extendTo` ledgers from now. Entries that already live longer than your target are left alone, so a repeated extension is harmless. + +There's also a ceiling: an entry can only be extended up to the network's [maximum TTL](../../../learn/fundamentals/contract-development/storage/state-archival.mdx#maximum-ttl), which is a network parameter (check the [network limits](https://lab.stellar.org/network-limits) in the Stellar Lab for the current value). + +::: + +## Extending a persistent entry + +The example below assumes your contract keys its data with a symbol, which is the common case. If your contract uses a vec, a map, or a struct as its storage key, build the `ScVal` that matches instead of reaching for `scvSymbol`. + +```typescript +import * as StellarSdk from "@stellar/stellar-sdk"; +import { Server } from "@stellar/stellar-sdk/rpc"; + +async function extendPersistentEntryTTL( + contractId: string, + storageKey: string | Buffer, + sourceKeypair: StellarSdk.Keypair, +) { const server = new Server("https://soroban-testnet.stellar.org"); const account = await server.getAccount(sourceKeypair.publicKey()); const fee = "100"; // BASE_FEE of 100 stroops // Create an identifier for the persistent entry - const persistentEntry = new StellarSdk.xdr.LedgerKey.contractData({ - contract: StellarSdk.Address.fromString(contractId).toScAddress(), - key: StellarSdk.xdr.ScVal.scvSymbol(storageKey), - durability: StellarSdk.xdr.ContractDataDurability.persistent(), - }); + const persistentEntry = StellarSdk.xdr.LedgerKey.contractData( + new StellarSdk.xdr.LedgerKeyContractData({ + contract: StellarSdk.Address.fromString(contractId).toScAddress(), + key: StellarSdk.xdr.ScVal.scvSymbol(storageKey), + durability: StellarSdk.xdr.ContractDataDurability.persistent(), + }), + ); // Build the transaction - let transaction = new StellarSdk.TransactionBuilder(account, { - fee, - networkPassphrase: StellarSdk.Networks.TESTNET, - }) - .addOperation( - StellarSdk.Operation.extendFootprintTtl({ - extendTo: 100_000, // The number of ledgers past the LCL (last closed ledger) by which to extend. Roughly 5 days - }), - ) - .setTimeout(30) - .build(); + let transaction: StellarSdk.Transaction | StellarSdk.TransactionBuilder = + new StellarSdk.TransactionBuilder(account, { + fee, + networkPassphrase: StellarSdk.Networks.TESTNET, + }) + .addOperation( + StellarSdk.Operation.extendFootprintTtl({ + extendTo: 100_000, // The number of ledgers past the LCL (last closed ledger) by which to extend. Roughly 5 days + }), + ) + .setTimeout(30); // Attach Soroban transaction data const sorobanData = new StellarSdk.SorobanDataBuilder() - .setReadOnly(persistentEntry) + .setReadOnly([persistentEntry]) .build(); transaction.setSorobanData(sorobanData); // Prepare the transaction for signing. - transaction = await server.prepareTransaction(transaction); + transaction = await server.prepareTransaction(transaction.build()); transaction.sign(sourceKeypair); return await server.sendTransaction(transaction); } ``` -### Extending the contract code +## Extending the contract instance -```javascript -const StellarSdk = require("@stellar/stellar-sdk"); -const { Server } = require("@stellar/stellar-sdk/rpc"); +`Contract.getFootprint()` hands you the ledger key for the deployed contract instance, so you don't have to assemble that one by hand. -async function extendContractCodeTTL(contractId, sourceKeypair) { +:::note + +This extends the contract _instance_ only. The Wasm code the instance points to is a separate ledger entry with its own TTL, and an `extendFootprintTtl` operation only touches the keys you actually put in the read-only footprint. A live instance backed by archived code still leaves your contract unusable, so the next section covers the code entry too. + +If you're coming from the Rust side, this is a real difference worth internalizing: inside a contract, `env.storage().instance().extend_ttl()` extends the instance and the code together. The operation used here does not. + +::: + +```typescript +import * as StellarSdk from "@stellar/stellar-sdk"; +import { Server } from "@stellar/stellar-sdk/rpc"; + +async function extendContractInstanceTTL( + contractId: string, + sourceKeypair: StellarSdk.Keypair, +) { const server = new Server("https://soroban-testnet.stellar.org"); const account = await server.getAccount(sourceKeypair.publicKey()); const fee = "100"; - // Get the footprint + // Get the ledger key for the contract instance const contract = new StellarSdk.Contract(contractId); const footprint = contract.getFootprint(); // Build the transaction - let transaction = new StellarSdk.TransactionBuilder(account, { - fee, - networkPassphrase: StellarSdk.Networks.TESTNET, - }) - .addOperation( - StellarSdk.Operation.extendFootprintTtl({ - extendTo: 100_000, //The number of ledgers past the LCL by which to extend - footprint: footprint, - }), - ) - .setTimeout(30) + let transaction: StellarSdk.Transaction | StellarSdk.TransactionBuilder = + new StellarSdk.TransactionBuilder(account, { + fee, + networkPassphrase: StellarSdk.Networks.TESTNET, + }) + .addOperation( + StellarSdk.Operation.extendFootprintTtl({ + extendTo: 100_000, //The number of ledgers past the LCL by which to extend + }), + ) + .setTimeout(30); + + // Attach Soroban transaction data + const sorobanData = new StellarSdk.SorobanDataBuilder() + .setReadOnly([footprint]) .build(); + transaction.setSorobanData(sorobanData); + + // Prepare the transaction + transaction = await server.prepareTransaction(transaction.build()); + + transaction.sign(sourceKeypair); + return await server.sendTransaction(transaction); +} +``` + +## Extending the contract code + +The code entry is keyed by the Wasm hash rather than by the contract address, and there's no local way to derive one from the other. That means one extra round trip: read the contract's instance entry from the network, pull the Wasm hash out of its executable, and build a `contractCode` ledger key from it. + +It's worth knowing every contract deployed from the same Wasm shares that single code entry, so extending it benefits all of them at once. + +```typescript +import * as StellarSdk from "@stellar/stellar-sdk"; +import { Server } from "@stellar/stellar-sdk/rpc"; + +// Look up the hash of the Wasm code that a deployed contract runs. +async function getWasmHash(server: Server, contractId: string) { + const contract = new StellarSdk.Contract(contractId); + const { entries } = await server.getLedgerEntries(contract.getFootprint()); + + if (!entries || entries.length === 0) { + throw new Error(`No instance entry found for ${contractId}`); + } + + const instance = entries[0].val.contractData().val().instance(); + return instance.executable().wasmHash(); +} + +async function extendContractCodeTTL( + contractId: string, + sourceKeypair: StellarSdk.Keypair, +) { + const server = new Server("https://soroban-testnet.stellar.org"); + const account = await server.getAccount(sourceKeypair.publicKey()); + const fee = "100"; + + // Create an identifier for the Wasm code entry + const codeEntry = StellarSdk.xdr.LedgerKey.contractCode( + new StellarSdk.xdr.LedgerKeyContractCode({ + hash: await getWasmHash(server, contractId), + }), + ); + + // Build the transaction + let transaction: StellarSdk.Transaction | StellarSdk.TransactionBuilder = + new StellarSdk.TransactionBuilder(account, { + fee, + networkPassphrase: StellarSdk.Networks.TESTNET, + }) + .addOperation( + StellarSdk.Operation.extendFootprintTtl({ + extendTo: 100_000, // The number of ledgers past the LCL by which to extend + }), + ) + .setTimeout(30); // Attach Soroban transaction data const sorobanData = new StellarSdk.SorobanDataBuilder() - .setFootprint(footprint) + .setReadOnly([codeEntry]) .build(); transaction.setSorobanData(sorobanData); // Prepare the transaction - transaction = await server.prepareTransaction(transaction); + transaction = await server.prepareTransaction(transaction.build()); transaction.sign(sourceKeypair); return await server.sendTransaction(transaction); } ``` -### Extending both the contract and a persistent entry +:::caution + +`getWasmHash` assumes the contract runs an executable that _was deployed_. A [Stellar Asset Contract](../../../tokens/stellar-asset-contract.mdx) instance has a built-in executable instead of a Wasm hash, so `executable().wasmHash()` will throw for one of those. Check `instance.executable().switch()` first if your code has to handle both. + +::: + +## Extending all three together -In Stellar, each Soroban operation must be the only Soroban operation in a single transaction. Additionally, persistent ledger entries have their own TTL values that are evaluated independently. Because the TTL extension operation only accepts one ledger footprint per transaction, you cannot combine extensions for different ledger entries in a single transaction. Instead, you must create two separate transactions to ensure each ledger key is extended correctly. +A Soroban operation has to be the only operation in its transaction, so you might expect to need one transaction per entry. Happily, you don't. A single `ExtendFootprintTTLOp` extends _every_ entry listed in the transaction's read-only footprint, applying the same `extendTo` target to all of them. When a batch of entries should live until the same ledger, list them all in one footprint and extend them together. Here's all three at once, reusing the `getWasmHash` helper from the previous section: + +```typescript +import * as StellarSdk from "@stellar/stellar-sdk"; +import { Server } from "@stellar/stellar-sdk/rpc"; -```javascript async function extendContractAndPersistentEntry( - contractId, - storageKey, - sourceKeypair, + contractId: string, + storageKey: string | Buffer, + sourceKeypair: StellarSdk.Keypair, ) { - // Extend the persistent entry - const persistentResult = await extendPersistentEntryTTL( - contractId, - storageKey, - sourceKeypair, + const server = new Server("https://soroban-testnet.stellar.org"); + const account = await server.getAccount(sourceKeypair.publicKey()); + const fee = "100"; + + // Ledger key for the persistent entry... + const persistentEntry = StellarSdk.xdr.LedgerKey.contractData( + new StellarSdk.xdr.LedgerKeyContractData({ + contract: StellarSdk.Address.fromString(contractId).toScAddress(), + key: StellarSdk.xdr.ScVal.scvSymbol(storageKey), + durability: StellarSdk.xdr.ContractDataDurability.persistent(), + }), + ); + + // ...the contract instance... + const contract = new StellarSdk.Contract(contractId); + + // ...and the Wasm code that instance runs. + const codeEntry = StellarSdk.xdr.LedgerKey.contractCode( + new StellarSdk.xdr.LedgerKeyContractCode({ + hash: await getWasmHash(server, contractId), + }), ); - // Extend the contract code - const contractResult = await extendContractCodeTTL(contractId, sourceKeypair); - return { persistentResult, contractResult }; + // A single ExtendFootprintTtl operation extends the whole read-only footprint. + let transaction: StellarSdk.Transaction | StellarSdk.TransactionBuilder = + new StellarSdk.TransactionBuilder(account, { + fee, + networkPassphrase: StellarSdk.Networks.TESTNET, + }) + .addOperation( + StellarSdk.Operation.extendFootprintTtl({ + extendTo: 100_000, // one shared target, applied to every key below + }), + ) + .setTimeout(30); + + // List every key to extend together in one read-only footprint. + const sorobanData = new StellarSdk.SorobanDataBuilder() + .setReadOnly([persistentEntry, contract.getFootprint(), codeEntry]) + .build(); + transaction.setSorobanData(sorobanData); + + transaction = await server.prepareTransaction(transaction.build()); + transaction.sign(sourceKeypair); + return await server.sendTransaction(transaction); } ``` -Usage example: +That shared `extendTo` is the one real constraint. Entries that need _different_ target TTLs can't share an operation, so extend each one in its own transaction. That's exactly what the `extendPersistentEntryTTL`, `extendContractInstanceTTL`, and `extendContractCodeTTL` helpers above do: each builds its own operation, so each can carry its own `extendTo` target. + +:::note + +Batching keys into one footprint grows the transaction's resource usage, since `diskReadBytes` has to cover the serialized size of every entry in the read-only set. `server.prepareTransaction` works that out for you, but a large enough batch can bump into the network's transaction limits. If that happens, split the batch across a few transactions. + +::: + +Putting one of these to work looks like this: -```javascript +```typescript const contractId = "CBEE2DHGMYRKJKSJO5E55LBKFMPXE57ZWKTXTCRMC5ANIRJ7IW2Y2WVE"; const storageKey = "BALANCE"; const sourceKeypair = StellarSdk.Keypair.fromSecret( diff --git a/docs/learn/fundamentals/contract-development/contract-interactions/stellar-transaction.mdx b/docs/learn/fundamentals/contract-development/contract-interactions/stellar-transaction.mdx index b520607275..df8c027db1 100644 --- a/docs/learn/fundamentals/contract-development/contract-interactions/stellar-transaction.mdx +++ b/docs/learn/fundamentals/contract-development/contract-interactions/stellar-transaction.mdx @@ -414,14 +414,14 @@ The `hostFunction` in `InvokeHostFunctionOp` will be executed by the Soroban hos enum ContractExecutableType { CONTRACT_EXECUTABLE_WASM = 0, - CONTRACT_EXECUTABLE_TOKEN = 1 + CONTRACT_EXECUTABLE_STELLAR_ASSET = 1 }; union ContractExecutable switch (ContractExecutableType type) { case CONTRACT_EXECUTABLE_WASM: Hash wasm_hash; - case CONTRACT_EXECUTABLE_TOKEN: + case CONTRACT_EXECUTABLE_STELLAR_ASSET: void; }; ``` @@ -444,7 +444,7 @@ The `hostFunction` in `InvokeHostFunctionOp` will be executed by the Soroban hos - The final contract identifier is created by computing SHA-256 of this together with the network identifier as a part of [`HashIDPreimage`]: - [`hashidpreimage`]: https://github.com/stellar/stellar-xdr/blob/e372df9f677961aac04c5a4cc80a3667f310b29f/Stellar-transaction.x#L697 + [`hashidpreimage`]: https://github.com/stellar/stellar-xdr/blob/v27.0/Stellar-transaction.x#L721-L762 ```cpp union HashIDPreimage switch (EnvelopeType type) @@ -460,7 +460,7 @@ The `hostFunction` in `InvokeHostFunctionOp` will be executed by the Soroban hos ``` - `CONTRACT_ID_PREIMAGE_FROM_ADDRESS` specifies that the contract will be created using the provided address and salt. This operation has to be authorized by `address` (see the following section for details). - - `CONTRACT_ID_FROM_ASSET` specifies that the contract will be created using the Stellar asset. This is only supported when `executable == CONTRACT_EXECUTABLE_TOKEN`. Note, that the asset doesn't need to exist when this is applied, however the issuer of the asset will be the initial token administrator. Anyone can deploy asset contracts. + - `CONTRACT_ID_PREIMAGE_FROM_ASSET` specifies that the contract will be created using the Stellar asset. This is only supported when `executable == CONTRACT_EXECUTABLE_STELLAR_ASSET`. Note, that the asset doesn't need to exist when this is applied, however the issuer of the asset will be the initial token administrator. Anyone can deploy asset contracts. ##### JavaScript Usage @@ -546,7 +546,7 @@ struct SorobanAuthorizedContractFunction Building `SorobanAuthorizedInvocation` trees may be simplified by using the recording auth mode in Soroban's `simulateTransaction` mechanism (see the [docs][simulate-transaction-doc] for more details). -[envelope-xdr]: https://github.com/stellar/stellar-xdr/blob/e372df9f677961aac04c5a4cc80a3667f310b29f/Stellar-transaction.x#L703 +[envelope-xdr]: https://github.com/stellar/stellar-xdr/blob/v27.0/Stellar-transaction.x#L747-L754 [simulate-transaction-doc]: transaction-simulation.mdx#authorization ##### Stellar Account Signatures @@ -596,7 +596,7 @@ const signedEntries = simTx.auth.map(async (entry) => Every Soroban transaction has to have a `SorobanTransactionData` transaction [extension] populated. This is needed to compute the [Soroban resource fee](../../../fundamentals/fees-resource-limits-metering.mdx). -[extension]: https://github.com/stellar/stellar-xdr/blob/c2e702c70951ff59a1eff257f08cf38d47210e5f/Stellar-transaction.x#L874 +[extension]: https://github.com/stellar/stellar-xdr/blob/v27.0/Stellar-transaction.x#L957-L964 The Soroban transaction data is defined as follows: @@ -608,26 +608,40 @@ struct SorobanResources // The maximum number of instructions this transaction can use uint32 instructions; - // The maximum number of bytes this transaction can read from ledger - uint32 readBytes; + // The maximum number of bytes this transaction can read from disk backed entries + uint32 diskReadBytes; // The maximum number of bytes this transaction can write to ledger uint32 writeBytes; }; +struct SorobanResourcesExtV0 +{ + // Vector of indices representing what Soroban + // entries in the footprint are archived, based on the + // order of keys provided in the readWrite footprint. + uint32 archivedSorobanEntries<>; +}; + struct SorobanTransactionData { + union switch (int v) + { + case 0: + void; + case 1: + SorobanResourcesExtV0 resourceExt; + } ext; SorobanResources resources; - // Portion of transaction `fee` allocated to refundable fees. - int64 refundableFee; - ExtensionPoint ext; + // Amount of the transaction `fee` allocated to the Soroban resource fees. + int64 resourceFee; }; ``` -This data comprises two parts: Soroban resources and the `refundableFee`. The `refundableFee` is the portion of the transaction fee eligible for refund. It includes the fees for the contract events emitted by the transaction, the return value of the host function invocation, and fees for the [ledger space rent](../storage/state-archival.mdx). +This data comprises the Soroban `resources` and the `resourceFee`. The `resourceFee` is the portion of the transaction fee allocated to Soroban resource fees. It has a non-refundable part (fees for instructions, ledger I/O, and transaction size) and a refundable part that is charged based on actual consumption of refundable resources: the contract events emitted by the transaction, the return value of the host function invocation, and the [ledger space rent](../storage/state-archival.mdx). The `SorobanResources` structure includes the ledger footprint and the resource values, which together determine the resource consumption limit and the resource fee. The footprint must contain the `LedgerKey`s that will be read and/or written. -The simplest method to determine the values in `SorobanResources` and `refundableFee` is to use the [`simulateTransaction` mechanism](transaction-simulation.mdx). +The simplest method to determine the values in `SorobanResources` and `resourceFee` is to use the [`simulateTransaction` mechanism](transaction-simulation.mdx). #### JavaScript Usage diff --git a/docs/learn/fundamentals/contract-development/storage/state-archival.mdx b/docs/learn/fundamentals/contract-development/storage/state-archival.mdx index ebcc248ce3..2b57c5b095 100644 --- a/docs/learn/fundamentals/contract-development/storage/state-archival.mdx +++ b/docs/learn/fundamentals/contract-development/storage/state-archival.mdx @@ -157,7 +157,7 @@ liveUntilLedger that is large enough. #### Transaction resources -`ExtendFootprintTTLOp` is a Soroban operation, and therefore must be the only operation in a transaction. The transaction also needs to populate `SorobanTransactionData` transaction extension explained [here](../contract-interactions/stellar-transaction.mdx#transaction-resources). To fill out `SorobanResources`, use the transaction simulation mentioned in the provided link, or make sure `readBytes` includes the key and entry size of every entry in the `readOnly` set. +`ExtendFootprintTTLOp` is a Soroban operation, and therefore must be the only operation in a transaction. The transaction also needs to populate `SorobanTransactionData` transaction extension explained [here](../contract-interactions/stellar-transaction.mdx#transaction-resources). To fill out `SorobanResources`, use the transaction simulation mentioned in the provided link, or make sure `diskReadBytes` covers the serialized size of every entry in the `readOnly` set. ### RestoreFootprintOp diff --git a/docs/learn/fundamentals/fees-resource-limits-metering.mdx b/docs/learn/fundamentals/fees-resource-limits-metering.mdx index f21255a4fd..cab2d7dc58 100644 --- a/docs/learn/fundamentals/fees-resource-limits-metering.mdx +++ b/docs/learn/fundamentals/fees-resource-limits-metering.mdx @@ -60,7 +60,7 @@ The resource fee is calculated with a non-refundable fees portion and a refundab **Non-refundable fees:** calculated from CPU instructions, read bytes, write bytes, and bandwidth (transaction size, including its signatures). -**Refundable fees:** calculated from rent, events, and return value. Refundable fees are charged from the source account before the transaction is executed and then refunded based on actual usage. However, the transaction will fail if `refundableFee` is not enough to cover the actual resource usage. +**Refundable fees:** calculated from rent, events, and return value. These fees are enforced in two distinct phases. Before execution, the declared `sorobanData.resourceFee` must be at least the fee computed from the transaction's _declared_ resources, or the transaction is rejected as invalid (`txSOROBAN_INVALID`). The refundable portion is then charged from the source account and, during execution, reconciled against actual usage (rent, events, and return value); if the refundable budget is not enough for the resources actually consumed, the transaction fails at apply time and any unused portion is refunded. ### Find a transaction’s resource fee diff --git a/docs/tokens/stellar-asset-contract.mdx b/docs/tokens/stellar-asset-contract.mdx index 5a1496d2b7..dd2aad53b7 100644 --- a/docs/tokens/stellar-asset-contract.mdx +++ b/docs/tokens/stellar-asset-contract.mdx @@ -53,11 +53,13 @@ Every Stellar asset on Stellar has reserved a contract address that the Stellar It can be deployed using the [Stellar CLI] as shown [here](../tools/cli/cookbook/deploy-stellar-asset-contract.mdx). -Or the [Stellar SDK] can be used as shown [here](../learn/fundamentals/contract-development/contract-interactions/stellar-transaction.mdx#xdr-usage) by calling `InvokeHostFunctionOp` with `HOST_FUNCTION_TYPE_CREATE_CONTRACT` and `CONTRACT_ID_FROM_ASSET`. The resulting token will have a deterministic identifier, which will be the sha256 hash of `HashIDPreimage::ENVELOPE_TYPE_CONTRACT_ID_FROM_ASSET` xdr specified [here][contract_id]. +Or the [Stellar SDK] can be used as shown [here](../learn/fundamentals/contract-development/contract-interactions/stellar-transaction.mdx#xdr-usage) by calling `InvokeHostFunctionOp` with `HOST_FUNCTION_TYPE_CREATE_CONTRACT` and a `CONTRACT_ID_PREIMAGE_FROM_ASSET` contract ID preimage. The resulting token will have a deterministic identifier, which will be the sha256 hash of `HashIDPreimage::ENVELOPE_TYPE_CONTRACT_ID` xdr specified [here][contract_id]. -Anyone can deploy the instances of Stellar Asset Contract. Note, that the initialization of the Stellar Asset Contracts happens automatically during the deployment. Asset Issuer will have the administrative permissions after the contract has been deployed. +Anyone can deploy the instances of Stellar Asset Contract. Note, that the initialization of the Stellar Asset Contracts happens automatically during the deployment. The asset issuer becomes the _initial_ administrator once the contract has been deployed; administrative authority is mutable and can be transferred afterwards with the SAC's `set_admin` function. -[contract_id]: https://github.com/stellar/stellar-xdr/blob/dc23adf60e095a6ce626b2b09128e58a5eae0cd0/Stellar-transaction.x#L661 +A deployed SAC can be identified on-chain from its contract instance: its `SCContractInstance.executable` is `CONTRACT_EXECUTABLE_STELLAR_ASSET` rather than a Wasm hash. Once a contract is verified as a SAC this way, its built-in `name()` and `symbol()` methods report the wrapped asset's identity — there is no generic SEP-41 `asset()`/`issuer()` accessor to rely on. Note that a contract-address hash and the current `admin` value are not, by themselves, proof of an asset's provenance: verify the executable first, since the administrator may have been changed via `set_admin`. + +[contract_id]: https://github.com/stellar/stellar-xdr/blob/curr/Stellar-transaction.x [stellar cli]: ../tools/cli/stellar-cli.mdx [stellar sdk]: ../tools/sdks/README.mdx @@ -65,7 +67,7 @@ Anyone can deploy the instances of Stellar Asset Contract. Note, that the initia The Stellar Asset Contract is the only way for contracts to interact with Stellar assets, either the native XLM asset, or those issued by Stellar accounts. -The issuer of the asset will be the administrator of the deployed contract. Because the Native Stellar token doesn't have an issuer, it will not have an administrator either. It also cannot be burned. +The issuer of the asset will be the initial administrator of the deployed contract (administrative authority can later be moved with `set_admin`). Because the Native Stellar token doesn't have an issuer, it will not have an administrator either. It also cannot be burned. After the contract has been deployed, users can use their classic account (for lumens) or trustline (for other assets) balance. There are some differences depending on if you are using a classic account `Address` vs a contract `Address` (corresponding either to a regular contract or to a custom account contract). The following section references some issuer and trustline flags from Stellar classic, which you can learn more about [here](./control-asset-access.mdx#controlling-access-to-an-asset-with-flags).