Install
$ agentstack add skill-clawdbotatg-clawdviction-solidity-security ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
About
Solidity Security
Master smart contract security best practices, vulnerability prevention, and secure Solidity development patterns.
When to Use This Skill
- Writing secure smart contracts
- Auditing existing contracts for vulnerabilities
- Implementing secure DeFi protocols
- Preventing reentrancy, overflow, and access control issues
- Optimizing gas usage while maintaining security
- Preparing contracts for professional audits
- Understanding common attack vectors
Critical Vulnerabilities
1. Reentrancy
Attacker calls back into your contract before state is updated.
Vulnerable Code:
// VULNERABLE TO REENTRANCY
contract VulnerableBank {
mapping(address => uint256) public balances;
function withdraw() public {
uint256 amount = balances[msg.sender];
// DANGER: External call before state update
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] = 0; // Too late!
}
}
Secure Pattern (Checks-Effects-Interactions):
contract SecureBank {
mapping(address => uint256) public balances;
function withdraw() public {
uint256 amount = balances[msg.sender];
require(amount > 0, "Insufficient balance");
// EFFECTS: Update state BEFORE external call
balances[msg.sender] = 0;
// INTERACTIONS: External call last
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Alternative: ReentrancyGuard
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureBank is ReentrancyGuard {
mapping(address => uint256) public balances;
function withdraw() public nonReentrant {
uint256 amount = balances[msg.sender];
require(amount > 0, "Insufficient balance");
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
2. Integer Overflow/Underflow
**Vulnerable Code (Solidity uint256) public balances;
function transfer(address to, uint256 amount) public { // No overflow check - can wrap around balances[msg.sender] -= amount; // Can underflow! balances[to] += amount; // Can overflow! } }
**Secure Pattern (Solidity >= 0.8.0):**
```solidity
// Solidity 0.8+ has built-in overflow/underflow checks
contract SecureToken {
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public {
// Automatically reverts on overflow/underflow
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
**For Solidity uint256) public balances;
function transfer(address to, uint256 amount) public { balances[msg.sender] = balances[msg.sender].sub(amount); balances[to] = balances[to].add(amount); } }
### 3. Access Control
**Vulnerable Code:**
```solidity
// VULNERABLE: Anyone can call critical functions
contract VulnerableContract {
address public owner;
function withdraw(uint256 amount) public {
// No access control!
payable(msg.sender).transfer(amount);
}
}
Secure Pattern:
import "@openzeppelin/contracts/access/Ownable.sol";
contract SecureContract is Ownable {
function withdraw(uint256 amount) public onlyOwner {
payable(owner()).transfer(amount);
}
}
// Or implement custom role-based access
contract RoleBasedContract {
mapping(address => bool) public admins;
modifier onlyAdmin() {
require(admins[msg.sender], "Not an admin");
_;
}
function criticalFunction() public onlyAdmin {
// Protected function
}
}
4. Front-Running
Vulnerable:
// VULNERABLE TO FRONT-RUNNING
contract VulnerableDEX {
function swap(uint256 amount, uint256 minOutput) public {
// Attacker sees this in mempool and front-runs
uint256 output = calculateOutput(amount);
require(output >= minOutput, "Slippage too high");
// Perform swap
}
}
Mitigation:
contract SecureDEX {
mapping(bytes32 => bool) public usedCommitments;
// Step 1: Commit to trade
function commitTrade(bytes32 commitment) public {
usedCommitments[commitment] = true;
}
// Step 2: Reveal trade (next block)
function revealTrade(
uint256 amount,
uint256 minOutput,
bytes32 secret
) public {
bytes32 commitment = keccak256(abi.encodePacked(
msg.sender, amount, minOutput, secret
));
require(usedCommitments[commitment], "Invalid commitment");
// Perform swap
}
}
Security Best Practices
Checks-Effects-Interactions Pattern
contract SecurePattern {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public {
// 1. CHECKS: Validate conditions
require(amount 0, "Amount must be positive");
// 2. EFFECTS: Update state
balances[msg.sender] -= amount;
// 3. INTERACTIONS: External calls last
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Pull Over Push Pattern
// Prefer this (pull)
contract SecurePayment {
mapping(address => uint256) public pendingWithdrawals;
function recordPayment(address recipient, uint256 amount) internal {
pendingWithdrawals[recipient] += amount;
}
function withdraw() public {
uint256 amount = pendingWithdrawals[msg.sender];
require(amount > 0, "Nothing to withdraw");
pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
}
// Over this (push)
contract RiskyPayment {
function distributePayments(address[] memory recipients, uint256[] memory amounts) public {
for (uint i = 0; i 0, "Amount must be positive");
require(amount {
const [deployer] = await ethers.getSigners();
const VictimBank = await ethers.getContractFactory("SecureBank");
bank = (await VictimBank.deploy()) as SecureBank;
await bank.waitForDeployment();
const Attacker = await ethers.getContractFactory("ReentrancyAttacker");
attackerContract = (await Attacker.deploy(await bank.getAddress())) as ReentrancyAttacker;
await attackerContract.waitForDeployment();
});
it("Should prevent reentrancy attack", async function () {
// Deposit funds
await bank.deposit({ value: ethers.parseEther("10") });
// Attempt reentrancy attack
await expect(
attackerContract.attack({ value: ethers.parseEther("1") }),
).to.be.revertedWith("ReentrancyGuard: reentrant call");
});
});
describe("Integer Overflow Protection", function () {
let token: SecureToken;
before(async () => {
const Token = await ethers.getContractFactory("SecureToken");
token = (await Token.deploy()) as SecureToken;
await token.waitForDeployment();
});
it("Should prevent integer overflow", async function () {
const [, attacker] = await ethers.getSigners();
// Attempt overflow
await expect(token.transfer(attacker.address, ethers.MaxUint256))
.to.be.reverted;
});
});
describe("Access Control", function () {
let contract: SecureContract;
before(async () => {
const Contract = await ethers.getContractFactory("SecureContract");
contract = (await Contract.deploy()) as SecureContract;
await contract.waitForDeployment();
});
it("Should enforce access control", async function () {
const [, attacker] = await ethers.getSigners();
// Attempt unauthorized withdrawal
await expect(contract.connect(attacker).withdraw(100)).to.be.revertedWith(
"Ownable: caller is not the owner",
);
});
});
});
Audit Preparation
contract WellDocumentedContract {
/**
* @title Well Documented Contract
* @dev Example of proper documentation for audits
* @notice This contract handles user deposits and withdrawals
*/
/// @notice Mapping of user balances
mapping(address => uint256) public balances;
/**
* @dev Deposits ETH into the contract
* @notice Anyone can deposit funds
*/
function deposit() public payable {
require(msg.value > 0, "Must send ETH");
balances[msg.sender] += msg.value;
}
/**
* @dev Withdraws user's balance
* @notice Follows CEI pattern to prevent reentrancy
* @param amount Amount to withdraw in wei
*/
function withdraw(uint256 amount) public {
// CHECKS
require(amount <= balances[msg.sender], "Insufficient balance");
// EFFECTS
balances[msg.sender] -= amount;
// INTERACTIONS
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Common Pitfalls
- Using
tx.originfor Authentication: Usemsg.senderinstead - Unchecked External Calls: Always check return values
- Delegatecall to Untrusted Contracts: Can hijack your contract
- Floating Pragma: Pin to specific Solidity version
- Missing Events: Emit events for state changes
- Excessive Gas in Loops: Can hit block gas limit
- No Upgrade Path: Consider proxy patterns if upgrades needed
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: clawdbotatg
- Source: clawdbotatg/clawdviction
- License: MIT
- Homepage: https://clawdviction.vercel.app
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.