Skip to main content

Sapphire vs Ethereum

Sapphire is generally compatible with Ethereum, the EVM, and all the user and developer tooling that you are used to. In addition to confidentiality features, you get a few extra benefits including the ability to generate private entropy, and make signatures on-chain. An example of such dApp that uses both is an HSM contract that generates an Ethereum wallet and signs transactions sent to it via transactions.

You need to consider the following breaking differences compared to Ethereum that originate mostly from the built-in confidentiality:

Encrypted Contract State

The contract state is only visible to the contract that wrote it. With respect to the contract API, it's as if all state variables are declared as private, but with the further restriction that not even full nodes can read the values. Public or access-controlled values are provided instead through explicit getters.

Calling the eth_getStorageAt() RPC will return zero for all storage slots, except for the following well-known EIP-1967 proxy-related slots, which remain readable to support compatibility with standard tooling:

  • 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc: Proxy implementation address
  • 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50: Beacon proxy implementation
  • 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103: Admin slot
danger

Do not use immutable nor constant for variables you want to keep private, since those would be stored in the runtime bytecode obtainable through eth_getCode() RPC, which is unencrypted on Sapphire.

End-to-End Encrypted Transactions and Calls

Transactions and calls are optionally end-to-end encrypted into the contract. Only the caller and the contract can see the data sent to/received from the ParaTime. This ends up defeating some utility of block explorers, however.

End-to-end encryption requires client-side support. Simply use a Sapphire client implementation for your language.

The status of the transaction is public and so are the error code, the revert message and logs (emitted events). Consult the Encrypted Events chapter to learn about encrypting emitted events.

eth_call Sender is Zero for Unsigned Calls

When invoking the eth_call() RPC the initial msg.sender address is derived from an EIP-712 signature attached to the call. Unsigned calls have msg.sender set to zero.

This approach enables contract authors to write getters that e.g. release secrets to authenticated callers simply by checking msg.sender. Consult to View-Call Authentication for details and alternatives.

eth_estimateGas Sender is Zero and No Errors Reported

When invoking the eth_estimateGas RPC the initial msg.sender address is always set to zero regardless of the signature or the provided from parameter.

Also, eth_estimateGas returns sucessful execution even if the call was reverted. In this case, the amount of spent gas is reported up to the point where the revert was triggered. This behavior is deliberate since the zero address would always fail for not having enough funds to cover transaction fees. If you need to validate the transaction and catch any errors, use eth_call instead.

Solidity Compiler Version

Sapphire currently supports smart contracts compiled with solc version up to 0.8.24. We suggest you pin the version by putting

pragma solidity 0.8.24;

in the beginning of your solidity sources.

Override receive and fallback when Funding the Contract

In Ethereum, you can fund a contract by sending Ether along the transaction in two ways:

  1. a transaction must call a payable function in the contract, or
  2. not calling any specific function (i.e. empty calldata). In this case, the payable receive() and/or fallback() functions need to be defined in the contract. If no such functions exist, the transaction will revert.

The behavior described above is the same in Sapphire when using EVM transactions to fund a contract.

However, the Oasis Network also uses Oasis-native transactions such as a deposit to a ParaTime account or a transfer. In this case, you will be able to fund the contract's account even though the contract may not implement payable receive() or fallback()! Or, if these functions do exist, they will not be triggered by the native Oasis transactions. You need to design the smart contract in a way that contract funds are always transferrable out of the contract. You can submit Oasis-native transactions with the Oasis CLI.

Instant Finality

The Oasis Network is a proof of stake network where 2/3+ of the validator nodes need to verify each block in order to consider it final. However, in Ethereum the signatures of those validator nodes can be submitted minutes after the block is proposed, which makes the block proposal mechanism independent of the validation, but adds uncertainty if and when will the proposed block actually be finalized.

In the Oasis Network, the 2/3+ of signatures need to be provided immediately after the block is proposed and the network will halt, until the required number signatures are provided. This means that you can rest assured that any validated block is final. As a consequence, the cross-chain bridges are more responsive yet safe on the Oasis Network.

See also

Concepts

Sapphire concepts