### Solidity: Secure CREATE2 Deployments (EIP-1014) Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Illustrates secure implementation of CREATE2 for deterministic contract deployment in Solidity. Vulnerable examples highlight issues like unchecked return values and predictable salts, while secure examples show how to validate deployment and use secure salt generation. ```solidity // CREATE2 address calculation // address = keccak256(0xff ++ deployer ++ salt ++ keccak256(init_code))[12:] // VULNERABLE: Unchecked CREATE2 return value function deployUnsafe(bytes32 salt, bytes memory bytecode) external returns (address) { address deployed; assembly { deployed := create2(0, add(bytecode, 0x20), mload(bytecode), salt) } // Missing check: deployed could be address(0) if deployment failed! return deployed; } // SECURE: Check CREATE2 return value function deploySafe(bytes32 salt, bytes memory bytecode) external returns (address) { address deployed; assembly { deployed := create2(0, add(bytecode, 0x20), mload(bytecode), salt) } require(deployed != address(0), "Deployment failed"); return deployed; } // VULNERABLE: Predictable salt enables frontrunning function deployWithPredictableSalt(uint256 counter) external returns (address) { bytes32 salt = bytes32(counter); // Anyone can predict and frontrun return address(new MyContract{salt: salt}()); } // SECURE: Salt bound to msg.sender function deployWithSecureSalt(uint256 nonce) external returns (address) { bytes32 salt = keccak256(abi.encodePacked(msg.sender, nonce)); return address(new MyContract{salt: salt}()); } // WARNING: Pre-Dencun metamorphic pattern (now restricted by EIP-6780) // Attacker could: CREATE2 → SELFDESTRUCT → CREATE2 with different code // Post-Dencun: SELFDESTRUCT only clears code in same transaction as creation ``` -------------------------------- ### Solidity: Account for Cold Access Costs (EIP-2929) Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Demonstrates how to securely handle gas costs in Solidity after EIP-2929, which introduced cold/warm access pricing. Vulnerable examples show how fixed gas assumptions can lead to underdelivery, while secure examples illustrate budgeting for cold access or using access lists. ```solidity // VULNERABLE: Gas limit doesn't account for cold access function callWithFixedGas(address target) external { // Pre-Berlin: CALL cost ~700 gas // Post-Berlin: Cold CALL cost ~2600 gas + 700 base = 3300 gas (bool success,) = target.call{gas: 50000}(""); // May underdeliver gas require(success); } // SECURE: Use access lists or account for cold costs function callWithAccessList(address target) external { // Either use EIP-2930 access lists to pre-warm addresses // Or budget for cold access in gas calculations uint256 coldAccessBuffer = 2600; // COLD_ACCOUNT_ACCESS_COST require(gasleft() > 50000 + coldAccessBuffer + 10000, "Insufficient gas"); (bool success,) = target.call{gas: 50000}(""); require(success); } // VULNERABLE: Hardcoded gas constants become stale after hardforks contract StaleGasConstants { uint256 constant CALL_GAS = 40; // Pre-EIP-150 value! uint256 constant SLOAD_GAS = 200; // Pre-EIP-2929 value! function estimateGas() external pure returns (uint256) { return CALL_GAS + SLOAD_GAS; // Dangerously underestimates } } ``` -------------------------------- ### Custom Factory with CREATE2 and Salt Source: https://github.com/bengalcatbalu/eip-security-handbook/blob/main/src/ercs/proxy/erc-1167.md An example of a custom factory contract using CREATE2 to deploy minimal proxies. It incorporates msg.sender into the salt to prevent address collision and front-running attacks, ensuring unique clone addresses. ```Solidity import "@openzeppelin/contracts/utils/CREATE2.sol"; contract CustomFactory { address public implementation; constructor(address _implementation) { implementation = _implementation; } function createClone(bytes32 salt) public returns (address) { // Using CREATE2 with a salt that includes msg.sender for uniqueness bytes memory bytecode = abi.encodePacked(implementation, bytes20(0)); // Minimal proxy bytecode structure address clone = CREATE2.deploy(0, salt, bytecode); // In a real scenario, you'd check if clone is address(0) and revert if necessary. // CREATE2.deploy handles some checks, but custom logic might be needed. return clone; } } ``` -------------------------------- ### ERC-4626 Deposit with Slippage Protection Wrapper (Solidity) Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt A Solidity example demonstrating how to implement slippage protection for ERC-4626 deposits using an external wrapper function. This addresses the lack of built-in slippage control in the base ERC-4626 standard. ```solidity // Missing slippage protection - use ERC-5143 wrapper function depositWithSlippage( uint256 assets, address receiver, uint256 minSharesOut ) external returns (uint256 shares) { shares = deposit(assets, receiver); require(shares >= minSharesOut, "Slippage exceeded"); } ``` -------------------------------- ### Calculate ERC-7201 Storage Root Formula Source: https://github.com/bengalcatbalu/eip-security-handbook/blob/main/src/ercs/proxy/erc-7201.md The mathematical formula used to derive a deterministic, collision-resistant storage slot for a given namespace ID string. It involves hashing, subtraction, and bitwise alignment to ensure the slot is safe from Solidity's default layout. ```solidity keccak256(abi.encode(uint256(keccak256(bytes(id))) - 1)) & ~bytes32(uint256(0xff)) ``` -------------------------------- ### OpenZeppelin Clones Library - clone Function Source: https://github.com/bengalcatbalu/eip-security-handbook/blob/main/src/ercs/proxy/erc-1167.md Demonstrates the use of OpenZeppelin's Clones library to create a minimal proxy. This function utilizes CREATE2 for predictable address generation and ensures the returned address is not zero, preventing silent creation failures. ```Solidity import "@openzeppelin/contracts/proxy/Clones.sol"; contract MyFactory { address public implementation; constructor(address _implementation) { implementation = _implementation; } function createClone() public returns (address) { address clone = Clones.clone(implementation); // Clones.clone() reverts if address is 0, handling silent creation failure. return clone; } } ``` -------------------------------- ### Implementing Secure STATICCALL Patterns in Solidity Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Demonstrates vulnerable and secure implementations of STATICCALL in Solidity. It highlights the importance of checking success booleans and avoiding state-modifying delegatecalls within view functions. ```solidity // VULNERABLE: Mislabeled view function with hidden state change contract VulnerableOracle { uint256 public price; function getPrice() external view returns (uint256) { (bool success, bytes memory data) = priceSource.delegatecall( abi.encodeWithSignature("updateAndGetPrice()") ); return abi.decode(data, (uint256)); } } // VULNERABLE: Unchecked staticcall return value function getBalanceUnsafe(address token, address account) external view returns (uint256) { (bool success, bytes memory data) = token.staticcall( abi.encodeWithSignature("balanceOf(address)", account) ); return abi.decode(data, (uint256)); } // SECURE: Check staticcall success function getBalanceSafe(address token, address account) external view returns (uint256) { (bool success, bytes memory data) = token.staticcall( abi.encodeWithSignature("balanceOf(address)", account) ); require(success, "staticcall failed"); return abi.decode(data, (uint256)); } // VULNERABLE: Read-only reentrancy during state transition contract VulnerableVault { mapping(address => uint256) public balances; function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount); (bool success,) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] -= amount; } } ``` -------------------------------- ### Implement Secure Gas Forwarding and Accounting in Solidity Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Demonstrates vulnerable and secure patterns for handling gas forwarding under the 63/64 rule. It highlights the necessity of accounting for cold access costs and value transfers, and warns against using gas-dependent logic in try/catch blocks. ```solidity // VULNERABLE: Incomplete minGas check ignores cold access, value transfer, memory expansion function executeWithMinGas(address target, uint256 minGas, bytes calldata data) external { // This check only accounts for 63/64 rule, missing other gas deductions require(gasleft() >= minGas * 64 / 63, "Insufficient gas"); (bool success,) = target.call{gas: minGas}(data); require(success, "Call failed"); } // SECURE: Account for all gas costs before the 63/64 split function executeWithMinGasSafe(address target, uint256 minGas, bytes calldata data) external { // Account for: COLD_ACCOUNT_ACCESS (2600) + potential value transfer (9000) + 63/64 overhead uint256 gasBuffer = 2600 + (minGas * 64 / 63); require(gasleft() >= gasBuffer + 50000, "Insufficient gas"); (bool success,) = target.call{gas: minGas}(data); require(success, "Call failed"); } // VULNERABLE: 1/64 gas overcount in reimbursement calculations function executeAndReimburse(address target, bytes calldata data) external { uint256 gasBefore = gasleft(); (bool success,) = target.call(data); uint256 gasUsed = gasBefore - gasleft(); // Includes retained 1/64! // Attacker can inflate gasUsed by supplying higher tx.gaslimit payable(msg.sender).transfer(gasUsed * tx.gasprice); } // VULNERABLE: Forced catch via gas tuning function riskyTryCatch(address target) external { try ITarget(target).riskyOperation() { // Success path } catch { // Attacker can force this path by tuning tx.gaslimit // Whatever state changes happen here are triggerable at will emit OperationFailed(); // Permanent state change in catch block } } ``` -------------------------------- ### Implementation Contract with Initializer Protection Source: https://github.com/bengalcatbalu/eip-security-handbook/blob/main/src/ercs/proxy/erc-1167.md A Solidity implementation contract designed to be used with minimal proxies. It includes an `initialize` function that is protected by an `initializer` modifier, preventing re-initialization and ensuring it's called only once. ```Solidity import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; contract MyImplementation is Initializable { address public owner; function initialize(address _owner) public initializer { owner = _owner; } // Other contract logic... } ``` -------------------------------- ### Implement Secure EIP-712 Typed Data Signing Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Demonstrates the secure construction of domain separators and permit functions. It highlights the importance of recomputing the domain separator using the current chain ID and enforcing nonce incrementation to prevent replay attacks. ```Solidity bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); function getDomainSeparator() public view returns (bytes32) { return keccak256(abi.encode( DOMAIN_TYPEHASH, keccak256("MyProtocol"), keccak256("1"), block.chainid, address(this) )); } function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external { require(block.timestamp <= deadline, "Permit expired"); bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)); bytes32 digest = keccak256(abi.encodePacked("\x19\x01", getDomainSeparator(), structHash)); address signer = ecrecover(digest, v, r, s); require(signer != address(0), "Invalid signature"); require(signer == owner, "Unauthorized"); _approve(owner, spender, value); } ``` -------------------------------- ### ERC-3156 Flash Lender Interface and Borrower Implementations (Solidity) Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Defines the ERC-3156 flash lender interface and demonstrates a vulnerable borrower contract that fails to verify the initiator, alongside a secure implementation that enforces checks on both the lender and initiator. It also includes essential lender-side checks for the callback return value. ```solidity // Flash lender interface interface IERC3156FlashLender { function maxFlashLoan(address token) external view returns (uint256); function flashFee(address token, uint256 amount) external view returns (uint256); function flashLoan( IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data ) external returns (bool); } // VULNERABLE: Borrower doesn't verify initiator contract VulnerableBorrower is IERC3156FlashBorrower { function onFlashLoan( address initiator, // WHO called flashLoan - not verified! address token, uint256 amount, uint256 fee, bytes calldata data ) external returns (bytes32) { // Attacker can call flashLoan with this contract as receiver // and attacker-controlled data, executing privileged logic _executeData(data); // DANGEROUS! IERC20(token).approve(msg.sender, amount + fee); return keccak256("ERC3156FlashBorrower.onFlashLoan"); } } // SECURE: Verify both lender and initiator contract SecureBorrower is IERC3156FlashBorrower { address public immutable trustedLender; address public immutable trustedInitiator; function onFlashLoan( address initiator, address token, uint256 amount, uint256 fee, bytes calldata data ) external returns (bytes32) { require(msg.sender == trustedLender, "Untrusted lender"); require(initiator == trustedInitiator, "Untrusted initiator"); // Now safe to execute logic _executeData(data); IERC20(token).approve(msg.sender, amount + fee); return keccak256("ERC3156FlashBorrower.onFlashLoan"); } } // Lender must check callback return value function flashLoan( IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data ) external returns (bool) { IERC20(token).transfer(address(receiver), amount); bytes32 result = receiver.onFlashLoan(msg.sender, token, amount, fee, data); require( result == keccak256("ERC3156FlashBorrower.onFlashLoan"), "Invalid callback return" ); IERC20(token).transferFrom(address(receiver), address(this), amount + fee); return true; } // maxFlashLoan MUST return 0, not revert, for unsupported tokens function maxFlashLoan(address token) external view returns (uint256) { if (!supportedTokens[token]) return 0; // Don't revert! return IERC20(token).balanceOf(address(this)); } // flashFee MUST revert for unsupported tokens function flashFee(address token, uint256 amount) external view returns (uint256) { require(supportedTokens[token], "Unsupported token"); // Must revert return amount * feeRate / 10000; } ``` -------------------------------- ### Secure ERC-20 Token Transfers with SafeERC20 Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Demonstrates how to safely transfer ERC-20 tokens, handling non-standard return values that can cause reverts with direct transfers. It contrasts a vulnerable approach with a secure one using SafeERC20. ```solidity // VULNERABLE: Assumes all tokens return bool function transferUnsafe(IERC20 token, address to, uint256 amount) external { // USDT, BNB return no data - this reverts! bool success = token.transfer(to, amount); require(success); } // SECURE: Use SafeERC20 import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; using SafeERC20 for IERC20; function transferSafe(IERC20 token, address to, uint256 amount) external { token.safeTransfer(to, amount); // Handles non-standard returns } ``` -------------------------------- ### Secure ERC-721 Minting and Transfer Patterns Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Demonstrates the use of _safeMint to prevent token locking and the importance of updating state before performing transfers to mitigate reentrancy risks in ERC-721. ```Solidity // VULNERABLE: Using _mint instead of _safeMint function mintUnsafe(address to, uint256 tokenId) external { _mint(to, tokenId); // Tokens sent to contracts may be locked forever } // SECURE: Use _safeMint for contract recipients function mintSafe(address to, uint256 tokenId) external { _safeMint(to, tokenId); // Calls onERC721Received on contract recipients } // VULNERABLE: Reentrancy via safeTransferFrom function stakeNFT(uint256 tokenId) external { nft.safeTransferFrom(msg.sender, address(this), tokenId); // Attacker's onERC721Received callback executes HERE // State not yet updated - reentrancy possible stakes[msg.sender].push(tokenId); } // SECURE: Update state before transfer function stakeNFTSafe(uint256 tokenId) external nonReentrant { stakes[msg.sender].push(tokenId); nft.safeTransferFrom(msg.sender, address(this), tokenId); } ``` -------------------------------- ### Preventing Permit Frontrunning in ERC-20 Transactions Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Explains the frontrunning vulnerability associated with the `permit()` function in ERC-20 tokens. The secure solution wraps the `permit()` call in a try-catch block, allowing the transfer to proceed even if the permit was frontrun. ```solidity // VULNERABLE: Permit frontrunning function permitAndTransfer( IERC20Permit token, address owner, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { token.permit(owner, address(this), value, deadline, v, r, s); // Frontrunnable! token.transferFrom(owner, address(this), value); } // SECURE: Wrap permit in try-catch function permitAndTransferSafe( IERC20Permit token, address owner, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { try token.permit(owner, address(this), value, deadline, v, r, s) {} catch {} // If permit was frontrun, allowance already set - transfer still works token.transferFrom(owner, address(this), value); } ``` -------------------------------- ### OpenZeppelin ERC1967Proxy Implementation Source: https://github.com/bengalcatbalu/eip-security-handbook/blob/main/src/ercs/proxy/erc-1967.md A snippet from OpenZeppelin's ERC1967Proxy contract demonstrating the use of the standard slot calculation and event emissions for proxy upgrades. It shows how the implementation, admin, and beacon addresses are managed. ```solidity import "@openzeppelin/contracts/proxy/ERC1967/ERC1967.sol"; contract ERC1967Proxy is ERC1967 { address private _implementation; address private _admin; address private _beacon; constructor(address initialImplementation) { _implementation = initialImplementation; emit Upgraded(initialImplementation); } function upgradeTo(address newImplementation) public { require(msg.sender == _admin, "ERC1967Proxy: caller is not the admin"); _upgradeTo(newImplementation); } function _upgradeTo(address newImplementation) internal virtual { address oldImplementation = _implementation; _implementation = newImplementation; emit Upgraded(newImplementation); // ... other logic ... } // ... other functions for admin and beacon management ... } ``` -------------------------------- ### ERC-1967 Proxy Upgrade Event - Upgraded Source: https://github.com/bengalcatbalu/eip-security-handbook/blob/main/src/ercs/proxy/erc-1967.md Illustrates the 'Upgraded' event that must be emitted when the implementation address in an ERC-1967 proxy is changed. This event logs the new implementation address for off-chain tools and auditing. ```solidity event Upgraded(address indexed implementation); // Example of emitting the event: // _setImplementation(newImplementation); // emit Upgraded(newImplementation); ``` -------------------------------- ### Implement ERC-165 supportsInterface Source: https://github.com/bengalcatbalu/eip-security-handbook/blob/main/src/ercs/defi/erc-165.md A standard implementation of the supportsInterface function in Solidity. It uses the type(I).interfaceId pattern to ensure accurate interface identification and proper inheritance handling. ```solidity function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC165).interfaceId || super.supportsInterface(interfaceId); } ``` -------------------------------- ### ERC-721 Token Metadata and Receiver Implementation Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Provides secure implementations for tokenURI to handle non-existent tokens and the required onERC721Received callback for contract recipients. ```Solidity // VULNERABLE: tokenURI doesn't revert for non-existent tokens function tokenURI(uint256 tokenId) external view returns (string memory) { return string(abi.encodePacked(baseURI, tokenId.toString())); } // SECURE: Revert for non-existent tokens function tokenURISafe(uint256 tokenId) external view returns (string memory) { require(_exists(tokenId), "Token does not exist"); return string(abi.encodePacked(baseURI, tokenId.toString())); } // onERC721Received implementation function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4) { return IERC721Receiver.onERC721Received.selector; // 0x150b7a02 } ``` -------------------------------- ### Implement Secure ERC-1967 Proxy Storage Source: https://context7.com/bengalcatbalu/eip-security-handbook/llms.txt Defines standardized storage slots for proxy metadata and demonstrates secure access-controlled upgrades. It emphasizes the use of the 'minus 1' formula to prevent storage collisions and the necessity of access control for implementation updates. ```Solidity bytes32 constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); function upgradeSafe(address newImpl) external onlyOwner { require(newImpl.code.length > 0, "Not a contract"); StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImpl; emit Upgraded(newImpl); } fallback() external payable { address impl = StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } ```