Selendra

Documentation

Common Vulnerabilities

Security issues to watch for and avoid

Common smart contract vulnerabilities and how to prevent them.

1. Reentrancy

Problem: External call before state update allows attacker to re-enter function.

// VULNERABLE
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool success, ) = msg.sender.call{value: amount}("");
    balances[msg.sender] -= amount; // Too late!
}

// FIXED - Checks-Effects-Interactions
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount; // Update first
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
}

// ALTERNATIVE - ReentrancyGuard
modifier nonReentrant() {
    require(!locked, "No reentrancy");
    locked = true;
    _;
    locked = false;
}

2. Integer Overflow

Problem: Arithmetic exceeds max/min values.

// Solidity 0.8+ has built-in protection
uint256 a = b + c; // Reverts on overflow

// For custom logic
require(balances[to] + amount >= balances[to], "Overflow");

3. Access Control

Problem: Missing permission checks.

// VULNERABLE
function mint(address to, uint256 amount) external {
    balances[to] += amount; // Anyone can mint
}

// FIXED
modifier onlyOwner() {
    require(owners[msg.sender], "Not owner");
    _;
}

function mint(address to, uint256 amount) external onlyOwner {
    balances[to] += amount;
}

4. Timestamp Dependence

Problem: Using block.timestamp for randomness or precise timing.

// VULNERABLE - Predictable
uint256 random = uint256(keccak256(block.timestamp));

// BETTER - Multiple entropy sources
uint256 random = uint256(keccak256(abi.encodePacked(
    block.timestamp, block.difficulty, block.gaslimit
)));

5. Unchecked Call Returns

Problem: External call failures go unnoticed.

// VULNERABLE
target.call(data); // May fail silently

// FIXED
(bool success, ) = target.call(data);
require(success, "Call failed");

6. Front-Running

Problem: Attackers see pending transactions and act first.

Prevention: Use commit-reveal schemes, accept higher bids, or add randomized delays.

7. Denial of Service

Problem: Unbounded operations exhaust gas.

// VULNERABLE
for (uint256 i = 0; i < data.length; i++) {
    _process(data[i]); // May run out of gas
}

// FIXED - Batched
function processBatch(uint256 start, uint256 end) external {
    require(end - start <= 50, "Batch too large");
    for (uint256 i = start; i < end; i++) {
        _process(data[i]);
    }
}

Testing

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

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

Resources

Next: Best PracticesAudit 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