Smart Contract Best Practices
Secure development patterns for Selendra contracts
Essential security practices for smart contract development.
Core Principles
- Fail Fast - Revert on errors
- Validate Inputs - Check all parameters
- Use Established Patterns - Don't reinvent security
- 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
- Reentrancy - External calls before state updates
- Integer Overflow - Use Solidity 0.8+
- Access Control - Insufficient permission checks
- Timestamp Dependence - Predictable timing
- Randomness - Using block hash for entropy
Resources
Tools: Slither • Mythril • Echidna
Audits: ConsenSys • Trail of Bits • OpenZeppelin
Contribute
Found an issue or want to contribute?
Help us improve this documentation by editing this page on GitHub.
