Soroban Solidity Language Compatibility

This page documents the Solidity-facing compatibility expectations for the Soroban target. It focuses on source-level language behavior: how Solidity constructs map to Soroban, where behavior differs from normal EVM expectations, and which Soroban-specific constructs developers should expect to use.

This page is about language compatibility, not storage-layout differences. For Solang-specific storage representation and differences from common Rust SDK patterns, see Solang and Soroban Rust SDK Differences.

Source-Level Compatibility

Solang aims for source-level Solidity familiarity on Soroban, not EVM equivalence.

In practice, that means:

  • familiar Solidity syntax should compile where the Soroban backend supports it

  • Solang may warn on Soroban-specific constraints rather than silently changing behavior

  • when Soroban cannot support a construct safely, Solang should reject it rather than compile incorrect semantics

The current supported and unsupported feature set is tracked on Soroban Support Matrix.

Authorization Model

The clearest Solidity-language difference on Soroban is authorization.

On EVM chains, access control is often written in terms of msg.sender. Soroban does not expose authorization through msg.sender in the same way. Instead, authorization is performed by the Soroban host for a specific address.

An EVM-style ownership check often looks like this:

pragma solidity ^0.8.20;

contract OnlyOwner {
    address owner;

    constructor() {
        owner = msg.sender;
    }

    function set(uint256 value) public {
        require(msg.sender == owner, "Only owner can call this function");
    }
}

On Soroban, the same intent should be written using requireAuth() on the address that must authorize the call:

contract auth {
    address public owner;
    uint64 public counter;

    constructor(address _owner) public {
        owner = _owner;
    }

    function increment() public returns (uint64) {
        owner.requireAuth();
        counter = counter + 1;
        return counter;
    }
}

The requireAuth() builtin calls into the Soroban host, which verifies that the address authorized the invocation. This is the Soroban-native pattern developers should expect to use instead of direct msg.sender comparisons.

For deeper authorization chains that pass through another contract, Solang also supports auth.authAsCurrContract(...). See:

Storage Classes and Lifetime

Soroban has three storage types: Persistent, Temporary and Instance. Solang exposes these directly in the language with Soroban-only storage class keywords:

  • persistent

  • temporary

  • instance

For example:

contract storage_types {
    uint64 public temporary var = 1;
    uint64 public instance var1 = 1;
    uint64 public persistent var2 = 2;
    uint64 public var3 = 2;

    function inc() public {
        var++;
        var1++;
        var2++;
        var3++;
    }
}

If no storage class is written, the variable defaults to persistent storage.

This is a Soroban-specific language extension rather than standard Solidity behavior. Developers should treat it as part of the Soroban target surface, not portable Solidity syntax.

Related Soroban-only lifetime helpers are available through builtins such as extendTtl(...) and extendInstanceTtl(...). See Builtin Functions and Variables and:

Types, Structs, Arrays, and Mappings

The Soroban backend supports a growing subset of Solidity types and compound constructs, including the tested use of:

  • primitive types such as bool, address, string, uint32, uint64, int32, int64, int128, uint128, uint256, and int256

  • structs in storage

  • mappings and nested mappings

  • memory arrays and storage arrays

These are Solidity-facing language features, but some of their behavior is constrained by Soroban’s host representation and storage model. For the storage and representation side of those features, see Solang and Soroban Rust SDK Differences.

Current documented support is summarized on Soroban Support Matrix.

Events

Solidity event declarations and emit statements are supported on Soroban. The Soroban host records contract events through the contract_event host function, which takes a vector of topics and a data value.

Solang maps Solidity event fields to Soroban event components as follows:

  • indexed fields become entries in the Soroban topics vector

  • non-indexed fields become the event data value

For example:

contract EventEmitter {
    event Transfer(address indexed from, address indexed to, uint64 value);

    function transfer(address from, address to, uint64 amount) public {
        from.requireAuth();
        emit Transfer(from, to, amount);
    }
}

In the example above, from and to are indexed and appear as topics in the recorded Soroban event. value is non-indexed and becomes the event data. Note that Soroban imposes a limit of four topics per event, and topics cannot contain Vec, Map, or Bytes values longer than 32 bytes.

See tests/soroban_testcases/events.rs for tested examples.

Integer Widths

Soroban only natively supports integer widths 32, 64, 128, and 256. When a contract uses another integer width, Solang rounds the width up to the next supported Soroban size:

  • 1..=32 becomes 32

  • 33..=64 becomes 64

  • 65..=128 becomes 128

  • 129..=256 becomes 256

By default, this produces a warning. If you compile with --strict-soroban-types, the same cases become compilation errors instead.

This is another case where Solang preserves Solidity source compatibility where practical, but makes the Soroban-specific constraint visible rather than silently pretending arbitrary integer widths are native to the runtime.

Current Expectation

When writing Solidity for Soroban, developers should expect:

  • Solidity-like syntax where supported

  • explicit Soroban-specific language differences where the runtime requires them

  • warnings or rejections instead of silent mismatches

For the current support matrix, see Soroban Support Matrix.