Skip to content
Merged
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
60 changes: 31 additions & 29 deletions docs/developers/best-practices/prepare-sc-supernova.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: Preparing SCs for Supernova
description: "Checklist to prepare smart contracts for the Supernova upgrade: timing changes, assumptions and safe migrations."
---

The MultiversX Supernova upgrade reduces block time from **6 seconds to 0.6 seconds**, enabling sub-second blocks. While this is a major improvement, it can impact existing smart contracts, especially those relying on assumptions about timestamp behavior.
The MultiversX Supernova upgrade changes the configured round duration from **6 seconds to 0.6 seconds**, enabling sub-second blocks. While this is a major improvement, it can impact existing smart contracts, especially those relying on assumptions about timestamp behavior.

This guide explains how to prepare your contracts for Supernova safely.

Expand All @@ -14,9 +14,9 @@ This guide explains how to prepare your contracts for Supernova safely.

## Understand What Changes — and What Doesn’t

All existing timestamp APIs continue returning **seconds** unless you explicitly call the millisecond versions. Nothing changes silently in the VM, the framework, or deployed contracts.
APIs that return timestamps in **seconds** keep that unit. Contracts do not need to switch to milliseconds unless their required precision or timing logic calls for it.

Obviously, contracts that have the 6 seconds between blocks hardcoded will have to change. But more importantly, block times expressed in seconds no longer uniquely identify a block, which leads to potential problems.
Review assumptions about block frequency and timestamp uniqueness: consecutive blocks in the same shard may now share the same timestamp in seconds.

We are going to go through the most important patterns to look out for.

Expand All @@ -30,11 +30,11 @@ We are going to go through the most important patterns to look out for.

### Replace Hardcoded Block Timing

If your contract uses hard-coded constants (like `6 seconds` or `6000 milliseconds`) to estimate the time between blocks, this logic will need to be changed.
Review hardcoded timing constants such as `6 seconds`, `6000 milliseconds`, or a number of blocks per day. A business duration of six seconds may remain valid; an assumption about block cadence may not.

It might estimate durations from nonce deltas, or vice-versa, it might estimate number of blocks by timestamps.
For elapsed time, subtract block timestamps. Multiplying a nonce difference by the round duration is not reliable because rounds can pass without producing a block.

**Fix:** Use the API instead: `self.blockchain().get_block_round_time_millis()`. This returns `6000` (as `DurationMillis`) today and `600` after Supernova.
**Fix:** For logic expressed in rounds, use `self.blockchain().get_block_round_time_millis()`. It returns the configured duration (`6000` milliseconds before Supernova, `600` after) as `DurationMillis`. Do not apply the current duration retroactively to historical data from a different cadence.



Expand All @@ -50,46 +50,46 @@ require!(ts_now > last_ts)

may break because multiple blocks can share the same timestamp.

**Fix:** Use **block nonces** for guaranteed monotonicity.
**Fix:** Use **block nonces** to distinguish blocks within the execution shard. Use timestamp differences when the requirement is elapsed time.



[comment]: # (mx-context-auto)

### Prevent Rate-Limit Bypasses
### Review Timestamp-Based Rate Limiting

If your contract allows one action “per block” but checks the difference in **seconds**, multiple blocks in the same second can bypass restrictions.
If your contract allows one action “per block” but compares timestamps in **seconds**, valid calls in later blocks may be rejected. A per-block rule also permits more actions per minute as blocks become more frequent.

**Fix:** Use block nonces or switch to millisecond timestamps.
**Fix:** Use block nonces for per-block limits and timestamp differences for time-based cooldowns. Use milliseconds when sub-second precision matters.



[comment]: # (mx-context-auto)

### Revisit Expiration Logic

Expiration logic written assuming a fixed block interval may accidentally allow:
Check whether expiration is defined by elapsed time or by a number of blocks:

