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
- End-to-End Encrypted Transactions and Calls
eth_callSender is Zero for Unsigned Callseth_estimateGasSender is Zero and No Errors Reported- Solidity Compiler Version
- Override
receiveandfallbackwhen Funding the Contract - Instant Finality
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 address0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50: Beacon proxy implementation0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103: Admin slot
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:
- a transaction must call a payable function in the contract, or
- not calling any specific function (i.e. empty calldata). In this case,
the payable
receive()and/orfallback()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