Skip to main content

Smart Contract Best Practices

Everything that makes Ethereum contracts safe or unsafe also applies to OXN. This page lists the standard practices you should follow, then the OXN-specific additions.

Standard EVM security (applies unchanged)

  • Guard against reentrancy. Use ReentrancyGuard from OpenZeppelin, or the checks-effects-interactions pattern. External calls (including call{value: x}) can recurse back into your contract.
  • Validate inputs at every external boundary. Don't trust the caller.
  • Use SafeMath-equivalent behavior. Solidity 0.8+ has built-in overflow checks; do not disable them without a specific reason.
  • Follow checks-effects-interactions. Do all state changes before external calls.
  • Prefer pull over push for payments. Let recipients withdraw rather than pushing funds to them.
  • Cap loop iterations. Unbounded loops can hit gas limits and brick your contract.
  • Use battle-tested libraries. OpenZeppelin Contracts is the gold standard. Solmate is a leaner alternative.
  • Access control gates. Use AccessControl or explicit require(msg.sender == owner). Never rely on tx.origin.
  • Emit events for every state change. Auditors and monitoring rely on them.

OXN-specific additions

Do not assume storage is opaque

Encrypted storage hides values, not layout. A node operator dumping raw storage sees which slots are populated, how large mappings are, and when each slot was last written. Design your storage layout with that in mind. For extreme sensitivity, consider oblivious patterns (write to every slot even when only one changes).

Understand the encrypted event constraint

Event contents are only decryptable by the caller who emitted them. This changes some patterns:

  • Never rely on a global indexer decrypting your app's events.
  • If you need public event visibility (for wallets, explorers, indexers), emit a plain companion event without sensitive fields.
  • Never emit sensitive fields as indexed — even encrypted, index topics leak presence via Bloom filter matching.

Design against Gas-based side channels

If a function's Gas usage depends on the value of a secret (a branch based on encrypted state), the observed Gas leaks that value:

// BAD: gas usage differs by branch
function bad(uint256 secret) external {
if (secret > 100) {
// expensive path
} else {
// cheap path
}
}

Attackers observing your transaction can distinguish the two cases from Gas alone. Prefer branchless code or constant-time patterns for security-critical logic:

// BETTER: both branches execute
function ok(uint256 secret) external {
uint256 pathA = expensiveCompute();
uint256 pathB = otherExpensiveCompute();
result = secret > 100 ? pathA : pathB;
}

Use secure randomness

Never use block.timestamp, block.number, block.hash, or PREVRANDAO for random values. Use Sapphire.randomBytes — see Randomness.

Pin evmVersion: "paris"

Compiling with a newer EVM version emits opcodes OXN cannot execute. Make this a CI check, not just a config comment.

Explicit gasLimit in every script

Pin gasLimit explicitly on every transaction and make this a code-review criterion — client-side estimation is not the recommended path.

Store secrets private, not public

public storage generates a plain unauthenticated getter that defeats encryption:

// BAD: automatic getter is a plain view function
mapping(address => uint256) public balances;

// GOOD: expose through a gated view
mapping(address => uint256) private balances;

function balanceOf(address who) external view returns (uint256) {
require(msg.sender == who, "not authorized");
return balances[who];
}

Never expose private keys

If your contract holds a signing key (see Signatures), never:

  • Expose it through a getter.
  • Emit it in an event.
  • Pass it as a return value.
  • Return it from a view function.

The runtime keeps it encrypted, but a bug that hands it back to the client is worthless anyway.

Standard toolchain

  • Static analysis: Slither, Mythril. These do not understand OXN-specific patterns, but they catch generic Solidity bugs.
  • Formal verification: Certora, Halmos. Same caveat.
  • Fuzzing: Foundry's built-in fuzzer, Echidna. Runs against local EVM without confidentiality, but flushes out logic bugs.
  • Integration tests against real OXN: catches confidentiality-related issues that fuzzers cannot.

Next steps