* extra blocks before expiration
* longer-than-intended windows for execution
* `timestamp + 60 seconds` keeps the same time window, with more blocks inside it
* `nonce + 100 blocks` takes approximately 60 seconds instead of 600, assuming no missed rounds

If your expiration logic uses seconds, double-check assumptions.
Use timestamps for time-based deadlines and test calls immediately before, at, and after expiration.



[comment]: # (mx-context-auto)

### Stop Using Timestamps as Block Identifiers

Before Supernova, a timestamp could uniquely identify a block.
After Supernova, multiple blocks may share the same timestamp.
Timestamps were never globally unique block identifiers.
After Supernova, even consecutive blocks in the same shard may share a timestamp in seconds.

If you use timestamps as map keys or identifiers:

* collisions will occur
* collisions may occur
* data may be overwritten or skipped

**Fix:** Use block **nonces**.
**Fix:** Use block **nonces** within the execution shard. Off-chain systems combining shards must retain the shard identifier as well.



Expand All @@ -106,10 +106,10 @@ delta = ts_now - last_ts
may behave unexpectedly:

* delta may be zero over multiple blocks
* rewards might accumulate slower or unevenly
* rounding on frequent calls may discard small accrued amounts
* divisions by delta may cause division-by-zero errors

Consider switching to milliseconds for finer granularity.
Handle zero deltas explicitly and check when the last-accounted timestamp advances. Use milliseconds when finer granularity is needed. Also review per-block reward rates: leaving them unchanged can increase rewards per minute as blocks become more frequent.



Expand All @@ -133,18 +133,18 @@ The most dangerous bug is accidentally mixing second and millisecond values, cau

To prevent this issue, use the [strongly typed timestamp and duration objects](time-types).

Starting in **multiversx-sc v0.63.0**, all timestamp APIs have typed replacements:
Typed time APIs are available since **multiversx-sc v0.63.0**. Their names in v0.63.1 and later include:

* `get_block_timestamp_seconds()`
* `get_block_timestamp_millis()`
* `get_prev_block_timestamp_seconds()`
* `get_prev_block_timestamp_millis()`
* `get_block_round_time_millis()`
* `epoch_start_block_timestamp_millis()`
* `get_epoch_start_block_timestamp_millis()`

And avoid using raw `u64` for time values.

Make sure to convert form `u64` to type timestamps **before** doing any other refactoring. It is much safer this way.
First replace raw `u64` values with typed values of the **same unit**. Changing the type does not convert stored values: decoding `1000` seconds as `TimestampMillis` preserves `1000`, rather than producing `1_000_000` milliseconds. Convert units explicitly and check overflow and rounding.



Expand All @@ -154,9 +154,9 @@ Make sure to convert form `u64` to type timestamps **before** doing any other re

When upgrading an existing contract from second to millisecond timestamps, it is essential to:

* Ensure storage remains consistent
* Update ESDT metadata carefully
* Add compatibility logic if needed
* Convert existing storage and token attributes explicitly if their time units change
* Preserve the units of public endpoints and events, or coordinate changes with their consumers
* Add compatibility decoding if stored layouts change, and test upgrades with existing data

If you are unsure that this can be done safely, it might be safer to keep the contract running on second timestamps.

Expand All @@ -168,11 +168,13 @@ If you are unsure that this can be done safely, it might be safer to keep the co

To prepare for Supernova:

* [ ] Upgrade to `multiversx-sc v0.63.0`
* [ ] If adopting typed time APIs, use a compatible SDK version (v0.63.1+ for the names above)
* [ ] Use typed timestamp/duration APIs
* [ ] Remove all hardcoded 6-second or 6000-millisecond assumptions
* [ ] Replace hardcoded current-cadence assumptions where they affect intended behavior
* [ ] Avoid using timestamps as block identifiers
* [ ] Review expiration, reward, and accumulation logic
* [ ] Switch to millisecond timestamps where appropriate
* [ ] Ensure storage/metadata compatibility
* [ ] Update tests to use for millisecond block timestamps, where needed
* [ ] Test both cadences, same-second blocks, zero deltas, missed rounds, and intervals spanning activation

In RustVM tests, set millisecond block timestamps for sub-second cases; the seconds API derives its value by truncation. A later seconds-only state update discards sub-second precision.
6 changes: 3 additions & 3 deletions docs/developers/best-practices/time-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ All four types:
* can be used in events and arguments
* behave like `u64` for serialization

This makes them safe to adopt even in existing contracts.
This makes the types ABI-compatible with existing `u64` values, but it does not convert the unit of an existing value. For example, decoding a seconds value directly as `TimestampMillis` preserves the numeric value instead of multiplying it by `1000`. Existing storage, endpoint semantics, events, and token attributes must be converted explicitly if their unit changes.


[comment]: # (mx-context-auto)
Expand All @@ -101,13 +101,13 @@ In blackbox tests, one can write:

world
.epoch_start_block()
.block_timestamp_ms(block_timestamp_ms)
.block_timestamp_millis(block_timestamp_ms)
.block_nonce(15_000)
.block_round(17_000);
```


Mandos supports both `blockTimestamp` and the newer `blockTimestampMs`. Set both if they are used together for backward compatibility.
Scenario files support both `blockTimestamp` (seconds) and `blockTimestampMs` (milliseconds). In RustVM, they update the same underlying timestamp. Set `blockTimestampMs` for sub-second tests; the seconds API returns the truncated value. A later seconds-only state update discards sub-second precision.


[comment]: # (mx-context-auto)
Expand Down
8 changes: 4 additions & 4 deletions docs/developers/developer-reference/sc-api-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ A smart contract call that runs out of gas will revert all operations, so this f
### get_block_timestamp_seconds

```rust
get_block_timestamp_millis() -> TimestampSeconds
get_block_timestamp_seconds() -> TimestampSeconds
```

Returns the timestamp of the current block, in seconds (UNIX timestamp).
Expand All @@ -169,7 +169,7 @@ Returns the timestamp of the current block, in milliseconds (UNIX timestamp).
get_block_nonce() -> u64
```

Returns the unique nonce of the block that includes the current transaction.
Returns the nonce of the block that includes the current transaction, unique within its shard. Off-chain consumers combining shards must retain the shard identifier as well.

[comment]: # (mx-context-auto)

Expand All @@ -179,7 +179,7 @@ Returns the unique nonce of the block that includes the current transaction.
get_block_round() -> u64
```

Returns the round number of the current block. Each epoch consists of a fixed number of rounds. The round number resets to 1 at the start of every new epoch.
Returns the round number of the current block. Round numbering continues across epoch boundaries; it does not reset at the start of an epoch. A round may pass without producing a block.

[comment]: # (mx-context-auto)

Expand Down Expand Up @@ -267,7 +267,7 @@ get_prev_block_random_seed() -> ManagedByteArray<Self::Api, 48>
get_block_round_time_millis(&self) -> DurationMillis
```

The block round time, in milliseconds, i.e the time between consecutive blocks.
Returns the configured protocol round duration in milliseconds. This is not the measured time between consecutive blocks: rounds may pass without a block. Use timestamp differences to measure elapsed time.


[comment]: # (mx-context-auto)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ the same.
"@multiversx/sdk-dapp-utils": "3.1.0",
"axios": "1.18.1",
"bignumber.js": "9.3.1",
"next": "15.5.21",
"next": "15.5.24",
"protobufjs": "7.6.5",
"react": "18.3.1",
"react-dom": "18.3.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ npm run dev
"@multiversx/sdk-dapp-utils": "3.1.0",
"axios": "1.18.1",
"bignumber.js": "9.3.1",
"next": "15.5.21",
"next": "15.5.24",
"protobufjs": "7.6.5",
"react": "18.3.1",
"react-dom": "18.3.1"
Expand Down
Loading
Loading