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
178 changes: 178 additions & 0 deletions docs/developers/best-practices/prepare-sc-supernova.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
---
id: prepare-sc-supernova
title: Preparing SCs for Supernova
---


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.

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



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

## 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.

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.

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



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

## Potential problems to look out for

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

### 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.

It might estimate durations from nonce deltas, or vice-versa, it might estimate number of blocks by timestamps.

**Fix:** Use the API instead: `self.blockchain().get_block_round_time_millis()`. This returns `6000` (as `DurationMillis`) today and `600` after Supernova.



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

### Avoid Timestamp-Based Monotonicity

Logic such as:

```
require!(ts_now > last_ts)
```

may break because multiple blocks can share the same timestamp.

**Fix:** Use **block nonces** for guaranteed monotonicity.



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

### Prevent Rate-Limit Bypasses

If your contract allows one action “per block” but checks the difference in **seconds**, multiple blocks in the same second can bypass restrictions.

**Fix:** Use block nonces or switch to millisecond timestamps.



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

### Revisit Expiration Logic

Expiration logic written assuming a fixed block interval may accidentally allow:

* extra blocks before expiration
* longer-than-intended windows for execution

If your expiration logic uses seconds, double-check assumptions.



[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.

If you use timestamps as map keys or identifiers:

* collisions will occur
* data may be overwritten or skipped

**Fix:** Use block **nonces**.



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

### Review Reward and Accumulation Calculations

Reward logic that uses:

```
delta = ts_now - last_ts
```

may behave unexpectedly:

* delta may be zero over multiple blocks
* rewards might accumulate slower or unevenly
* divisions by delta may cause division-by-zero errors

Consider switching to milliseconds for finer granularity.



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

## Migration Guidance

Just as important as fixing potential issues with Supernova, it is essential not to introduce new bugs in the process.

Make sure to take the following into consideration when migrating:



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

### Seconds vs. Milliseconds

Switching from second to millisecond timestamp can be error-prone.

The most dangerous bug is accidentally mixing second and millisecond values, causing storage and logic corruption.

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:

* `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()`

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.



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

### Backwards compatibility

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

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



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

## Summary Checklist

To prepare for Supernova:

* [ ] Upgrade to `multiversx-sc v0.63.0`
* [ ] Use typed timestamp/duration APIs
* [ ] Remove all hardcoded 6-second or 6000-millisecond assumptions
* [ ] 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
121 changes: 121 additions & 0 deletions docs/developers/best-practices/time-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
id: time-types
title: Time-related Types
---


The Supernova release introduces increased block frequency and encourages transitioning to millisecond timestamps, instead of seconds. To support this, the SpaceCraft SDK (starting with `v0.63.0`) provides strong type wrappers for time values to prevent common bugs.


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

## Overview

Traditionally, smart contracts used plain `u64` values to represent timestamps and durations. As the ecosystem transitions to millisecond precision, mixing seconds and milliseconds becomes error-prone. Runtime errors are difficult to detect, so the compiler now assists by enforcing type correctness.


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

## The Four Time-Related Types

The framework introduces four new types:

* **`TimestampSeconds`** — moment in time measured in seconds
* **`TimestampMillis`** — moment in time measured in milliseconds
* **`DurationSeconds`** — duration measured in seconds
* **`DurationMillis`** — duration measured in milliseconds

Each is a **newtype wrapper around `u64`**, with an identical underlying representation but distinct meaning.


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

## Why These Types Exist

Using plain `u64` makes it easy to:

* accidentally mix seconds and milliseconds
* add timestamps together (nonsensical)
* subtract mismatched units
* perform invalid arithmetic without realizing it

The new types make such mistakes **fail at compile time**.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better emphasize this sentence using :::info

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't use info boxes anywhere, feels a bit off



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

## Supported Operations

The framework implements common operators only where they make sense.

Examples:

```
TimestampMillis - TimestampMillis → DurationMillis
TimestampSeconds + DurationSeconds → TimestampSeconds
DurationSeconds + DurationSeconds → DurationSeconds
```

Examples of invalid operations (will not compile):

```
TimestampMillis - TimestampSeconds
TimestampMillis + TimestampMillis
```


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

## Codec and ABI Support

All four types:

* support codec and ABI traits
* can be used in storage
* can be used in events and arguments
* behave like `u64` for serialization

This makes them safe to adopt even in existing contracts.


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

## When to Use Seconds vs. Milliseconds

* **Seconds**: acceptable when your contract already stores timestamps in seconds or relies on existing storage/metadata
* **Milliseconds**: recommended for all new contracts and for future-proof logic

The key rule: **Never mix seconds and milliseconds without explicit conversion.**


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

## Testing

Time-related types also work in testing frameworks.

In blackbox tests, one can write:

```rust
let block_timestamp_ms = TimestampMillis::new(123_000_000);

world
.epoch_start_block()
.block_timestamp_ms(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.


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

## Summary

* Time-related newtypes enforce correctness at compile time
* They eliminate a class of subtle bugs related to timestamp unit mismatches
* Both seconds and milliseconds will continue to work, but milliseconds are recommended for new code

Use these types to ensure your contract remains safe and consistent across all future protocol updates.
45 changes: 40 additions & 5 deletions docs/developers/developer-reference/sc-api-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,26 @@ A smart contract call that runs out of gas will revert all operations, so this f

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

### get_block_timestamp
### get_block_timestamp_seconds

```rust
get_block_timestamp() -> u64
get_block_timestamp_millis() -> TimestampSeconds
```

Returns the timestamp of the current block, in seconds (UNIX timestamp).


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

### get_block_timestamp_millis

```rust
get_block_timestamp_millis() -> TimestampMillis
```

Returns the timestamp of the current block, in milliseconds (UNIX timestamp).


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

### get_block_nonce
Expand Down Expand Up @@ -194,12 +206,26 @@ This will be the same for all the calls in the current block, so it can be predi

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

### get_prev_block_timestamp
### get_prev_block_timestamp_seconds

```rust
get_prev_block_timestamp_seconds() -> TimestampSeconds
```

Returns the timestamp of the previous block, in seconds (UNIX timestamp).


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

### get_prev_block_timestamp_millis

```rust
get_prev_block_timestamp() -> u64
get_prev_block_timestamp_millis() -> TimestampMillis
```

Returns the timestamp of the previous block, in milliseconds (UNIX timestamp).


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

### get_prev_block_nonce
Expand Down Expand Up @@ -232,7 +258,16 @@ get_prev_block_epoch() -> u64
get_prev_block_random_seed() -> ManagedByteArray<Self::Api, 48>
```

The same as the functions above, but for the previous block instead of the current block.
[comment]: # (mx-context-auto)

### get_block_round_time_millis

```rust
get_block_round_time_millis(&self) -> DurationMillis
```

The block round time, in milliseconds, i.e the time between consecutive blocks.


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

Expand Down
4 changes: 3 additions & 1 deletion sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,14 @@ const sidebars = {
},
{
type: "category",
label: "Rust Developers Best Practices",
label: "Best Practices",
items: [
"developers/best-practices/best-practices-basics",
"developers/best-practices/biguint-operations",
"developers/best-practices/managed-decimal",
"developers/best-practices/the-dynamic-allocation-problem",
"developers/best-practices/time-types",
"developers/best-practices/prepare-sc-supernova",
],
},
{
Expand Down
Loading