neo-devpack-solidity 0.22.0

Production-focused Solidity-to-NeoVM compilation system
Documentation
---
title: "EIP-1153 — Transient Storage"
description: "EIP-1153 — Transient Storage mapped to Neo N3."
---

# EIP-1153 — Transient Storage

[Back to Protocol-Level EIPs](/standards-mirror/protocol-eips)

<StandardsMirror>

<StandardEntry
  id="eip-1153"
  title="EIP-1153 — Transient Storage"
  eip="1153"
  status="Final"
  neoMapping="Native (Storage with transaction-scoped lifetime)"
  category="Storage"
  parityLabel="Native"
  parityClass="sm-pill-native"
>

<template #spec>

## EIP-1153: Transient Storage Opcodes (TSTORE / TLOAD)

EIP-1153 (Cancun, 2024) introduced two new opcodes — `TSTORE` and `TLOAD` — that
read/write storage that lives only for the duration of a single transaction.
Persistent storage (`SSTORE`) costs 20,000 gas; transient storage costs 100 gas.

### Use Cases

- **Re-entrancy guards**: a `bool` flag set during entry, checked on re-entry,
  cleared at the end of the call. Without TSTORE this requires writing a regular
  storage slot at 20K gas.
- **Inter-call communication**: a contract that needs to pass non-trivial state
  through a callback no longer pays for permanent storage.
- **Cached computation**: expensive views computed once per transaction.

### Neo Equivalent

Neo's storage API supports the same pattern via per-transaction storage scopes.
A contract can use a small, manually-managed transient store: write a value, use
it, delete it before the transaction ends. The cost is similar to permanent
storage at write/delete, but the value is gone after the tx — no permanent state
bloat.

For the most common use case (re-entrancy guards), the standard Neo idiom is
to use a contract-local boolean stored in transient memory implemented through
a deliberate `Put` then `Delete` pattern, OR to use the `Runtime.GetTrigger() ==
TriggerType.Application` check combined with `Runtime.ExecutingScriptHash !=
Runtime.CallingScriptHash` to detect external entry. Neo VM's call semantics
prevent a re-entrancy hazard pattern much more strongly than EVM's, so guards
are needed less often.


::: tip Live on Neo TestNet
Both implementations are deployed on Neo N3 TestNet (network magic `894710606`).

| Implementation | Contract Hash | Deploy Tx |
| --- | --- | --- |
| **Solidity** (`neo-solc`) | `0x7e4e48124ed93c56eb4715965bb5b91fd0eb1a66` | (reused — see [`0x7e4e48124ed93c56eb4715965bb5b91fd0eb1a66`]https://dora.coz.io/contract/neo3/testnet/0x7e4e48124ed93c56eb4715965bb5b91fd0eb1a66) |
| **Neo C#** (`nccs`) | `0xe67e6815ad4d151bf87667af4e9aa9cbc3eaa7bf` | (reused — see [`0xe67e6815ad4d151bf87667af4e9aa9cbc3eaa7bf`]https://dora.coz.io/contract/neo3/testnet/0xe67e6815ad4d151bf87667af4e9aa9cbc3eaa7bf) |

Checked-in snapshot: Solidity 2 / 3 assertions pass; Neo C# 2 / 3 assertions pass. This is a validation snapshot, not an all-green parity certification; see [TestNet Results](/standards-mirror/deployments/RESULTS) for failure details.

Source pairs: [`docs/standards-mirror/deployments/eip-1153/`](https://github.com/r3e-network/neo-devpack-solidity/tree/main/docs/standards-mirror/deployments/eip-1153).
:::

</template>

<template #solidity>

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

abstract contract ReentrancyGuardTransient {
    bytes32 private constant LOCKED = keccak256("ReentrancyGuard.LOCKED");

    modifier nonReentrant() {
        require(_locked() == 0, "reentrant");
        _setLocked(1);
        _;
        _setLocked(0);
    }

    function _locked() private view returns (uint256 v) {
        bytes32 slot = LOCKED;
        assembly { v := tload(slot) }
    }

    function _setLocked(uint256 v) private {
        bytes32 slot = LOCKED;
        assembly { tstore(slot, v) }
    }
}

contract MyVault is ReentrancyGuardTransient {
    function withdraw(uint256 amount) external nonReentrant {
        // ... safe to do external calls here ...
    }
}
```

</template>

<template #csharp>

```csharp
using System;
using System.ComponentModel;
using System.Numerics;
using Neo;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Attributes;
using Neo.SmartContract.Framework.Native;
using Neo.SmartContract.Framework.Services;

namespace R3E.Examples;

/// <summary>
/// Neo re-entrancy guard. Two patterns:
///
/// (a) Storage-based: same as EIP-1153 conceptually — write a flag, check on
///     re-entry, delete at exit. Storage in Neo is only persistent if you don't
///     delete it; transient effect = put+delete in one tx.
///
/// (b) Caller-based: use Runtime.CallingScriptHash to detect direct vs reentrant
///     calls. Often sufficient because Neo's call model is more restrictive than
///     delegatecall-heavy EVM patterns.
/// </summary>
[DisplayName("ReentrancyGuarded")]
[ContractPermission("*", "*")]
public class ReentrancyGuarded : SmartContract
{
    private static readonly byte[] LockKey = { 0x70 };

    public static void Withdraw(BigInteger amount)
    {
        if (Storage.Get(Storage.CurrentContext, LockKey) != null)
            throw new Exception("reentrant");
        Storage.Put(Storage.CurrentContext, LockKey, 1);

        try
        {
            // ... external calls / asset transfers here ...
        }
        finally
        {
            Storage.Delete(Storage.CurrentContext, LockKey);
        }
    }
}
```

### Cost Comparison

| Op | EVM (post-EIP-1153) | Neo |
| --- | --- | --- |
| Set guard | TSTORE — 100 gas | Put — ~3.5 GAS at default fee factor |
| Check guard | TLOAD — 100 gas | Get — ~1 GAS |
| Clear guard | TSTORE 0 — 100 gas | Delete — ~1 GAS |

Neo's storage costs are higher in absolute terms but the gas-to-value-protected
ratio is similar — a guarded function call costs only marginally more than an
unguarded one in either system.

</template>

</StandardEntry>

</StandardsMirror>