June 2, 2026
Solidity BasicsPart 3: Advanced Patterns
Series: Web3 Security Zero se Advance π‘οΈ | Article #7 By HackerMD | 26 min read
Hacker MD
16 min read
Aaj Kya Seekhenge?
- Receive & Fallback functions ETH receive karna
- Low-level calls call, delegatecall, staticcall
- Assembly (Yul) inline assembly basics
- ABI Encoding abi.encode, abi.encodePacked
- Contract creation CREATE vs CREATE2
- Gas optimization patterns
- Checks-Effects-Interactions (CEI) pattern
- ReentrancyGuard kaise kaam karta hai
- Har concept pe security bugs aur attacks!
Hacker Note: Yeh article ka har concept directly kisi na kisi million dollar hack se linked hai! Fallback function = TheDAO hack! Low-level calls = Reentrancy! CREATE2 = Salting attacks! Ek ek cheez dhyan se padho!
PART 1: Receive & Fallback ETH Kaise Aata Hai Contract Mein?
// Contract ETH receive karne ke liye
// Special functions chahiye!
contract ETHReceiver {
event Received(address from, uint256 amount);
event FallbackCalled(
address from,
uint256 amount,
bytes data
);
// βββ receive() ββββββββββββββββββββββββ
// Jab:
// β Pure ETH transfer aaye (no data)
// β msg.data EMPTY ho
// Conditions:
// β MUST be external
// β MUST be payable
// β No arguments, no return
receive() external payable {
emit Received(msg.sender, msg.value);
// Simple ETH receive!
}
// βββ fallback() βββββββββββββββββββββββ
// Jab:
// β Koi function match nahi karta
// β msg.data non-empty ho
// β receive() nahi hai aur ETH aaye
// Conditions:
// β MUST be external
// β payable optional
fallback() external payable {
emit FallbackCalled(
msg.sender,
msg.value,
msg.data
);
}
}
// ETH receive decision tree:
//
// ETH Transfer aaya
// β
// msg.data empty?
// βββββββ΄βββββββ
// YES NO
// β β
// receive() fallback()
// exist? exist?
// ββββ΄βββ ββββ΄βββ
// YES NO YES NO
// β β β β
// run fallback run REVERT!
// run?
// ββ΄β
// YES NO
// β β
// run REVERT// Contract ETH receive karne ke liye
// Special functions chahiye!
contract ETHReceiver {
event Received(address from, uint256 amount);
event FallbackCalled(
address from,
uint256 amount,
bytes data
);
// βββ receive() ββββββββββββββββββββββββ
// Jab:
// β Pure ETH transfer aaye (no data)
// β msg.data EMPTY ho
// Conditions:
// β MUST be external
// β MUST be payable
// β No arguments, no return
receive() external payable {
emit Received(msg.sender, msg.value);
// Simple ETH receive!
}
// βββ fallback() βββββββββββββββββββββββ
// Jab:
// β Koi function match nahi karta
// β msg.data non-empty ho
// β receive() nahi hai aur ETH aaye
// Conditions:
// β MUST be external
// β payable optional
fallback() external payable {
emit FallbackCalled(
msg.sender,
msg.value,
msg.data
);
}
}
// ETH receive decision tree:
//
// ETH Transfer aaya
// β
// msg.data empty?
// βββββββ΄βββββββ
// YES NO
// β β
// receive() fallback()
// exist? exist?
// ββββ΄βββ ββββ΄βββ
// YES NO YES NO
// β β β β
// run fallback run REVERT!
// run?
// ββ΄β
// YES NO
// β β
// run REVERTFallback Security Bugs:
// BUG 1: TheDAO Style β Fallback Reentrancy!
// (2016 β $60M hack ka origin)
contract VulnerableDAO {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "Nothing to withdraw!");
// β οΈ External call PEHLE!
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok, "Transfer failed!");
// β οΈ State update BAAD MEIN!
balances[msg.sender] = 0;
// Attacker ka fallback/receive baar baar
// withdraw() call karta hai!
// Balance kabhi 0 nahi hota withdraw se pehle!
}
}
// Attacker contract:
contract Attacker {
VulnerableDAO dao;
uint256 public count;
constructor(address _dao) {
dao = VulnerableDAO(_dao);
}
function attack() external payable {
dao.deposit{value: msg.value}();
dao.withdraw();
}
// Yeh baar baar call hoga!
receive() external payable {
count++;
if (count < 10) {
dao.withdraw(); // Re-enter!
}
}
}
// β
FIX: CEI Pattern (baad mein detail mein!)
function withdrawSafe() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "Nothing!");
// Effect FIRST!
balances[msg.sender] = 0;
// Interaction LAST!
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok, "Transfer failed!");
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 2: Accidental ETH lock!
contract NoReceive {
// receive() aur fallback() DONO missing!
function doSomething() external {
// Normal function
}
// Koi ETH bheje β REVERT!
// ETH transfer always fails!
// Agar protocol ETH expect karta tha:
// β Funds stuck forever!
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 3: Payable fallback β unintended ETH accept!
contract Dangerous {
// Developer ne sirf fallback likha
// "proxy ke liye"
fallback() external payable {
// Forward to implementation...
}
// Ab koi bhi ETH bhej sakta hai!
// Contract ETH hold karne ke liye
// designed nahi tha!
// ETH permanently stuck!
}// BUG 1: TheDAO Style β Fallback Reentrancy!
// (2016 β $60M hack ka origin)
contract VulnerableDAO {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "Nothing to withdraw!");
// β οΈ External call PEHLE!
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok, "Transfer failed!");
// β οΈ State update BAAD MEIN!
balances[msg.sender] = 0;
// Attacker ka fallback/receive baar baar
// withdraw() call karta hai!
// Balance kabhi 0 nahi hota withdraw se pehle!
}
}
// Attacker contract:
contract Attacker {
VulnerableDAO dao;
uint256 public count;
constructor(address _dao) {
dao = VulnerableDAO(_dao);
}
function attack() external payable {
dao.deposit{value: msg.value}();
dao.withdraw();
}
// Yeh baar baar call hoga!
receive() external payable {
count++;
if (count < 10) {
dao.withdraw(); // Re-enter!
}
}
}
// β
FIX: CEI Pattern (baad mein detail mein!)
function withdrawSafe() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "Nothing!");
// Effect FIRST!
balances[msg.sender] = 0;
// Interaction LAST!
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok, "Transfer failed!");
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 2: Accidental ETH lock!
contract NoReceive {
// receive() aur fallback() DONO missing!
function doSomething() external {
// Normal function
}
// Koi ETH bheje β REVERT!
// ETH transfer always fails!
// Agar protocol ETH expect karta tha:
// β Funds stuck forever!
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 3: Payable fallback β unintended ETH accept!
contract Dangerous {
// Developer ne sirf fallback likha
// "proxy ke liye"
fallback() external payable {
// Forward to implementation...
}
// Ab koi bhi ETH bhej sakta hai!
// Contract ETH hold karne ke liye
// designed nahi tha!
// ETH permanently stuck!
}PART 2: Low-Level Calls Powerful aur Dangerous!
contract LowLevelCalls {
// βββ .call() ββββββββββββββββββββββββββ
// Sabse flexible!
// ETH transfer + function call
// Returns: (bool success, bytes memory data)
function callExample(
address target,
bytes memory data
) external payable returns (bool, bytes memory) {
// With ETH:
(bool ok, bytes memory ret) =
target.call{
value: msg.value,
gas: 50000 // Gas limit optional
}(data);
return (ok, ret);
}
// Specific function call:
function callTransfer(
address token,
address to,
uint256 amount
) external returns (bool) {
(bool ok, bytes memory data) = token.call(
abi.encodeWithSignature(
"transfer(address,uint256)",
to,
amount
)
);
// Return value decode:
if (ok && data.length > 0) {
return abi.decode(data, (bool));
}
return ok;
}
// βββ .delegatecall() ββββββββββββββββββ
// Target ka CODE β Caller ka CONTEXT!
// msg.sender = Original caller
// Storage = Calling contract ka!
// β οΈ EXTREMELY DANGEROUS!
function delegateExample(
address implementation,
bytes memory data
) external returns (bool, bytes memory) {
(bool ok, bytes memory ret) =
implementation.delegatecall(data);
return (ok, ret);
}
// βββ .staticcall() ββββββββββββββββββββ
// Read-only call!
// State change β REVERT!
// Safe for reading data
function staticExample(
address target,
bytes memory data
) external view returns (bytes memory) {
(bool ok, bytes memory ret) =
target.staticcall(data);
require(ok, "Static call failed!");
return ret;
}
}contract LowLevelCalls {
// βββ .call() ββββββββββββββββββββββββββ
// Sabse flexible!
// ETH transfer + function call
// Returns: (bool success, bytes memory data)
function callExample(
address target,
bytes memory data
) external payable returns (bool, bytes memory) {
// With ETH:
(bool ok, bytes memory ret) =
target.call{
value: msg.value,
gas: 50000 // Gas limit optional
}(data);
return (ok, ret);
}
// Specific function call:
function callTransfer(
address token,
address to,
uint256 amount
) external returns (bool) {
(bool ok, bytes memory data) = token.call(
abi.encodeWithSignature(
"transfer(address,uint256)",
to,
amount
)
);
// Return value decode:
if (ok && data.length > 0) {
return abi.decode(data, (bool));
}
return ok;
}
// βββ .delegatecall() ββββββββββββββββββ
// Target ka CODE β Caller ka CONTEXT!
// msg.sender = Original caller
// Storage = Calling contract ka!
// β οΈ EXTREMELY DANGEROUS!
function delegateExample(
address implementation,
bytes memory data
) external returns (bool, bytes memory) {
(bool ok, bytes memory ret) =
implementation.delegatecall(data);
return (ok, ret);
}
// βββ .staticcall() ββββββββββββββββββββ
// Read-only call!
// State change β REVERT!
// Safe for reading data
function staticExample(
address target,
bytes memory data
) external view returns (bytes memory) {
(bool ok, bytes memory ret) =
target.staticcall(data);
require(ok, "Static call failed!");
return ret;
}
}Call vs DelegateCall vs StaticCall:
Feature | call() | delegatecall() | staticcall()
-----------------|-------------|----------------|-------------
msg.sender | This contract| Original caller| This contract
msg.value | Forwarded | Caller's value | 0
Storage used | Target's | CALLER'S! | Target's
State change | Allowed | CALLER's state!| FORBIDDEN!
ETH transfer | Yes | No | No
Use case | Normal calls| Proxy pattern | Read only
Security risk | Reentrancy | Storage collision| Safe!
DELEGATECALL VISUAL:
ββββββββββββββββββββ delegatecall ββββββββββββββββββββ
β Contract A β ββββββββββββββββββ β Library/Impl B β
β β β β
β Storage: β β Code runs here β
β slot0: owner β βββ modifies ββββ β but A's storage β
β slot1: balance β β is affected! β
ββββββββββββββββββββ ββββββββββββββββββββFeature | call() | delegatecall() | staticcall()
-----------------|-------------|----------------|-------------
msg.sender | This contract| Original caller| This contract
msg.value | Forwarded | Caller's value | 0
Storage used | Target's | CALLER'S! | Target's
State change | Allowed | CALLER's state!| FORBIDDEN!
ETH transfer | Yes | No | No
Use case | Normal calls| Proxy pattern | Read only
Security risk | Reentrancy | Storage collision| Safe!
DELEGATECALL VISUAL:
ββββββββββββββββββββ delegatecall ββββββββββββββββββββ
β Contract A β ββββββββββββββββββ β Library/Impl B β
β β β β
β Storage: β β Code runs here β
β slot0: owner β βββ modifies ββββ β but A's storage β
β slot1: balance β β is affected! β
ββββββββββββββββββββ ββββββββββββββββββββLow-Level Call Bugs:
// BUG 1: Return value not checked!
contract Unchecked {
function sendETH(address to) external {
// β οΈ Return value IGNORE!
to.call{value: 1 ether}("");
// Agar call fail ho?
// No revert! Code continues!
// ETH lost silently!
}
// β
CORRECT:
function sendETHSafe(address to) external {
(bool ok,) = to.call{value: 1 ether}("");
require(ok, "ETH transfer failed!");
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 2: Arbitrary delegatecall!
contract ArbitraryDelegate {
address public owner; // slot 0
// β οΈ User controlled target!
function execute(
address target, // Attacker controls this!
bytes memory data
) external {
target.delegatecall(data);
// Attacker passes malicious contract:
// contract Evil {
// function pwn() external {
// // slot 0 = owner!
// assembly { sstore(0, caller()) }
// }
// }
// Result: owner = attacker!
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 3: Gas forwarding attack!
contract GasIssue {
function forward(address target) external {
// All gas forward kar raha hai!
target.call{gas: gasleft()}(
abi.encodeWithSignature("expensive()")
);
// Target ko infinite gas!
// Return bomb possible!
}
// β
CORRECT: Gas limit lagao!
function forwardSafe(address target) external {
target.call{gas: 50000}(
abi.encodeWithSignature("safe()")
);
}
}// BUG 1: Return value not checked!
contract Unchecked {
function sendETH(address to) external {
// β οΈ Return value IGNORE!
to.call{value: 1 ether}("");
// Agar call fail ho?
// No revert! Code continues!
// ETH lost silently!
}
// β
CORRECT:
function sendETHSafe(address to) external {
(bool ok,) = to.call{value: 1 ether}("");
require(ok, "ETH transfer failed!");
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 2: Arbitrary delegatecall!
contract ArbitraryDelegate {
address public owner; // slot 0
// β οΈ User controlled target!
function execute(
address target, // Attacker controls this!
bytes memory data
) external {
target.delegatecall(data);
// Attacker passes malicious contract:
// contract Evil {
// function pwn() external {
// // slot 0 = owner!
// assembly { sstore(0, caller()) }
// }
// }
// Result: owner = attacker!
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 3: Gas forwarding attack!
contract GasIssue {
function forward(address target) external {
// All gas forward kar raha hai!
target.call{gas: gasleft()}(
abi.encodeWithSignature("expensive()")
);
// Target ko infinite gas!
// Return bomb possible!
}
// β
CORRECT: Gas limit lagao!
function forwardSafe(address target) external {
target.call{gas: 50000}(
abi.encodeWithSignature("safe()")
);
}
}PART 3: Inline Assembly EVM Ka Direct Access!
// Assembly = EVM Opcodes directly Solidity mein
// Use cases:
// β Gas optimization
// β Operations Solidity mein possible nahi
// β Low-level storage manipulation
// β Custom memory management
contract AssemblyExamples {
// βββ Basic Assembly βββββββββββββββββββ
function addAssembly(uint256 a, uint256 b)
public pure
returns (uint256 result)
{
assembly {
result := add(a, b)
// := assignment in Yul
}
}
// βββ Storage Direct Access ββββββββββββ
function setSlot(uint256 slot, uint256 value)
internal
{
assembly {
sstore(slot, value)
}
}
function getSlot(uint256 slot)
internal view
returns (uint256 value)
{
assembly {
value := sload(slot)
}
}
// βββ Address Operations βββββββββββββββ
function getContractSize(address addr)
public view
returns (uint256 size)
{
assembly {
size := extcodesize(addr)
}
// extcodesize = 0 β EOA
// extcodesize > 0 β Contract
// β οΈ But: Constructor ke time = 0!
// isContract() bypass possible!
}
// βββ Efficient ETH Transfer βββββββββββ
function efficientTransfer(
address to,
uint256 amount
) internal {
assembly {
// Direct ETH transfer
let ok := call(
gas(), // All gas
to, // Recipient
amount, // Value
0, 0, // No input data
0, 0 // No output
)
if iszero(ok) {
revert(0, 0)
}
}
}
// βββ Memory Operations ββββββββββββββββ
function memoryExample()
public pure
returns (bytes32 result)
{
assembly {
// Free memory pointer:
let ptr := mload(0x40)
// 0x40 = Free memory pointer location!
// Store data:
mstore(ptr, 0x12345678)
// Update free memory pointer:
mstore(0x40, add(ptr, 0x20))
result := mload(ptr)
}
}
}// Assembly = EVM Opcodes directly Solidity mein
// Use cases:
// β Gas optimization
// β Operations Solidity mein possible nahi
// β Low-level storage manipulation
// β Custom memory management
contract AssemblyExamples {
// βββ Basic Assembly βββββββββββββββββββ
function addAssembly(uint256 a, uint256 b)
public pure
returns (uint256 result)
{
assembly {
result := add(a, b)
// := assignment in Yul
}
}
// βββ Storage Direct Access ββββββββββββ
function setSlot(uint256 slot, uint256 value)
internal
{
assembly {
sstore(slot, value)
}
}
function getSlot(uint256 slot)
internal view
returns (uint256 value)
{
assembly {
value := sload(slot)
}
}
// βββ Address Operations βββββββββββββββ
function getContractSize(address addr)
public view
returns (uint256 size)
{
assembly {
size := extcodesize(addr)
}
// extcodesize = 0 β EOA
// extcodesize > 0 β Contract
// β οΈ But: Constructor ke time = 0!
// isContract() bypass possible!
}
// βββ Efficient ETH Transfer βββββββββββ
function efficientTransfer(
address to,
uint256 amount
) internal {
assembly {
// Direct ETH transfer
let ok := call(
gas(), // All gas
to, // Recipient
amount, // Value
0, 0, // No input data
0, 0 // No output
)
if iszero(ok) {
revert(0, 0)
}
}
}
// βββ Memory Operations ββββββββββββββββ
function memoryExample()
public pure
returns (bytes32 result)
{
assembly {
// Free memory pointer:
let ptr := mload(0x40)
// 0x40 = Free memory pointer location!
// Store data:
mstore(ptr, 0x12345678)
// Update free memory pointer:
mstore(0x40, add(ptr, 0x20))
result := mload(ptr)
}
}
}Assembly Security Bugs:
// BUG 1: isContract bypass via constructor!
contract ConstructorBypass {
modifier onlyEOA() {
uint256 size;
assembly {
size := extcodesize(caller())
}
require(size == 0, "No contracts!");
_;
}
function sensitiveFunc()
external
onlyEOA
{
// "Only EOA can call" β OR CAN THEY?
}
}
// Attack:
contract Attacker {
constructor(address target) {
// Constructor ke time:
// extcodesize(this) = 0!
// Even though we ARE a contract!
ConstructorBypass(target).sensitiveFunc();
// β Check passes! We're a contract!
// but check thinks we're EOA!
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 2: Memory corruption!
contract MemoryBug {
function dangerous(bytes memory data)
external pure
returns (uint256)
{
assembly {
// β οΈ Free memory pointer nahi update kiya!
let ptr := mload(0x40)
mstore(ptr, mload(add(data, 0x20)))
// 0x40 update bhool gaya!
// Next allocation same ptr use karega!
// Memory overlap = Data corruption!
}
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 3: Arbitrary storage write!
contract StorageWrite {
address public owner; // slot 0
uint256 public totalFunds; // slot 1
function updateConfig(
uint256 slot, // β οΈ User controlled!
uint256 value
) external {
assembly {
sstore(slot, value)
// Attacker β slot=0, value=attacker_addr
// owner = attacker!
}
}
}// BUG 1: isContract bypass via constructor!
contract ConstructorBypass {
modifier onlyEOA() {
uint256 size;
assembly {
size := extcodesize(caller())
}
require(size == 0, "No contracts!");
_;
}
function sensitiveFunc()
external
onlyEOA
{
// "Only EOA can call" β OR CAN THEY?
}
}
// Attack:
contract Attacker {
constructor(address target) {
// Constructor ke time:
// extcodesize(this) = 0!
// Even though we ARE a contract!
ConstructorBypass(target).sensitiveFunc();
// β Check passes! We're a contract!
// but check thinks we're EOA!
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 2: Memory corruption!
contract MemoryBug {
function dangerous(bytes memory data)
external pure
returns (uint256)
{
assembly {
// β οΈ Free memory pointer nahi update kiya!
let ptr := mload(0x40)
mstore(ptr, mload(add(data, 0x20)))
// 0x40 update bhool gaya!
// Next allocation same ptr use karega!
// Memory overlap = Data corruption!
}
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// BUG 3: Arbitrary storage write!
contract StorageWrite {
address public owner; // slot 0
uint256 public totalFunds; // slot 1
function updateConfig(
uint256 slot, // β οΈ User controlled!
uint256 value
) external {
assembly {
sstore(slot, value)
// Attacker β slot=0, value=attacker_addr
// owner = attacker!
}
}
}PART 4: ABI Encoding Data Kaise Pack Hota Hai!
contract ABIEncoding {
// βββ abi.encode() βββββββββββββββββββββ
// Padded encoding β 32 bytes per element
// Safe for all types!
function encodeExample()
external pure
returns (bytes memory)
{
return abi.encode(
uint256(100),
address(0xABC...),
bool(true)
);
// Each value padded to 32 bytes:
// 0x0000...0064 (100)
// 0x0000...ABC (address)
// 0x0000...0001 (true)
// Total: 96 bytes
}
// βββ abi.encodePacked() βββββββββββββββ
// Compact encoding β NO padding!
// Gas efficient
// β οΈ COLLISION POSSIBLE!
function encodePackedExample()
external pure
returns (bytes memory)
{
return abi.encodePacked(
uint256(100),
address(0xABC...)
);
// No padding! Tight packing!
// 32 bytes + 20 bytes = 52 bytes
}
// βββ abi.encodeWithSelector() βββββββββ
// Function call data encode karo!
function encodeCall()
external pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256(
"transfer(address,uint256)"
)),
address(0xBob),
uint256(1000)
);
// = 0xa9059cbb + encoded args
}
// βββ abi.encodeWithSignature() ββββββββ
// String signature use karo
function encodeWithSig()
external pure
returns (bytes memory)
{
return abi.encodeWithSignature(
"transfer(address,uint256)",
address(0xBob),
uint256(1000)
);
// Same as above β cleaner syntax!
}
// βββ Decoding βββββββββββββββββββββββββ
function decodeExample(bytes memory data)
external pure
returns (uint256, address, bool)
{
return abi.decode(
data,
(uint256, address, bool)
);
}
}contract ABIEncoding {
// βββ abi.encode() βββββββββββββββββββββ
// Padded encoding β 32 bytes per element
// Safe for all types!
function encodeExample()
external pure
returns (bytes memory)
{
return abi.encode(
uint256(100),
address(0xABC...),
bool(true)
);
// Each value padded to 32 bytes:
// 0x0000...0064 (100)
// 0x0000...ABC (address)
// 0x0000...0001 (true)
// Total: 96 bytes
}
// βββ abi.encodePacked() βββββββββββββββ
// Compact encoding β NO padding!
// Gas efficient
// β οΈ COLLISION POSSIBLE!
function encodePackedExample()
external pure
returns (bytes memory)
{
return abi.encodePacked(
uint256(100),
address(0xABC...)
);
// No padding! Tight packing!
// 32 bytes + 20 bytes = 52 bytes
}
// βββ abi.encodeWithSelector() βββββββββ
// Function call data encode karo!
function encodeCall()
external pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256(
"transfer(address,uint256)"
)),
address(0xBob),
uint256(1000)
);
// = 0xa9059cbb + encoded args
}
// βββ abi.encodeWithSignature() ββββββββ
// String signature use karo
function encodeWithSig()
external pure
returns (bytes memory)
{
return abi.encodeWithSignature(
"transfer(address,uint256)",
address(0xBob),
uint256(1000)
);
// Same as above β cleaner syntax!
}
// βββ Decoding βββββββββββββββββββββββββ
function decodeExample(bytes memory data)
external pure
returns (uint256, address, bool)
{
return abi.decode(
data,
(uint256, address, bool)
);
}
}ABI Encoding Bugs:
// BUG: encodePacked Collision Attack!
// Hash signing mein bahut common!
contract VulnerableSig {
function getHash(
address user,
uint256 amount,
string memory message
) public pure returns (bytes32) {
// β οΈ encodePacked collision!
return keccak256(
abi.encodePacked(user, amount, message)
);
}
}
// ATTACK:
// Normal call:
// user=0xAlice, amount=100, message="hello"
// Packed: [alice_20bytes][100_32bytes][hello_5bytes]
// Attacker:
// user=0xAlice, amount=100, message="hell"
// + different amount in message!
// Packed bytes can be SAME!
// Hash collision! Signature reuse!
// β
FIX: Use abi.encode() for dynamic types!
function getHashSafe(
address user,
uint256 amount,
string memory message
) public pure returns (bytes32) {
return keccak256(
abi.encode(user, amount, message)
// Padded encoding = No collision!
);
}
// βββββββββββββββββββββββββββββββββββββββββ
// REAL EXAMPLE: Signature Replay
contract VulnPayment {
mapping(bytes32 => bool) public usedHashes;
function pay(
address to,
uint256 amount,
uint8 v, bytes32 r, bytes32 s
) external {
bytes32 hash = keccak256(
abi.encodePacked(to, amount)
// β οΈ No chainId! No nonce!
);
address signer = ecrecover(hash, v, r, s);
require(signer == owner, "Invalid sig!");
// β οΈ No hash usage tracking!
payable(to).transfer(amount);
// Replay attack!
// Same signature β Infinite payments!
}
// β
CORRECT:
function paySafe(
address to,
uint256 amount,
uint256 nonce,
uint8 v, bytes32 r, bytes32 s
) external {
bytes32 hash = keccak256(
abi.encode(
to,
amount,
nonce,
block.chainid, // Chain specific!
address(this) // Contract specific!
)
);
require(!usedHashes[hash], "Replayed!");
usedHashes[hash] = true; // Mark used!
address signer = ecrecover(hash, v, r, s);
require(signer == owner, "Invalid!");
payable(to).transfer(amount);
}
}// BUG: encodePacked Collision Attack!
// Hash signing mein bahut common!
contract VulnerableSig {
function getHash(
address user,
uint256 amount,
string memory message
) public pure returns (bytes32) {
// β οΈ encodePacked collision!
return keccak256(
abi.encodePacked(user, amount, message)
);
}
}
// ATTACK:
// Normal call:
// user=0xAlice, amount=100, message="hello"
// Packed: [alice_20bytes][100_32bytes][hello_5bytes]
// Attacker:
// user=0xAlice, amount=100, message="hell"
// + different amount in message!
// Packed bytes can be SAME!
// Hash collision! Signature reuse!
// β
FIX: Use abi.encode() for dynamic types!
function getHashSafe(
address user,
uint256 amount,
string memory message
) public pure returns (bytes32) {
return keccak256(
abi.encode(user, amount, message)
// Padded encoding = No collision!
);
}
// βββββββββββββββββββββββββββββββββββββββββ
// REAL EXAMPLE: Signature Replay
contract VulnPayment {
mapping(bytes32 => bool) public usedHashes;
function pay(
address to,
uint256 amount,
uint8 v, bytes32 r, bytes32 s
) external {
bytes32 hash = keccak256(
abi.encodePacked(to, amount)
// β οΈ No chainId! No nonce!
);
address signer = ecrecover(hash, v, r, s);
require(signer == owner, "Invalid sig!");
// β οΈ No hash usage tracking!
payable(to).transfer(amount);
// Replay attack!
// Same signature β Infinite payments!
}
// β
CORRECT:
function paySafe(
address to,
uint256 amount,
uint256 nonce,
uint8 v, bytes32 r, bytes32 s
) external {
bytes32 hash = keccak256(
abi.encode(
to,
amount,
nonce,
block.chainid, // Chain specific!
address(this) // Contract specific!
)
);
require(!usedHashes[hash], "Replayed!");
usedHashes[hash] = true; // Mark used!
address signer = ecrecover(hash, v, r, s);
require(signer == owner, "Invalid!");
payable(to).transfer(amount);
}
}PART 5: Contract Creation CREATE vs CREATE2!
// βββ CREATE (Regular) βββββββββββββββββββββ
// Address = keccak256(deployer_address + nonce)
// Nonce se predictable β lekin pehle se predict karna mushkil
contract Factory {
event ContractCreated(address newContract);
function createChild() external
returns (address)
{
// Regular CREATE:
ChildContract child = new ChildContract(
msg.sender
);
emit ContractCreated(address(child));
return address(child);
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// βββ CREATE2 (Deterministic) ββββββββββββββ
// Address = keccak256(0xFF + deployer + salt + bytecodeHash)
// SALT control karo β Address predict karo!
// Deploy karo β Destroy karo β Same address pe re-deploy!
contract Factory2 {
event ContractCreated(
address newContract,
bytes32 salt
);
function createWithSalt(
bytes32 salt
) external returns (address) {
ChildContract child = new ChildContract{
salt: salt
}(msg.sender);
emit ContractCreated(address(child), salt);
return address(child);
}
// Address predict karo DEPLOY se pehle!
function predictAddress(
bytes32 salt
) external view returns (address) {
bytes memory bytecode = abi.encodePacked(
type(ChildContract).creationCode,
abi.encode(msg.sender)
);
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xFF),
address(this),
salt,
keccak256(bytecode)
)
);
return address(uint160(uint256(hash)));
}
}
contract ChildContract {
address public owner;
constructor(address _owner) {
owner = _owner;
}
}// βββ CREATE (Regular) βββββββββββββββββββββ
// Address = keccak256(deployer_address + nonce)
// Nonce se predictable β lekin pehle se predict karna mushkil
contract Factory {
event ContractCreated(address newContract);
function createChild() external
returns (address)
{
// Regular CREATE:
ChildContract child = new ChildContract(
msg.sender
);
emit ContractCreated(address(child));
return address(child);
}
}
// βββββββββββββββββββββββββββββββββββββββββ
// βββ CREATE2 (Deterministic) ββββββββββββββ
// Address = keccak256(0xFF + deployer + salt + bytecodeHash)
// SALT control karo β Address predict karo!
// Deploy karo β Destroy karo β Same address pe re-deploy!
contract Factory2 {
event ContractCreated(
address newContract,
bytes32 salt
);
function createWithSalt(
bytes32 salt
) external returns (address) {
ChildContract child = new ChildContract{
salt: salt
}(msg.sender);
emit ContractCreated(address(child), salt);
return address(child);
}
// Address predict karo DEPLOY se pehle!
function predictAddress(
bytes32 salt
) external view returns (address) {
bytes memory bytecode = abi.encodePacked(
type(ChildContract).creationCode,
abi.encode(msg.sender)
);
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xFF),
address(this),
salt,
keccak256(bytecode)
)
);
return address(uint160(uint256(hash)));
}
}
contract ChildContract {
address public owner;
constructor(address _owner) {
owner = _owner;
}
}CREATE2 Security Bugs:
// BUG: Metamorphic Contract Attack!
// Same address pe different code deploy!
// Step 1: Malicious factory deploy karo
contract MetamorphicFactory {
mapping(bytes32 => address) public contracts;
function deploy(
bytes32 salt,
bytes memory initCode
) external returns (address addr) {
assembly {
addr := create2(
0,
add(initCode, 0x20),
mload(initCode),
salt
)
}
}
function destroy(bytes32 salt) external {
address target = contracts[salt];
// Selfdestruct the deployed contract
IDestructible(target).destroy();
delete contracts[salt];
}
}
// ATTACK SCENARIO:
// 1. Audit request: Deploy "safe" contract at addr X
// Auditors verify code at X β Clean! β
//
// 2. SELFDESTRUCT the contract at X!
// (Same address pe code wipe!)
//
// 3. CREATE2 same salt β Same address X!
// Deploy MALICIOUS code at same X!
//
// 4. Users trust "audited" address X
// Reality: Malicious code!
//
// Defense:
// β SELFDESTRUCT wale contracts trust mat karo!
// β Code hash verify karo har interaction pe!
// β Immutable contracts prefer karo!// BUG: Metamorphic Contract Attack!
// Same address pe different code deploy!
// Step 1: Malicious factory deploy karo
contract MetamorphicFactory {
mapping(bytes32 => address) public contracts;
function deploy(
bytes32 salt,
bytes memory initCode
) external returns (address addr) {
assembly {
addr := create2(
0,
add(initCode, 0x20),
mload(initCode),
salt
)
}
}
function destroy(bytes32 salt) external {
address target = contracts[salt];
// Selfdestruct the deployed contract
IDestructible(target).destroy();
delete contracts[salt];
}
}
// ATTACK SCENARIO:
// 1. Audit request: Deploy "safe" contract at addr X
// Auditors verify code at X β Clean! β
//
// 2. SELFDESTRUCT the contract at X!
// (Same address pe code wipe!)
//
// 3. CREATE2 same salt β Same address X!
// Deploy MALICIOUS code at same X!
//
// 4. Users trust "audited" address X
// Reality: Malicious code!
//
// Defense:
// β SELFDESTRUCT wale contracts trust mat karo!
// β Code hash verify karo har interaction pe!
// β Immutable contracts prefer karo!PART 6: CEI Pattern Sabse Important Security Pattern!
// CEI = Checks β Effects β Interactions
//
// Checks = All validations first
// Effects = State changes second
// Interactions = External calls LAST!
//
// Kyun? Reentrancy prevent karne ke liye!
// β οΈ WRONG ORDER (Vulnerable):
contract WrongOrder {
mapping(address => uint256) balances;
function withdraw(uint256 amount) external {
// Check β
require(balances[msg.sender] >= amount);
// Interaction β WRONG POSITION!
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok);
// Effect β TOO LATE!
balances[msg.sender] -= amount;
// Attacker re-entered before this!
// Balance never decremented in time!
}
}
// β
CORRECT ORDER (CEI):
contract CorrectCEI {
mapping(address => uint256) balances;
function withdraw(uint256 amount) external {
// βββ CHECKS βββββββββββββββββββββββ
require(amount > 0, "Zero amount!");
require(
balances[msg.sender] >= amount,
"Insufficient!"
);
// βββ EFFECTS ββββββββββββββββββββββ
// State update PEHLE!
balances[msg.sender] -= amount;
// Ab re-entry karo toh:
// balance = 0 β require fail!
// βββ INTERACTIONS βββββββββββββββββ
// External call BAAD MEIN!
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok, "Transfer failed!");
}
}// CEI = Checks β Effects β Interactions
//
// Checks = All validations first
// Effects = State changes second
// Interactions = External calls LAST!
//
// Kyun? Reentrancy prevent karne ke liye!
// β οΈ WRONG ORDER (Vulnerable):
contract WrongOrder {
mapping(address => uint256) balances;
function withdraw(uint256 amount) external {
// Check β
require(balances[msg.sender] >= amount);
// Interaction β WRONG POSITION!
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok);
// Effect β TOO LATE!
balances[msg.sender] -= amount;
// Attacker re-entered before this!
// Balance never decremented in time!
}
}
// β
CORRECT ORDER (CEI):
contract CorrectCEI {
mapping(address => uint256) balances;
function withdraw(uint256 amount) external {
// βββ CHECKS βββββββββββββββββββββββ
require(amount > 0, "Zero amount!");
require(
balances[msg.sender] >= amount,
"Insufficient!"
);
// βββ EFFECTS ββββββββββββββββββββββ
// State update PEHLE!
balances[msg.sender] -= amount;
// Ab re-entry karo toh:
// balance = 0 β require fail!
// βββ INTERACTIONS βββββββββββββββββ
// External call BAAD MEIN!
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok, "Transfer failed!");
}
}PART 7: ReentrancyGuard Defense in Depth!
// ReentrancyGuard = Mutex lock for contracts
// Ek function execute ho raha hai β
// Same function dobara call β REVERT!
// OpenZeppelin implementation:
abstract contract ReentrancyGuard {
// Status values:
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
// Check: Already entered?
require(
_status != ENTERED,
"ReentrancyGuard: reentrant call"
);
// Set: Entered!
_status = ENTERED;
_; // Function body
// Reset: Not entered!
_status = NOT_ENTERED;
}
}
// Usage:
contract SecureVault is ReentrancyGuard {
mapping(address => uint256) balances;
function withdraw(uint256 amount)
external
nonReentrant // β One call at a time!
{
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok);
}
}
// β οΈ ReentrancyGuard bypass:
// Cross-function reentrancy!
contract CrossReentrancy is ReentrancyGuard {
mapping(address => uint256) balances;
mapping(address => bool) public locked;
// nonReentrant protect karta hai
// Same function ko!
// Lekin DIFFERENT function?
function withdrawA(uint256 amount)
external
nonReentrant
{
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
// External call:
msg.sender.call{value: amount}("");
// Attacker re-enters withdrawB!
}
function withdrawB(uint256 amount)
external
nonReentrant
// β Different nonReentrant status!
// withdrawA ka ENTERED alag hai!
// withdrawB ka apna fresh status hai!
{
// β οΈ Balance already decremented in A
// But if logic here is different...
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
// Double withdrawal possible in some cases!
}
}
// β
Complete Defense:
// 1. CEI pattern use karo
// 2. nonReentrant lagao
// 3. Cross-function bhi consider karo!// ReentrancyGuard = Mutex lock for contracts
// Ek function execute ho raha hai β
// Same function dobara call β REVERT!
// OpenZeppelin implementation:
abstract contract ReentrancyGuard {
// Status values:
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
// Check: Already entered?
require(
_status != ENTERED,
"ReentrancyGuard: reentrant call"
);
// Set: Entered!
_status = ENTERED;
_; // Function body
// Reset: Not entered!
_status = NOT_ENTERED;
}
}
// Usage:
contract SecureVault is ReentrancyGuard {
mapping(address => uint256) balances;
function withdraw(uint256 amount)
external
nonReentrant // β One call at a time!
{
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok);
}
}
// β οΈ ReentrancyGuard bypass:
// Cross-function reentrancy!
contract CrossReentrancy is ReentrancyGuard {
mapping(address => uint256) balances;
mapping(address => bool) public locked;
// nonReentrant protect karta hai
// Same function ko!
// Lekin DIFFERENT function?
function withdrawA(uint256 amount)
external
nonReentrant
{
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
// External call:
msg.sender.call{value: amount}("");
// Attacker re-enters withdrawB!
}
function withdrawB(uint256 amount)
external
nonReentrant
// β Different nonReentrant status!
// withdrawA ka ENTERED alag hai!
// withdrawB ka apna fresh status hai!
{
// β οΈ Balance already decremented in A
// But if logic here is different...
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
// Double withdrawal possible in some cases!
}
}
// β
Complete Defense:
// 1. CEI pattern use karo
// 2. nonReentrant lagao
// 3. Cross-function bhi consider karo!PART 8: Gas Optimization Patterns!
contract GasOptimization {
// βββ Tip 1: uint256 > uint8 βββββββββββ
// Counterintuitive! uint8 MORE gas in some cases!
uint8 public small; // More gas! (padding needed)
uint256 public large; // Less gas in isolation!
// βββ Tip 2: Storage Packing βββββββββββ
// WRONG: 3 slots!
uint256 a; // slot 0
uint8 b; // slot 1 (wasted space!)
uint256 c; // slot 2
// CORRECT: 2 slots!
uint256 d; // slot 0
uint8 e; // slot 1 (packed!)
uint8 f; // slot 1 (same slot!)
uint8 g; // slot 1 (same slot!)
uint256 h; // slot 2
// βββ Tip 3: Memory vs Storage βββββββββ
function expensive(uint256[] storage arr)
internal view
returns (uint256 sum)
{
for (uint i = 0; i < arr.length; i++) {
sum += arr[i]; // SLOAD every time!
}
}
function cheap(uint256[] memory arr)
internal pure
returns (uint256 sum)
{
for (uint i = 0; i < arr.length; i++) {
sum += arr[i]; // MLOAD β cheaper!
}
}
// βββ Tip 4: Cache Array Length ββββββββ
// WRONG:
function wrongLoop(uint256[] memory arr)
external pure
{
for (uint i = 0; i < arr.length; i++) {
// arr.length har iteration compute!
}
}
// CORRECT:
function rightLoop(uint256[] memory arr)
external pure
{
uint256 len = arr.length; // Cache once!
for (uint i = 0; i < len; i++) {
// len = just a variable read
}
}
// βββ Tip 5: Immutable vs Constant βββββ
address public constant CONST_ADDR =
address(0x123); // Compile-time replacement
address public immutable IMMUT_ADDR;
// Deploy-time β cheaper than storage!
// βββ Tip 6: Custom Errors > Strings βββ
// WRONG: String error message
function wrongError() external pure {
require(false, "This is a long error!");
// String stored in bytecode = Size + Gas!
}
// CORRECT: Custom error
error MyError();
function rightError() external pure {
revert MyError();
// Just 4 bytes selector!
}
// βββ Tip 7: Events > Storage ββββββββββ
// Historical data ke liye events use karo
// Events = Logs = Cheaper than storage!
// Storage: 20,000 gas (new slot)
// Event: 375+ gas
// βββ Tip 8: Short-circuit evaluation ββ
function shortCircuit(
uint256 x,
address addr
) external view returns (bool) {
// Cheap check PEHLE!
return x > 0 &&
addr != address(0) &&
addr.code.length == 0;
// Expensive check BAAD MEIN!
}
}contract GasOptimization {
// βββ Tip 1: uint256 > uint8 βββββββββββ
// Counterintuitive! uint8 MORE gas in some cases!
uint8 public small; // More gas! (padding needed)
uint256 public large; // Less gas in isolation!
// βββ Tip 2: Storage Packing βββββββββββ
// WRONG: 3 slots!
uint256 a; // slot 0
uint8 b; // slot 1 (wasted space!)
uint256 c; // slot 2
// CORRECT: 2 slots!
uint256 d; // slot 0
uint8 e; // slot 1 (packed!)
uint8 f; // slot 1 (same slot!)
uint8 g; // slot 1 (same slot!)
uint256 h; // slot 2
// βββ Tip 3: Memory vs Storage βββββββββ
function expensive(uint256[] storage arr)
internal view
returns (uint256 sum)
{
for (uint i = 0; i < arr.length; i++) {
sum += arr[i]; // SLOAD every time!
}
}
function cheap(uint256[] memory arr)
internal pure
returns (uint256 sum)
{
for (uint i = 0; i < arr.length; i++) {
sum += arr[i]; // MLOAD β cheaper!
}
}
// βββ Tip 4: Cache Array Length ββββββββ
// WRONG:
function wrongLoop(uint256[] memory arr)
external pure
{
for (uint i = 0; i < arr.length; i++) {
// arr.length har iteration compute!
}
}
// CORRECT:
function rightLoop(uint256[] memory arr)
external pure
{
uint256 len = arr.length; // Cache once!
for (uint i = 0; i < len; i++) {
// len = just a variable read
}
}
// βββ Tip 5: Immutable vs Constant βββββ
address public constant CONST_ADDR =
address(0x123); // Compile-time replacement
address public immutable IMMUT_ADDR;
// Deploy-time β cheaper than storage!
// βββ Tip 6: Custom Errors > Strings βββ
// WRONG: String error message
function wrongError() external pure {
require(false, "This is a long error!");
// String stored in bytecode = Size + Gas!
}
// CORRECT: Custom error
error MyError();
function rightError() external pure {
revert MyError();
// Just 4 bytes selector!
}
// βββ Tip 7: Events > Storage ββββββββββ
// Historical data ke liye events use karo
// Events = Logs = Cheaper than storage!
// Storage: 20,000 gas (new slot)
// Event: 375+ gas
// βββ Tip 8: Short-circuit evaluation ββ
function shortCircuit(
uint256 x,
address addr
) external view returns (bool) {
// Cheap check PEHLE!
return x > 0 &&
addr != address(0) &&
addr.code.length == 0;
// Expensive check BAAD MEIN!
}
}PART 9: Complete Pattern Production Ready Contract!
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/// @title AdvancedVault
/// @notice All advanced patterns applied!
/// @dev CEI + ReentrancyGuard + Assembly optimize
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract AdvancedVault is
ReentrancyGuard,
Ownable
{
// βββ Storage (Optimized Packing) ββββββ
address public immutable token; // 20 bytes
bool public paused; // 1 byte β Packed!
uint96 public totalFees; // 12 bytes β Packed!
// Slot 0: token(20) + paused(1) + totalFees(12) = 33? No!
// Actually: address = 20 bytes, next slot
uint256 public totalDeposited;
uint256 public feeRate; // basis points (100 = 1%)
mapping(address => uint256) private _balances;
mapping(address => uint256) private _lastAction;
// βββ Events βββββββββββββββββββββββββββ
event Deposited(
address indexed user,
uint256 amount,
uint256 fee
);
event Withdrawn(
address indexed user,
uint256 amount
);
// βββ Custom Errors ββββββββββββββββββββ
error Paused();
error ZeroAmount();
error ZeroAddress();
error InsufficientBalance(
uint256 have,
uint256 need
);
error CooldownActive(uint256 remaining);
error InvalidFeeRate(uint256 rate);
// βββ Constants ββββββββββββββββββββββββ
uint256 public constant MAX_FEE_RATE = 1000; // 10%
uint256 public constant COOLDOWN = 1 minutes;
uint256 private constant FEE_DENOMINATOR = 10000;
// βββ Modifiers ββββββββββββββββββββββββ
modifier whenActive() {
if (paused) revert Paused();
_;
}
modifier cooldownPassed() {
uint256 lastAction = _lastAction[msg.sender];
if (lastAction != 0) {
uint256 elapsed = block.timestamp - lastAction;
if (elapsed < COOLDOWN) {
revert CooldownActive(COOLDOWN - elapsed);
}
}
_;
}
// βββ Constructor ββββββββββββββββββββββ
constructor(
address _token,
uint256 _feeRate
) Ownable(msg.sender) {
if (_token == address(0)) revert ZeroAddress();
if (_feeRate > MAX_FEE_RATE)
revert InvalidFeeRate(_feeRate);
token = _token;
feeRate = _feeRate;
}
// βββ Core Functions βββββββββββββββββββ
function deposit(uint256 amount)
external
whenActive
nonReentrant
cooldownPassed
{
// βββ CHECKS βββββββββββββββββββββββ
if (amount == 0) revert ZeroAmount();
// βββ EFFECTS ββββββββββββββββββββββ
uint256 fee = (amount * feeRate) /
FEE_DENOMINATOR;
uint256 netAmount = amount - fee;
_balances[msg.sender] += netAmount;
totalDeposited += netAmount;
totalFees += uint96(fee);
_lastAction[msg.sender] = block.timestamp;
// βββ INTERACTIONS βββββββββββββββββ
_safeTransferFrom(
token,
msg.sender,
address(this),
amount
);
emit Deposited(msg.sender, netAmount, fee);
}
function withdraw(uint256 amount)
external
whenActive
nonReentrant
{
// βββ CHECKS βββββββββββββββββββββββ
if (amount == 0) revert ZeroAmount();
uint256 bal = _balances[msg.sender];
if (bal < amount)
revert InsufficientBalance(bal, amount);
// βββ EFFECTS ββββββββββββββββββββββ
_balances[msg.sender] -= amount;
totalDeposited -= amount;
// βββ INTERACTIONS βββββββββββββββββ
_safeTransfer(token, msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
// βββ Internal Helpers βββββββββββββββββ
function _safeTransfer(
address _token,
address to,
uint256 amount
) internal {
(bool ok, bytes memory data) =
_token.call(
abi.encodeWithSignature(
"transfer(address,uint256)",
to,
amount
)
);
require(
ok && (data.length == 0 ||
abi.decode(data, (bool))),
"Transfer failed!"
);
}
function _safeTransferFrom(
address _token,
address from,
address to,
uint256 amount
) internal {
(bool ok, bytes memory data) =
_token.call(
abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
from,
to,
amount
)
);
require(
ok && (data.length == 0 ||
abi.decode(data, (bool))),
"TransferFrom failed!"
);
}
// βββ View Functions βββββββββββββββββββ
function balanceOf(address user)
external view returns (uint256)
{
return _balances[user];
}
// βββ Admin ββββββββββββββββββββββββββββ
function setPause(bool _paused)
external onlyOwner
{
paused = _paused;
}
function setFeeRate(uint256 _feeRate)
external onlyOwner
{
if (_feeRate > MAX_FEE_RATE)
revert InvalidFeeRate(_feeRate);
feeRate = _feeRate;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/// @title AdvancedVault
/// @notice All advanced patterns applied!
/// @dev CEI + ReentrancyGuard + Assembly optimize
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract AdvancedVault is
ReentrancyGuard,
Ownable
{
// βββ Storage (Optimized Packing) ββββββ
address public immutable token; // 20 bytes
bool public paused; // 1 byte β Packed!
uint96 public totalFees; // 12 bytes β Packed!
// Slot 0: token(20) + paused(1) + totalFees(12) = 33? No!
// Actually: address = 20 bytes, next slot
uint256 public totalDeposited;
uint256 public feeRate; // basis points (100 = 1%)
mapping(address => uint256) private _balances;
mapping(address => uint256) private _lastAction;
// βββ Events βββββββββββββββββββββββββββ
event Deposited(
address indexed user,
uint256 amount,
uint256 fee
);
event Withdrawn(
address indexed user,
uint256 amount
);
// βββ Custom Errors ββββββββββββββββββββ
error Paused();
error ZeroAmount();
error ZeroAddress();
error InsufficientBalance(
uint256 have,
uint256 need
);
error CooldownActive(uint256 remaining);
error InvalidFeeRate(uint256 rate);
// βββ Constants ββββββββββββββββββββββββ
uint256 public constant MAX_FEE_RATE = 1000; // 10%
uint256 public constant COOLDOWN = 1 minutes;
uint256 private constant FEE_DENOMINATOR = 10000;
// βββ Modifiers ββββββββββββββββββββββββ
modifier whenActive() {
if (paused) revert Paused();
_;
}
modifier cooldownPassed() {
uint256 lastAction = _lastAction[msg.sender];
if (lastAction != 0) {
uint256 elapsed = block.timestamp - lastAction;
if (elapsed < COOLDOWN) {
revert CooldownActive(COOLDOWN - elapsed);
}
}
_;
}
// βββ Constructor ββββββββββββββββββββββ
constructor(
address _token,
uint256 _feeRate
) Ownable(msg.sender) {
if (_token == address(0)) revert ZeroAddress();
if (_feeRate > MAX_FEE_RATE)
revert InvalidFeeRate(_feeRate);
token = _token;
feeRate = _feeRate;
}
// βββ Core Functions βββββββββββββββββββ
function deposit(uint256 amount)
external
whenActive
nonReentrant
cooldownPassed
{
// βββ CHECKS βββββββββββββββββββββββ
if (amount == 0) revert ZeroAmount();
// βββ EFFECTS ββββββββββββββββββββββ
uint256 fee = (amount * feeRate) /
FEE_DENOMINATOR;
uint256 netAmount = amount - fee;
_balances[msg.sender] += netAmount;
totalDeposited += netAmount;
totalFees += uint96(fee);
_lastAction[msg.sender] = block.timestamp;
// βββ INTERACTIONS βββββββββββββββββ
_safeTransferFrom(
token,
msg.sender,
address(this),
amount
);
emit Deposited(msg.sender, netAmount, fee);
}
function withdraw(uint256 amount)
external
whenActive
nonReentrant
{
// βββ CHECKS βββββββββββββββββββββββ
if (amount == 0) revert ZeroAmount();
uint256 bal = _balances[msg.sender];
if (bal < amount)
revert InsufficientBalance(bal, amount);
// βββ EFFECTS ββββββββββββββββββββββ
_balances[msg.sender] -= amount;
totalDeposited -= amount;
// βββ INTERACTIONS βββββββββββββββββ
_safeTransfer(token, msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
// βββ Internal Helpers βββββββββββββββββ
function _safeTransfer(
address _token,
address to,
uint256 amount
) internal {
(bool ok, bytes memory data) =
_token.call(
abi.encodeWithSignature(
"transfer(address,uint256)",
to,
amount
)
);
require(
ok && (data.length == 0 ||
abi.decode(data, (bool))),
"Transfer failed!"
);
}
function _safeTransferFrom(
address _token,
address from,
address to,
uint256 amount
) internal {
(bool ok, bytes memory data) =
_token.call(
abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
from,
to,
amount
)
);
require(
ok && (data.length == 0 ||
abi.decode(data, (bool))),
"TransferFrom failed!"
);
}
// βββ View Functions βββββββββββββββββββ
function balanceOf(address user)
external view returns (uint256)
{
return _balances[user];
}
// βββ Admin ββββββββββββββββββββββββββββ
function setPause(bool _paused)
external onlyOwner
{
paused = _paused;
}
function setFeeRate(uint256 _feeRate)
external onlyOwner
{
if (_feeRate > MAX_FEE_RATE)
revert InvalidFeeRate(_feeRate);
feeRate = _feeRate;
}
}PART 10: Final Bug Hunt Challenge!
// βββ 6 bugs dhundho! ββββββββββββββββββββββ
contract AdvancedBuggy {
mapping(address => uint256) balances;
address public implementation;
bool private locked;
// Bug #1:
receive() external {
balances[msg.sender] += msg.value;
}
// Bug #2:
function delegateTo(bytes memory data)
external
{
implementation.delegatecall(data);
}
// Bug #3:
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok);
balances[msg.sender] -= amount;
}
// Bug #4:
function getHash(
address user,
uint256 amount
) public pure returns (bytes32) {
return keccak256(
abi.encodePacked(user, amount)
);
}
// Bug #5:
modifier noReentrant() {
require(!locked, "Locked!");
locked = true;
_;
}
// Bug #6:
function create2Deploy(
bytes32 salt,
bytes memory code
) external returns (address addr) {
assembly {
addr := create2(
0,
add(code, 0x20),
mload(code),
salt
)
}
}
}
// βββ ANSWERS ββββββββββββββββββββββββββββββ
// Bug #1: receive() not payable!
// ETH receive karna hai β payable zaroori!
// receive() external payable { ... }
// Bug #2: Arbitrary delegatecall!
// Koi bhi implementation set kar sakta hai
// + Koi bhi data pass kar sakta hai
// = Storage corruption + owner takeover!
// Fix: onlyOwner + whitelist allowed calls!
// Bug #3: CEI violation!
// External call pehle β State update baad
// Reentrancy attack possible!
// Fix: balances -= amount PEHLE!
// Bug #4: encodePacked collision!
// Dynamic types ke saath collision possible
// abi.encode() use karo!
// Plus: No chainId, no nonce = Replay attack!
// Bug #5: nonReentrant lock reset missing!
// locked = true set kiya
// Lekin function ke baad locked = false
// KABHI NAHI HOGA!
// Contract permanently locked after first call!
// Fix: locked = false; after _;
// Bug #6: create2Deploy access control missing!
// Koi bhi arbitrary code deploy kar sakta hai
// Factory contract ke naam pe!
// Trust issue + Potential attack surface
// Fix: onlyOwner modifier!// βββ 6 bugs dhundho! ββββββββββββββββββββββ
contract AdvancedBuggy {
mapping(address => uint256) balances;
address public implementation;
bool private locked;
// Bug #1:
receive() external {
balances[msg.sender] += msg.value;
}
// Bug #2:
function delegateTo(bytes memory data)
external
{
implementation.delegatecall(data);
}
// Bug #3:
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool ok,) = msg.sender.call{
value: amount
}("");
require(ok);
balances[msg.sender] -= amount;
}
// Bug #4:
function getHash(
address user,
uint256 amount
) public pure returns (bytes32) {
return keccak256(
abi.encodePacked(user, amount)
);
}
// Bug #5:
modifier noReentrant() {
require(!locked, "Locked!");
locked = true;
_;
}
// Bug #6:
function create2Deploy(
bytes32 salt,
bytes memory code
) external returns (address addr) {
assembly {
addr := create2(
0,
add(code, 0x20),
mload(code),
salt
)
}
}
}
// βββ ANSWERS ββββββββββββββββββββββββββββββ
// Bug #1: receive() not payable!
// ETH receive karna hai β payable zaroori!
// receive() external payable { ... }
// Bug #2: Arbitrary delegatecall!
// Koi bhi implementation set kar sakta hai
// + Koi bhi data pass kar sakta hai
// = Storage corruption + owner takeover!
// Fix: onlyOwner + whitelist allowed calls!
// Bug #3: CEI violation!
// External call pehle β State update baad
// Reentrancy attack possible!
// Fix: balances -= amount PEHLE!
// Bug #4: encodePacked collision!
// Dynamic types ke saath collision possible
// abi.encode() use karo!
// Plus: No chainId, no nonce = Replay attack!
// Bug #5: nonReentrant lock reset missing!
// locked = true set kiya
// Lekin function ke baad locked = false
// KABHI NAHI HOGA!
// Contract permanently locked after first call!
// Fix: locked = false; after _;
// Bug #6: create2Deploy access control missing!
// Koi bhi arbitrary code deploy kar sakta hai
// Factory contract ke naam pe!
// Trust issue + Potential attack surface
// Fix: onlyOwner modifier!Quick Revision
π₯ receive() = Pure ETH receive (no data)
MUST be payable!
π₯ fallback() = Fallback (data ya no receive)
Reentrancy entry point! β οΈ
π .call() = ETH + function, return check karo!
π .delegatecall= Code borrow, caller storage β οΈ
π .staticcall() = Read only, safe!
βοΈ Assembly = Direct EVM access
isContract bypass possible! β οΈ
Storage arbitrary write! β οΈ
π‘ abi.encode = Padded, safe, collision-free β
π‘ encodePacked = Compact, collision possible β οΈ
Dynamic types ke saath dangerous!
π CREATE = Nonce-based address
π CREATE2 = Salt-based deterministic address
Metamorphic contract attack! β οΈ
π‘οΈ CEI Pattern = Checks β Effects β Interactions
MOST IMPORTANT security pattern!
π ReentrancyGuard= Mutex lock
Cross-function bypass possible! β οΈ
β½ Gas Tips:
Storage pack karo
Custom errors use karo
Array length cache karo
Immutable/Constant prefer karoπ₯ receive() = Pure ETH receive (no data)
MUST be payable!
π₯ fallback() = Fallback (data ya no receive)
Reentrancy entry point! β οΈ
π .call() = ETH + function, return check karo!
π .delegatecall= Code borrow, caller storage β οΈ
π .staticcall() = Read only, safe!
βοΈ Assembly = Direct EVM access
isContract bypass possible! β οΈ
Storage arbitrary write! β οΈ
π‘ abi.encode = Padded, safe, collision-free β
π‘ encodePacked = Compact, collision possible β οΈ
Dynamic types ke saath dangerous!
π CREATE = Nonce-based address
π CREATE2 = Salt-based deterministic address
Metamorphic contract attack! β οΈ
π‘οΈ CEI Pattern = Checks β Effects β Interactions
MOST IMPORTANT security pattern!
π ReentrancyGuard= Mutex lock
Cross-function bypass possible! β οΈ
β½ Gas Tips:
Storage pack karo
Custom errors use karo
Array length cache karo
Immutable/Constant prefer karoMeri Baatβ¦
Teen articles mein Solidity cover kiΰ₯€ Ab tum jaante ho:
Article #5: Variables, Functions, Types
Article #6: OOP, Inheritance, Interfaces
Article #7: Advanced Patterns, Assembly, CEI
Yeh sab combine karo:
Ek Solidity contract open karo β
β Variables dekho (overflow possible?)
β Functions dekho (modifier missing?)
β Inheritance chain dekho (bypass?)
β receive/fallback dekho (reentrancy?)
β Low-level calls dekho (CEI?)
β Assembly dekho (storage write?)
β ABI encoding dekho (collision?)
Yeh hai ek BASIC audit checklist!
Real auditors yahi karte hain β
Sirf zyada systematically!
Tumne 7 articles mein woh seekha
jo bahut log 6 months mein seekhte hain!
Agle phase: TOOLS!
Foundry se exploit likhenge β
Real contracts pe test karenge β
Automated bugs dhundhenge!
Taiyar ho? πArticle #5: Variables, Functions, Types
Article #6: OOP, Inheritance, Interfaces
Article #7: Advanced Patterns, Assembly, CEI
Yeh sab combine karo:
Ek Solidity contract open karo β
β Variables dekho (overflow possible?)
β Functions dekho (modifier missing?)
β Inheritance chain dekho (bypass?)
β receive/fallback dekho (reentrancy?)
β Low-level calls dekho (CEI?)
β Assembly dekho (storage write?)
β ABI encoding dekho (collision?)
Yeh hai ek BASIC audit checklist!
Real auditors yahi karte hain β
Sirf zyada systematically!
Tumne 7 articles mein woh seekha
jo bahut log 6 months mein seekhte hain!
Agle phase: TOOLS!
Foundry se exploit likhenge β
Real contracts pe test karenge β
Automated bugs dhundhenge!
Taiyar ho? πArticle #8 mein: Smart Contract Lifecycle Deploy se Exploit tak poora journey! β‘
HackerMD_ Web3 Security Researcher_ GitHub: BotGJ16 | Medium: @HackerMD
Previous: Article #6 Solidity OOP & Inheritance Next: Article #8 Smart Contract Lifecycle
#Solidity #AdvancedPatterns #CEI #Reentrancy #Web3Security #BugBounty #Hinglish #HackerMD