Selendra

Documentation

Smart Contract Best Practices

Secure development patterns for Selendra contracts

Essential security practices for smart contract development.

Core Principles

  1. Fail Fast - Revert on errors
  2. Validate Inputs - Check all parameters
  3. Use Established Patterns - Don't reinvent security
  4. Test Thoroughly - Cover all edge cases

Input Validation

function transfer(address to, uint256 amount) external {
    require(to != address(0), "Invalid recipient");
    require(amount > 0, "Amount must be positive");
    require(amount <= balanceOf(msg.sender), "Insufficient balance");
    _transfer(msg.sender, to, amount);
}

Access Control

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MyContract is Ownable, ReentrancyGuard {
    modifier onlyAdmin() {
        require(admins[msg.sender], "Admin only");
        _;
    }

    function withdraw(uint256 amount) external onlyOwner nonReentrant {
        payable(owner()).transfer(amount);
    }
}

Reentrancy Protection

Checks-Effects-Interactions Pattern:

function withdraw(uint256 amount) external {
    // 1. Checks
    require(balanceOf[msg.sender] >= amount);
    // 2. Effects
    _burn(msg.sender, amount);
    // 3. Interactions
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
}

Event Logging

event Transfer(address indexed from, address indexed to, uint256 value);

function transfer(address to, uint256 amount) external {
    _transfer(msg.sender, to, amount);
    emit Transfer(msg.sender, to, amount);
}

Gas Optimization

contract Optimized {
    uint256 public constant MAX_BATCH = 100;

    // Packed struct (32 bytes total)
    struct User {
        uint128 balance;
        uint64 timestamp;
        uint32 level;
        bool verified;
        uint8 role;
    }

    // Immutable saves gas
    uint256 public immutable TOTAL_SUPPLY;

    function processBatch(uint256[] calldata items) external {
        require(items.length <= MAX_BATCH, "Batch too large");
        for (uint256 i = 0; i < items.length; i++) {
            _process(items[i]);
        }
    }
}

Security Testing

describe("Security", function () {
    it("Should prevent reentrancy", async function () {
        await attacker.attack();
        expect(await contract.balance()).to.equal(initialBalance);
    });

    it("Should validate inputs", async function () {
        await expect(contract.transfer(ethers.ZeroAddress, 100))
            .to.be.revertedWith("Invalid recipient");
    });

    it("Should enforce access control", async function () {
        await expect(contract.connect(user).adminFunction())
            .to.be.revertedWith("Admin only");
    });
});

Pre-Audit Checklist

  • All external inputs validated
  • Reentrancy protection implemented
  • Access control properly implemented
  • Events emitted for state changes
  • Unit tests cover all functions
  • Attack scenarios tested
  • Code reviewed by security expert

Common Vulnerabilities

  1. Reentrancy - External calls before state updates
  2. Integer Overflow - Use Solidity 0.8+
  3. Access Control - Insufficient permission checks
  4. Timestamp Dependence - Predictable timing
  5. Randomness - Using block hash for entropy

Resources

Tools: SlitherMythrilEchidna

Audits: ConsenSysTrail of BitsOpenZeppelin

Next: Common VulnerabilitiesAudit Checklist

Contribute

Found an issue or want to contribute?

Help us improve this documentation by editing this page on GitHub.

Edit this page on GitHub