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.