How upgrades work
Tolk provides two functions for upgrades.contract.setCodePostponed(code: cell)— schedules the code to be replaced during the action phase. The new code takes effect after the current transaction completes.contract.setData(data: cell)— immediately replaces the contract’s persistent storage. This happens during the compute phase, before the transaction ends.
setCodePostponed() applies changes after the current transaction, while setData() applies changes immediately. This means the new code won’t run until the next message arrives, but the new data is already active.
Funds at riskContract upgrades change code behavior and can affect funds or contract state. Unauthorized upgrades can cause loss of control or funds. Restrict upgrade messages to trusted admin addresses only.
EthicsUse delayed upgrades to allow users to react to compromised admin keys or unwanted updates.
Basic upgrade pattern
The contract accepts upgrade messages containing new code and data. Only the admin can trigger upgrades.How it works
- Admin sends upgrade message. The message contains new code, data, or both.
- Contract verifies sender. Checks that the sender is the admin address.
- Code is scheduled. If new code is provided,
setCodePostponed()schedules it for .replacement - Data is upgraded. If new data is provided,
setData()immediately replaces the storage. - Transaction completes. The action phase executes, applying the new code.
- Next message uses new code. Subsequent messages execute with the upgraded logic.
Example
Delayed upgrades for production safety
When upgrading protocols that are already running and have users, delayed upgrades are a best practice. This provides additional security layers: if an admin is compromised, there is time to react. Users can also see the upgrade and withdraw funds from the protocol if it has been compromised. The pattern adds a time delay between requesting and approving an upgrade. The admin must first request an upgrade, wait for a timeout period, then approve it.How it works
- Admin requests upgrade. Sends
RequestUpgrademessage with new code and data - Contract verifies and stores. Validates admin, ensures no pending request, stores upgrade details with timestamp
- Timeout period. The contract enforces a waiting period before approval
- Admin approves upgrade. Sends
ApproveUpgrademessage after timeout expires - Contract verifies timeout. Checks that enough time has passed since the request
- Upgrade applies. Schedules new code with
setCodePostponed()and upgrades data withsetData() - Request cleared. Removes the pending request from storage
RejectUpgrade at any time to cancel a pending upgrade. This three-message flow (request → wait → approve or reject) gives users time to review changes and react if the admin account is compromised.
Example
Hot upgrades for frequently upgraded contracts
Standard upgrade methods fail when a contract receives frequent updates. For example, DEX pools that update prices every second or lending protocols that continuously adjust interest rates. The problem: it is not possible to predict what data will be in storage when the upgrade transaction executes. When an upgrade message with new code and data is sent, other transactions may execute before the upgrade arrives. By the time the upgrade applies, the prepared data may be stale. For a DEX pool, this can overwrite current price data with outdated values, breaking the protocol. Hot upgrades solve this by scheduling a code change and immediately calling a migration function with the new code. The migration function runs in the same transaction that applies the upgrade. It reads the old storage structure, transforms it to match the new schema, and writes the upgraded storage. This preserves all state changes that happened between preparing the upgrade and executing it.How it works
- Admin sends upgrade message. The message contains new code cell and optional additional data
- Contract verifies sender. Checks that the sender is the admin address
- Schedule code change.
setCodePostponed()schedules the code replacement - Switch to new code.
setTvmRegisterC3()immediately activates the new code in register C3 - Call migration. Invoke
hotUpgradeData()which now runs with the new code - Migration executes. The function reads old storage, transforms it, and writes new storage
setTvmRegisterC3() switches the code register so the migration function executes with the new code in the same transaction. The migration reads the current storage state (preserving all updates), transforms it to the new schema, and saves it. When the transaction completes, the new code becomes permanent through setCodePostponed().
Hot upgrades require careful migration logic. Test migrations thoroughly on testnet. If the migration function fails, the contract becomes unusable. The
hotUpgradeData() function runs only during upgrade messages, not on regular messages, preventing accidental repeated migrations.Example
The example shows a counter contract that adds a metadata field through a hot upgrade. The storage structure changes: the original version stores onlyadminAddress and counter. The new version adds metadata and reorders fields.
Original contract (main.tolk):
hotUpgradeData() function in the original code returns null because it does not perform any migration. When the upgrade message arrives:
contract.setCodePostponed(msg.code)schedules the new codesetTvmRegisterC3()switches register C3 to the new code immediatelyhotUpgradeData(msg.additionalData)is called and runs with the new code
new.tolk):
hotUpgradeData() performs the migration:
- Loads storage using the old structure (
oldStoragewithadminAddressandcounter) - Creates new storage with the additional
metadatafield fromadditionalData - Reorders fields (
countermoves beforeadminAddress) - Writes the migrated storage immediately with
contract.setData()
When to use hot upgrades
Use hot upgrades when:- The contract receives frequent state updates (DEX pools, oracles, lending protocols)
- Storage changes between preparing and applying the upgrade would cause data loss
- You need to preserve all intermediate state transitions
- The contract upgrades infrequently
- You can predict storage state at upgrade time
- Simpler upgrade logic reduces risk
Combining delayed and hot upgrades
You can combine delayed upgrades with hot upgrades for production protocols that require both safety and structure migration. The delayed pattern provides time for users to review changes, while the hot upgrade mechanism handles storage migration without data loss.📁 Complete Example CodeYou can find full working examples demonstrating all upgrade patterns in our GitHub repository. This includes implementations for basic, delayed, and hot upgrade patterns.