Best Practices and Anti-Patterns
A cheat sheet for confidential contract development. Skim this before you ship anything sensitive.
Do
Assume storage layout is public. Slot addresses are not encrypted — only the values are. If your slot access pattern reveals structural information, that leaks. Consider padding, dummy writes, or oblivious data structures for high-sensitivity contracts.
Understand who can decrypt event logs. Event contents are encrypted with the caller's session key. A global indexer cannot decrypt them across users. Design your event strategy up front — see Confidentiality Model: event logs gotcha.
Pin evmVersion: "paris" in your Hardhat / Foundry / solc config. Newer EVM versions include PUSH0, which OXN does not currently support. See EVM Compatibility.
Verify tx.data is encrypted in integration tests. Assert that tx.data starts with a CBOR envelope byte after every important transaction. This catches the "forgot to wrap the signer" bug before it hits production. See Encrypted vs Plain Calls for the check.
Test against a real OXN network, not a local Hardhat. Local EVM emulators do not model TEE behavior, encrypted calldata, or signed queries. Any test that "works" against hardhat network proves nothing about your production behavior.
Use private storage and explicit view functions. Avoid public on storage that should be confidential — it generates a plain unauthenticated getter that defeats the encryption. Expose data through explicit view functions gated by msg.sender.
Set gas limits explicitly. Set gasLimit explicitly on every OXN transaction — client-side estimation is not the recommended path. Recommended defaults: 500k for simple state changes, 3-5M for deployments.
Don't
Don't emit sensitive data in events if you need a global indexer. Events are only decryptable by the caller. If you must have public-facing event visibility, emit a plain-data companion event without sensitive fields, or use a separate public relay contract.
Don't use block.timestamp or block.number for randomness. They are public, predictable, and manipulable by validators. Use the Sapphire.randomBytes precompile instead. It draws from a TEE-attested entropy source.
Don't assume gas costs are confidential. Gas is metered on public hardware. If your contract's execution cost depends on the value of a secret (branching on encrypted state), the gas used leaks that value. Rewrite as constant-time when possible; see Confidentiality Pitfalls.
Don't skip access control because "the state is encrypted anyway". Encryption hides values from outsiders. It does not stop authorized callers from reading everything. If your contract has admins, users, and third parties, each needs a distinct authorization path. See Access Control Patterns.
Don't rely on view functions for authorization. A view function does not consume gas and does not persist state. require(msg.sender == owner) inside a view function is fine for gating what the caller sees, but do not use it as the sole enforcement of a state-changing operation.
Don't trust that "confidential" replaces "audited". Every smart contract security bug on Ethereum also exists on OXN — reentrancy, integer overflow, unchecked calls, oracle manipulation. Confidentiality is a data-privacy feature, not a code-safety feature. Ship audited code.
Don't emit events with indexed on sensitive fields. Indexed event topics are hashed into the Bloom filter that lives in every block header. Even though the topic is encrypted, its presence is inferable via eth_getLogs filtering patterns.
Situational: when to use plain calls
Not everything has to be encrypted. Legitimate plain-call scenarios:
- Reading public constants. Contract configuration, admin addresses, deployed proxy targets.
- Cross-contract calls where the target does not store secrets. Calls to a public oracle, a plain relay, or an ERC-20 that intentionally exposes balances.
- Debugging. Sometimes you need to inspect the exact bytes on-chain, which requires them to be plain.
- Interoperability with tools that do not know OXN. Some third-party tools cannot construct encrypted envelopes. Plain calls to public interfaces still work.
If plainness is intentional, comment it in your code. Otherwise it looks like a bug.
Situational: when NOT to use OXN
OXN is not always the right tool.
- Your data is inherently public. Public liquidity pools, oracle price feeds, public NFT marketplaces. Use Ethereum or an L2.
- You need to publish state transitions to a bigger chain. That is a rollup, not OXN.
- Your threat model is nation-state adversaries with hardware attack budgets. Add ZK proofs or off-chain enclaves you control.
- Your contract has no secrets, only expensive computation. Then you want scaling, not privacy.
Next steps
- Confidentiality Pitfalls — side channels and data-dependent leaks in detail
- Smart Contract Best Practices — general Solidity security
- Building a Confidential ERC-20 — a complete example applying these practices