Security Practices
Smart contract security
Security critical for smart contracts. Bugs expensive after deployment. Follow best practices.
Common Vulnerabilities
Reentrancy
Check-effects-interactions pattern:
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // Update state first
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
Or use ReentrancyGuard:
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract MyContract is ReentrancyGuard {
function withdraw(uint256 amount) external nonReentrant {
// Withdrawal logic
}
}
Access Control
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is Ownable {
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
}
tx.origin Phishing
Never use tx.origin for authorization:
require(msg.sender == owner); // CORRECT
// Never: require(tx.origin == owner);
Unchecked External Calls
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
using SafeERC20 for IERC20;
token.safeTransfer(to, amount); // Reverts on failure
Denial of Service
Avoid unbounded loops. Use pull pattern:
// GOOD - pull pattern
mapping(address => uint256) public rewards;
function claimReward() external {
uint256 reward = rewards[msg.sender];
rewards[msg.sender] = 0;
payable(msg.sender).transfer(reward);
}
Security Tools
Slither
pip3 install slither-analyzer
slither contracts/MyContract.sol
Finds common vulnerabilities automatically.
Mythril
pip3 install mythril
myth analyze contracts/MyContract.sol
Code Review Checklist
- ✓ Sensitive functions protected
- ✓ No tx.origin usage
- ✓ External call return values checked
- ✓ Reentrancy protected
- ✓ State updates before external calls
- ✓ No unbounded loops
- ✓ Zero addresses checked
- ✓ Bounds verified
- ✓ Important changes emit events
Emergency Controls
Pause Functionality
import "@openzeppelin/contracts/utils/Pausable.sol";
contract MyContract is Pausable, Ownable {
function emergencyPause() external onlyOwner {
_pause();
}
function transfer(address to, uint256 amount) external whenNotPaused {
// Transfer logic
}
}
Circuit Breakers
uint256 public maxDailyWithdraw = 1000000;
uint256 public dailyWithdrawn;
uint256 public lastResetTime;
function withdraw(uint256 amount) external {
if (block.timestamp > lastResetTime + 1 days) {
dailyWithdrawn = 0;
lastResetTime = block.timestamp;
}
require(dailyWithdrawn + amount <= maxDailyWithdraw, "Daily limit");
dailyWithdrawn += amount;
_withdraw(amount);
}
Best Practices
- Use proven libraries: OpenZeppelin audited extensively
- Keep it simple: Complexity increases risk
- Test everything: Complete test suite catches bugs
- Document assumptions: Help auditors understand code
Self-Audit Steps
- Automated tools: Run Slither and Mythril
- Manual review: Read code line by line
- Peer review: Fresh eyes catch issues
- Test coverage: Aim for 90%+
Same Risks as Ethereum
EVM compatible means same vulnerabilities. All Ethereum exploits possible. Low fees enable thorough testing—deploy multiple test versions affordably.
Next Steps
Contribute
Found an issue or want to contribute?
Help us improve this documentation by editing this page on GitHub.
