Best practives

Best Practices

Essential security guidelines for building robust smart contracts

Smart contract security isn't just about avoiding vulnerabilities — it's about adopting a holistic approach to secure development. These best practices, learned from years of audits and real-world incidents, will help you build safer blockchain applications.

Remember: security is a journey, not a destination. Stay informed, keep learning, and always prioritize the safety of user funds.

Secure Coding Patterns

Follow Checks-Effects-Interactions Pattern

Critical

Always perform checks first, then update state, and finally interact with external contracts. This prevents reentrancy attacks.

Example

// Good pattern
function withdraw() public {
    // 1. Checks
    uint256 amount = balances[msg.sender];
    require(amount > 0, "No balance");

    // 2. Effects
    balances[msg.sender] = 0;

    // 3. Interactions
    (bool success,) = msg.sender.call{value: amount}("");
    require(success);
}

Use Proven Libraries

High

Leverage battle-tested libraries like OpenZeppelin instead of implementing security-critical functionality from scratch.

Example

// Use OpenZeppelin
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20, Ownable, ReentrancyGuard {
    // Secure foundation built on proven code
}

Validate All Inputs

High

Never trust user input. Always validate parameters, check bounds, and sanitize data before processing.

Example

function transfer(address to, uint256 amount) public {
    require(to != address(0), "Invalid address");
    require(to != address(this), "Cannot send to contract");
    require(amount > 0, "Amount must be positive");
    require(amount <= balances[msg.sender], "Insufficient balance");

    // Proceed with transfer
}

Testing & Auditing

Write Comprehensive Tests

Critical

Achieve high test coverage with unit, integration, and end-to-end tests. Test both happy paths and edge cases.

Example

// Example test structure
describe("Token Contract", () => {
    // Unit tests
    it("should transfer tokens correctly");
    it("should reject transfers to zero address");
    it("should prevent transfers exceeding balance");

    // Edge cases
    it("should handle maximum uint256 values");
    it("should work with zero amounts");

    // Attack scenarios
    it("should prevent reentrancy attacks");
    it("should block unauthorized access");
});

Get Professional Audits

Critical

Before mainnet deployment, have your contracts audited by reputable security firms. Multiple audits are recommended for high-value contracts.

Example

// Audit checklist:
✓ Static analysis with multiple tools
✓ Manual code review by experts
✓ Formal verification (for critical components)
✓ Economic attack modeling
✓ Gas optimization review
✓ Compliance verification

Test on Testnets Extensively

High

Deploy to testnets (Sepolia, Goerli) and run extensive tests in a production-like environment before mainnet deployment.

Example

// Testnet deployment workflow
1. Deploy to local network (Hardhat/Ganache)
2. Deploy to public testnet
3. Run integration tests
4. Perform stress testing
5. Bug bounty on testnet
6. Monitor for 2-4 weeks
7. Deploy to mainnet

Code Quality & Documentation

Write Clear Documentation

Medium

Document all functions, state variables, and complex logic. Use NatSpec comments for automatic documentation generation.

Example

/// @title A secure token contract
/// @author Your Name
/// @notice This contract implements ERC20 with additional security
/// @dev Uses OpenZeppelin base contracts
contract SecureToken {
    /// @notice Transfer tokens to a recipient
    /// @dev Implements checks-effects-interactions pattern
    /// @param to The recipient address
    /// @param amount The amount to transfer
    /// @return success True if transfer succeeded
    function transfer(address to, uint256 amount)
        public
        returns (bool success)
    {
        // Implementation
    }
}

Keep Functions Small and Focused

Medium

Break complex logic into smaller, testable functions. Each function should do one thing well.

Example

// Bad: Complex function doing multiple things
function processPayment(address user, uint amount) public { ... }

// Good: Separated concerns
function validatePayment(address user, uint amount) internal view { ... }
function updateBalance(address user, uint amount) internal { ... }
function emitPaymentEvent(address user, uint amount) internal { ... }
function processPayment(address user, uint amount) public {
    validatePayment(user, amount);
    updateBalance(user, amount);
    emitPaymentEvent(user, amount);
}

Use Explicit Variable and Function Names

Low

Choose descriptive names that clearly indicate purpose. Avoid abbreviations and single-letter variables.

Example

// Bad
function p(address u, uint a) public { ... }
uint x;

// Good
function processUserPayment(address userAddress, uint paymentAmount) public { ... }
uint totalPendingWithdrawals;

Access Control & Permissions

Implement Role-Based Access Control

Critical

Use OpenZeppelin's AccessControl for complex permission systems. Clearly define roles and their capabilities.

Example

import "@openzeppelin/contracts/access/AccessControl.sol";

contract SecureVault is AccessControl {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ADMIN_ROLE, msg.sender);
    }

    function emergencyPause()
        public
        onlyRole(ADMIN_ROLE)
    {
        // Only admins can pause
    }
}

Use Multi-Signature for Critical Operations

High

Require multiple authorized parties to approve critical operations like fund withdrawals or parameter changes.

Example

// Use Gnosis Safe or similar
// Critical operations should require:
// - Multiple signatures (e.g., 3 of 5)
// - Timelock delays for transparency
// - Emergency pause mechanisms

Implement Emergency Stop Mechanisms

High

Add circuit breakers that allow pausing critical functionality if vulnerabilities are discovered.

Example

import "@openzeppelin/contracts/security/Pausable.sol";

contract SecureContract is Pausable, Ownable {
    function criticalOperation()
        public
        whenNotPaused
    {
        // Operation only works when not paused
    }

    function emergencyPause()
        public
        onlyOwner
    {
        _pause();
    }
}

Monitoring & Maintenance

Monitor Contract Activity

High

Set up monitoring for unusual activity, failed transactions, and security events. Use tools like Forta or Tenderly.

Example

// Monitor for:
- Unusual transaction patterns
- Failed transactions
- Large transfers
- Privileged function calls
- Gas price anomalies
- Smart contract events

Plan for Upgradability (Carefully)

High

If you need upgradeable contracts, use proven patterns like OpenZeppelin's proxy contracts. Document upgrade procedures thoroughly.

Example

// Use OpenZeppelin upgradeable contracts
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract MyContractV1 is Initializable {
    function initialize() public initializer {
        // Initialization logic
    }
}

// Important: Test upgrades extensively
// Use timelock for upgrade governance
// Communicate changes to users

Maintain Bug Bounty Programs

Medium

Incentivize security researchers to find vulnerabilities before malicious actors do. Use platforms like Immunefi.

Example

// Bug bounty structure example:
Critical: $50,000 - $100,000
High: $10,000 - $50,000
Medium: $5,000 - $10,000
Low: $1,000 - $5,000

// Scope: All deployed contracts
// Rules: Responsible disclosure required

Pre-Deployment Security Checklist

Use this checklist before deploying any smart contract to mainnet:

Code Review

Testing & Auditing

Documentation

Deployment

Continue Learning