[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-4-security-primitives-access-control-reentrancy":3},{"article":4,"author":55},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":7,"meta_description":16,"focus_keyword":17,"og_image":18,"canonical_url":18,"robots_meta":19,"created_at":15,"updated_at":15,"tags":20,"category_name":34,"related_articles":35},"d0000000-0000-0000-0000-000000000104","a0000000-0000-0000-0000-000000000002","Deep EVM #4: Security Primitives — msg.sender, Access Control, and Reentrancy","deep-evm-4-security-primitives-access-control-reentrancy","How the EVM's execution model creates security vulnerabilities and how to prevent them: msg.sender vs tx.origin, reentrancy attacks, delegatecall risks, and access control patterns.","## Security at the EVM Level\n\nSmart contract security is not about adding checks on top of working code — it is about understanding how the EVM's execution model creates attack surfaces. Every external call is a potential reentry point. Every delegatecall is a storage hijack vector. Every unchecked return value is a silent failure.\n\nIn this article, we go beyond the Solidity surface to understand the EVM mechanics behind the most devastating vulnerabilities.\n\n## msg.sender vs tx.origin\n\nThese two values look similar but have fundamentally different security properties.\n\n- **msg.sender** (CALLER opcode): The immediate caller of the current execution context. Changes with each CALL.\n- **tx.origin** (ORIGIN opcode): The externally owned account (EOA) that initiated the transaction. Never changes, regardless of call depth.\n\n```\n\u002F\u002F Transaction flow: EOA -> ContractA -> ContractB\n\u002F\u002F\n\u002F\u002F Inside ContractA:\n\u002F\u002F   msg.sender = EOA address\n\u002F\u002F   tx.origin  = EOA address\n\u002F\u002F\n\u002F\u002F Inside ContractB (called by ContractA):\n\u002F\u002F   msg.sender = ContractA address\n\u002F\u002F   tx.origin  = EOA address      \u003C-- same!\n```\n\n### The tx.origin Attack\n\nUsing `tx.origin` for authentication is a well-known vulnerability:\n\n```solidity\n\u002F\u002F VULNERABLE: uses tx.origin for auth\ncontract Wallet {\n    address public owner;\n\n    function transfer(address to, uint256 amount) external {\n        require(tx.origin == owner, \"Not owner\");  \u002F\u002F WRONG!\n        payable(to).transfer(amount);\n    }\n}\n\n\u002F\u002F Attacker deploys this contract and tricks the owner into calling it:\ncontract Attack {\n    Wallet wallet;\n\n    function attack() external {\n        \u002F\u002F When owner calls this function:\n        \u002F\u002F tx.origin = owner (the EOA who signed the transaction)\n        \u002F\u002F msg.sender = this contract\n        wallet.transfer(attacker, wallet.balance);\n        \u002F\u002F tx.origin check passes because owner initiated the tx!\n    }\n}\n```\n\nThe fix is simple: **always use msg.sender**, never tx.origin for authentication. In the context of account abstraction (ERC-4337), tx.origin may even be a bundler address, not the user at all.\n\n## Reentrancy: The DAO Attack Pattern\n\nReentrancy is the most infamous smart contract vulnerability — it caused the $60M DAO hack in 2016 and has been exploited repeatedly since. The pattern is simple: an external call returns control to the attacker before state updates are complete.\n\n### How It Works at the EVM Level\n\nWhen a contract executes a CALL opcode, execution transfers to the callee. The callee has its own stack and memory, but crucially, the caller's storage has not yet been updated if the SSTORE comes after the CALL.\n\n```solidity\n\u002F\u002F VULNERABLE: state update after external call\ncontract VulnerableVault {\n    mapping(address => uint256) public balances;\n\n    function withdraw() external {\n        uint256 amount = balances[msg.sender];\n        require(amount > 0, \"No balance\");\n\n        \u002F\u002F DANGER: external call before state update\n        (bool success, ) = msg.sender.call{value: amount}(\"\");\n        require(success, \"Transfer failed\");\n\n        \u002F\u002F This line executes AFTER the external call returns\n        \u002F\u002F But the attacker can re-enter before reaching here!\n        balances[msg.sender] = 0;\n    }\n}\n```\n\nThe attacker's contract receives ETH via the `call`, which triggers its `receive()` function. Inside `receive()`, the attacker calls `withdraw()` again. Since `balances[msg.sender]` has not been set to 0 yet, the check passes and the attacker drains the contract.\n\n```solidity\ncontract Attacker {\n    VulnerableVault vault;\n\n    function attack() external payable {\n        vault.deposit{value: 1 ether}();\n        vault.withdraw();\n    }\n\n    receive() external payable {\n        if (address(vault).balance >= 1 ether) {\n            vault.withdraw();  \u002F\u002F Re-enter!\n        }\n    }\n}\n```\n\n### The Checks-Effects-Interactions Pattern\n\nThe standard defense is to order operations correctly:\n\n1. **Checks** — Validate all conditions (require statements)\n2. **Effects** — Update all state variables\n3. **Interactions** — Make external calls last\n\n```solidity\nfunction withdraw() external {\n    uint256 amount = balances[msg.sender];  \u002F\u002F Check\n    require(amount > 0, \"No balance\");       \u002F\u002F Check\n    balances[msg.sender] = 0;                \u002F\u002F Effect (BEFORE call)\n    (bool success, ) = msg.sender.call{value: amount}(\"\");  \u002F\u002F Interaction\n    require(success, \"Transfer failed\");\n}\n```\n\nNow if the attacker re-enters, `balances[msg.sender]` is already 0, so the require fails.\n\n### Reentrancy Guards\n\nFor complex functions where CEI ordering is difficult, use a reentrancy guard:\n\n```solidity\n\u002F\u002F OpenZeppelin-style reentrancy guard\nabstract contract ReentrancyGuard {\n    uint256 private constant NOT_ENTERED = 1;\n    uint256 private constant ENTERED = 2;\n    uint256 private _status = NOT_ENTERED;\n\n    modifier nonReentrant() {\n        require(_status != ENTERED, \"ReentrancyGuard: reentrant call\");\n        _status = ENTERED;\n        _;\n        _status = NOT_ENTERED;\n    }\n}\n```\n\nNotice that `_status` uses values 1 and 2, never 0. This is a gas optimization: changing a nonzero slot to a different nonzero value costs 2900 gas, while changing from zero to nonzero costs 20000 gas.\n\n### Transient Storage Reentrancy Guards (EIP-1153)\n\nWith transient storage, reentrancy guards become dramatically cheaper:\n\n```solidity\nmodifier nonReentrant() {\n    assembly {\n        if tload(0) { revert(0, 0) }\n        tstore(0, 1)\n    }\n    _;\n    assembly {\n        tstore(0, 0)\n    }\n}\n\u002F\u002F Cost: ~200 gas vs ~5000 gas for storage-based guard\n```\n\n### Cross-Function Reentrancy\n\nReentrancy is not limited to re-entering the same function. An attacker can call a different function on the same contract that reads stale state:\n\n```solidity\ncontract CrossFunctionVuln {\n    mapping(address => uint256) public balances;\n\n    function withdraw() external {\n        uint256 amount = balances[msg.sender];\n        (bool success, ) = msg.sender.call{value: amount}(\"\");\n        require(success);\n        balances[msg.sender] = 0;\n    }\n\n    function transfer(address to, uint256 amount) external {\n        \u002F\u002F Attacker re-enters HERE during withdraw\n        \u002F\u002F balances[msg.sender] still has the old value!\n        require(balances[msg.sender] >= amount);\n        balances[msg.sender] -= amount;\n        balances[to] += amount;\n    }\n}\n```\n\n## DELEGATECALL: Power and Peril\n\nDELEGATECALL executes another contract's code in the context of the calling contract. The callee's code reads and writes the caller's storage, and msg.sender\u002Fmsg.value are preserved from the original call.\n\nThis is the foundation of proxy patterns (ERC-1967, UUPS, Transparent Proxy) that enable upgradeable contracts. But it is also incredibly dangerous.\n\n### Storage Collision Attack\n\n```solidity\n\u002F\u002F Proxy contract (simplified)\ncontract Proxy {\n    address public implementation;  \u002F\u002F slot 0\n    address public admin;           \u002F\u002F slot 1\n\n    fallback() external payable {\n        (bool s, ) = implementation.delegatecall(msg.data);\n        require(s);\n    }\n}\n\n\u002F\u002F Implementation contract\ncontract Implementation {\n    uint256 public value;  \u002F\u002F slot 0 -- COLLIDES with implementation address!\n\n    function setValue(uint256 _v) external {\n        value = _v;  \u002F\u002F This overwrites the proxy's implementation address!\n    }\n}\n```\n\nWhen `setValue` executes via DELEGATECALL, `value` writes to slot 0 of the Proxy — which is the `implementation` address. The attacker can set the implementation to their own contract and take over the proxy.\n\nThe fix: use unstructured storage with pseudo-random slots (ERC-1967 standard):\n\n```solidity\nbytes32 constant IMPLEMENTATION_SLOT = bytes32(uint256(\n    keccak256(\"eip1967.proxy.implementation\")) - 1\n);\n\u002F\u002F = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc\n```\n\n### DELEGATECALL and msg.sender Preservation\n\nIn a DELEGATECALL chain:\n```\nUser -> Proxy (DELEGATECALL) -> Implementation\n\u002F\u002F Inside Implementation:\n\u002F\u002F   msg.sender = User (preserved!)\n\u002F\u002F   address(this) = Proxy (executing in Proxy's context)\n\u002F\u002F   storage reads\u002Fwrites = Proxy's storage\n```\n\nThis means the implementation contract must never assume `address(this)` is its own deployment address. Any selfdestruct, balance check, or address comparison will reference the proxy.\n\n## Access Control Patterns\n\n### Ownable\n\nThe simplest pattern: a single `owner` address with privileged access.\n\n```solidity\ncontract Ownable {\n    address public owner;\n    error NotOwner();\n\n    modifier onlyOwner() {\n        if (msg.sender != owner) revert NotOwner();\n        _;\n    }\n}\n```\n\nLimitations: single point of failure, no granularity.\n\n### Role-Based Access Control (RBAC)\n\nOpenZeppelin's AccessControl provides multi-role management:\n\n```solidity\nbytes32 public constant MINTER_ROLE = keccak256(\"MINTER_ROLE\");\nbytes32 public constant PAUSER_ROLE = keccak256(\"PAUSER_ROLE\");\n\nfunction mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {\n    _mint(to, amount);\n}\n```\n\nAt the EVM level, role checks are `mapping(bytes32 => mapping(address => bool))` storage lookups — two keccak256 hash computations and an SLOAD per check.\n\n### Multisig and Timelock\n\nFor high-value operations, combine RBAC with:\n- **Multisig** — Require M-of-N signatures (Gnosis Safe pattern)\n- **Timelock** — Delay execution to allow community review (Compound's Timelock pattern)\n\n## Return Value Checks\n\nThe CALL opcode pushes 1 (success) or 0 (failure) onto the stack. If you do not check this return value, a failed call is silently ignored:\n\n```solidity\n\u002F\u002F DANGEROUS: unchecked low-level call\n(bool success, ) = target.call(data);\n\u002F\u002F If success is false and you don't check, execution continues!\n\n\u002F\u002F SAFE: always check\n(bool success, ) = target.call(data);\nrequire(success, \"Call failed\");\n```\n\nThis also applies to ERC-20 tokens. Some tokens (like USDT) do not return a boolean on `transfer()`. Use OpenZeppelin's `SafeERC20` or check return data length:\n\n```solidity\n(bool success, bytes memory data) = token.call(\n    abi.encodeWithSelector(IERC20.transfer.selector, to, amount)\n);\nrequire(success && (data.length == 0 || abi.decode(data, (bool))));\n```\n\n## Integer Overflow and Underflow\n\nSolidity 0.8+ includes automatic overflow\u002Funderflow checks. Before 0.8, and in raw Yul\u002Fassembly, arithmetic silently wraps:\n\n```yul\n\u002F\u002F In Yul, there are no overflow checks!\nlet a := sub(0, 1)  \u002F\u002F a = 2^256 - 1 (max uint256)\nlet b := add(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1)  \u002F\u002F b = 0\n```\n\nIf you write Yul for gas optimization (as many MEV bots do), you must manually validate arithmetic bounds or prove that overflow is impossible for your input domain.\n\n## Practical Security Checklist for MEV Bots\n\n1. **Never use tx.origin** — Use msg.sender or signature-based authentication.\n2. **CEI pattern everywhere** — Update state before making external calls.\n3. **Check all return values** — Especially for low-level calls and non-standard tokens.\n4. **Validate DELEGATECALL targets** — Only delegate to trusted, audited implementations.\n5. **Use reentrancy guards** — Especially for functions that transfer ETH or tokens.\n6. **Validate calldata** — Ensure function selectors and parameters are within expected ranges.\n7. **Protect against sandwich attacks** — Your bot is a smart contract too; it can be attacked.\n8. **Use access control** — Only your EOA\u002Fmultisig should be able to call profit-extraction functions.\n9. **Implement circuit breakers** — Pause functionality if unexpected behavior is detected.\n10. **Audit Yul\u002FHuff code extra carefully** — No compiler safety nets in raw assembly.\n\n## Conclusion\n\nSecurity in the EVM is not a layer you add — it is a property of how you structure your state transitions relative to external calls. The checks-effects-interactions pattern, reentrancy guards, and careful delegatecall usage are not best practices — they are survival requirements. In the next article, we leave Solidity behind and enter the world of Yul, Solidity's inline assembly language.","\u003Ch2 id=\"security-at-the-evm-level\">Security at the EVM Level\u003C\u002Fh2>\n\u003Cp>Smart contract security is not about adding checks on top of working code — it is about understanding how the EVM’s execution model creates attack surfaces. Every external call is a potential reentry point. Every delegatecall is a storage hijack vector. Every unchecked return value is a silent failure.\u003C\u002Fp>\n\u003Cp>In this article, we go beyond the Solidity surface to understand the EVM mechanics behind the most devastating vulnerabilities.\u003C\u002Fp>\n\u003Ch2 id=\"msg-sender-vs-tx-origin\">msg.sender vs tx.origin\u003C\u002Fh2>\n\u003Cp>These two values look similar but have fundamentally different security properties.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>msg.sender\u003C\u002Fstrong> (CALLER opcode): The immediate caller of the current execution context. Changes with each CALL.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>tx.origin\u003C\u002Fstrong> (ORIGIN opcode): The externally owned account (EOA) that initiated the transaction. Never changes, regardless of call depth.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>\u002F\u002F Transaction flow: EOA -&gt; ContractA -&gt; ContractB\n\u002F\u002F\n\u002F\u002F Inside ContractA:\n\u002F\u002F   msg.sender = EOA address\n\u002F\u002F   tx.origin  = EOA address\n\u002F\u002F\n\u002F\u002F Inside ContractB (called by ContractA):\n\u002F\u002F   msg.sender = ContractA address\n\u002F\u002F   tx.origin  = EOA address      &lt;-- same!\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>The tx.origin Attack\u003C\u002Fh3>\n\u003Cp>Using \u003Ccode>tx.origin\u003C\u002Fcode> for authentication is a well-known vulnerability:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F VULNERABLE: uses tx.origin for auth\ncontract Wallet {\n    address public owner;\n\n    function transfer(address to, uint256 amount) external {\n        require(tx.origin == owner, \"Not owner\");  \u002F\u002F WRONG!\n        payable(to).transfer(amount);\n    }\n}\n\n\u002F\u002F Attacker deploys this contract and tricks the owner into calling it:\ncontract Attack {\n    Wallet wallet;\n\n    function attack() external {\n        \u002F\u002F When owner calls this function:\n        \u002F\u002F tx.origin = owner (the EOA who signed the transaction)\n        \u002F\u002F msg.sender = this contract\n        wallet.transfer(attacker, wallet.balance);\n        \u002F\u002F tx.origin check passes because owner initiated the tx!\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The fix is simple: \u003Cstrong>always use msg.sender\u003C\u002Fstrong>, never tx.origin for authentication. In the context of account abstraction (ERC-4337), tx.origin may even be a bundler address, not the user at all.\u003C\u002Fp>\n\u003Ch2 id=\"reentrancy-the-dao-attack-pattern\">Reentrancy: The DAO Attack Pattern\u003C\u002Fh2>\n\u003Cp>Reentrancy is the most infamous smart contract vulnerability — it caused the $60M DAO hack in 2016 and has been exploited repeatedly since. The pattern is simple: an external call returns control to the attacker before state updates are complete.\u003C\u002Fp>\n\u003Ch3>How It Works at the EVM Level\u003C\u002Fh3>\n\u003Cp>When a contract executes a CALL opcode, execution transfers to the callee. The callee has its own stack and memory, but crucially, the caller’s storage has not yet been updated if the SSTORE comes after the CALL.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F VULNERABLE: state update after external call\ncontract VulnerableVault {\n    mapping(address =&gt; uint256) public balances;\n\n    function withdraw() external {\n        uint256 amount = balances[msg.sender];\n        require(amount &gt; 0, \"No balance\");\n\n        \u002F\u002F DANGER: external call before state update\n        (bool success, ) = msg.sender.call{value: amount}(\"\");\n        require(success, \"Transfer failed\");\n\n        \u002F\u002F This line executes AFTER the external call returns\n        \u002F\u002F But the attacker can re-enter before reaching here!\n        balances[msg.sender] = 0;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The attacker’s contract receives ETH via the \u003Ccode>call\u003C\u002Fcode>, which triggers its \u003Ccode>receive()\u003C\u002Fcode> function. Inside \u003Ccode>receive()\u003C\u002Fcode>, the attacker calls \u003Ccode>withdraw()\u003C\u002Fcode> again. Since \u003Ccode>balances[msg.sender]\u003C\u002Fcode> has not been set to 0 yet, the check passes and the attacker drains the contract.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">contract Attacker {\n    VulnerableVault vault;\n\n    function attack() external payable {\n        vault.deposit{value: 1 ether}();\n        vault.withdraw();\n    }\n\n    receive() external payable {\n        if (address(vault).balance &gt;= 1 ether) {\n            vault.withdraw();  \u002F\u002F Re-enter!\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>The Checks-Effects-Interactions Pattern\u003C\u002Fh3>\n\u003Cp>The standard defense is to order operations correctly:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Checks\u003C\u002Fstrong> — Validate all conditions (require statements)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Effects\u003C\u002Fstrong> — Update all state variables\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Interactions\u003C\u002Fstrong> — Make external calls last\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode class=\"language-solidity\">function withdraw() external {\n    uint256 amount = balances[msg.sender];  \u002F\u002F Check\n    require(amount &gt; 0, \"No balance\");       \u002F\u002F Check\n    balances[msg.sender] = 0;                \u002F\u002F Effect (BEFORE call)\n    (bool success, ) = msg.sender.call{value: amount}(\"\");  \u002F\u002F Interaction\n    require(success, \"Transfer failed\");\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Now if the attacker re-enters, \u003Ccode>balances[msg.sender]\u003C\u002Fcode> is already 0, so the require fails.\u003C\u002Fp>\n\u003Ch3>Reentrancy Guards\u003C\u002Fh3>\n\u003Cp>For complex functions where CEI ordering is difficult, use a reentrancy guard:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F OpenZeppelin-style reentrancy guard\nabstract contract ReentrancyGuard {\n    uint256 private constant NOT_ENTERED = 1;\n    uint256 private constant ENTERED = 2;\n    uint256 private _status = NOT_ENTERED;\n\n    modifier nonReentrant() {\n        require(_status != ENTERED, \"ReentrancyGuard: reentrant call\");\n        _status = ENTERED;\n        _;\n        _status = NOT_ENTERED;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Notice that \u003Ccode>_status\u003C\u002Fcode> uses values 1 and 2, never 0. This is a gas optimization: changing a nonzero slot to a different nonzero value costs 2900 gas, while changing from zero to nonzero costs 20000 gas.\u003C\u002Fp>\n\u003Ch3>Transient Storage Reentrancy Guards (EIP-1153)\u003C\u002Fh3>\n\u003Cp>With transient storage, reentrancy guards become dramatically cheaper:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">modifier nonReentrant() {\n    assembly {\n        if tload(0) { revert(0, 0) }\n        tstore(0, 1)\n    }\n    _;\n    assembly {\n        tstore(0, 0)\n    }\n}\n\u002F\u002F Cost: ~200 gas vs ~5000 gas for storage-based guard\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Cross-Function Reentrancy\u003C\u002Fh3>\n\u003Cp>Reentrancy is not limited to re-entering the same function. An attacker can call a different function on the same contract that reads stale state:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">contract CrossFunctionVuln {\n    mapping(address =&gt; uint256) public balances;\n\n    function withdraw() external {\n        uint256 amount = balances[msg.sender];\n        (bool success, ) = msg.sender.call{value: amount}(\"\");\n        require(success);\n        balances[msg.sender] = 0;\n    }\n\n    function transfer(address to, uint256 amount) external {\n        \u002F\u002F Attacker re-enters HERE during withdraw\n        \u002F\u002F balances[msg.sender] still has the old value!\n        require(balances[msg.sender] &gt;= amount);\n        balances[msg.sender] -= amount;\n        balances[to] += amount;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"delegatecall-power-and-peril\">DELEGATECALL: Power and Peril\u003C\u002Fh2>\n\u003Cp>DELEGATECALL executes another contract’s code in the context of the calling contract. The callee’s code reads and writes the caller’s storage, and msg.sender\u002Fmsg.value are preserved from the original call.\u003C\u002Fp>\n\u003Cp>This is the foundation of proxy patterns (ERC-1967, UUPS, Transparent Proxy) that enable upgradeable contracts. But it is also incredibly dangerous.\u003C\u002Fp>\n\u003Ch3>Storage Collision Attack\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F Proxy contract (simplified)\ncontract Proxy {\n    address public implementation;  \u002F\u002F slot 0\n    address public admin;           \u002F\u002F slot 1\n\n    fallback() external payable {\n        (bool s, ) = implementation.delegatecall(msg.data);\n        require(s);\n    }\n}\n\n\u002F\u002F Implementation contract\ncontract Implementation {\n    uint256 public value;  \u002F\u002F slot 0 -- COLLIDES with implementation address!\n\n    function setValue(uint256 _v) external {\n        value = _v;  \u002F\u002F This overwrites the proxy's implementation address!\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>When \u003Ccode>setValue\u003C\u002Fcode> executes via DELEGATECALL, \u003Ccode>value\u003C\u002Fcode> writes to slot 0 of the Proxy — which is the \u003Ccode>implementation\u003C\u002Fcode> address. The attacker can set the implementation to their own contract and take over the proxy.\u003C\u002Fp>\n\u003Cp>The fix: use unstructured storage with pseudo-random slots (ERC-1967 standard):\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">bytes32 constant IMPLEMENTATION_SLOT = bytes32(uint256(\n    keccak256(\"eip1967.proxy.implementation\")) - 1\n);\n\u002F\u002F = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>DELEGATECALL and msg.sender Preservation\u003C\u002Fh3>\n\u003Cp>In a DELEGATECALL chain:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>User -&gt; Proxy (DELEGATECALL) -&gt; Implementation\n\u002F\u002F Inside Implementation:\n\u002F\u002F   msg.sender = User (preserved!)\n\u002F\u002F   address(this) = Proxy (executing in Proxy's context)\n\u002F\u002F   storage reads\u002Fwrites = Proxy's storage\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This means the implementation contract must never assume \u003Ccode>address(this)\u003C\u002Fcode> is its own deployment address. Any selfdestruct, balance check, or address comparison will reference the proxy.\u003C\u002Fp>\n\u003Ch2 id=\"access-control-patterns\">Access Control Patterns\u003C\u002Fh2>\n\u003Ch3>Ownable\u003C\u002Fh3>\n\u003Cp>The simplest pattern: a single \u003Ccode>owner\u003C\u002Fcode> address with privileged access.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">contract Ownable {\n    address public owner;\n    error NotOwner();\n\n    modifier onlyOwner() {\n        if (msg.sender != owner) revert NotOwner();\n        _;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Limitations: single point of failure, no granularity.\u003C\u002Fp>\n\u003Ch3>Role-Based Access Control (RBAC)\u003C\u002Fh3>\n\u003Cp>OpenZeppelin’s AccessControl provides multi-role management:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">bytes32 public constant MINTER_ROLE = keccak256(\"MINTER_ROLE\");\nbytes32 public constant PAUSER_ROLE = keccak256(\"PAUSER_ROLE\");\n\nfunction mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {\n    _mint(to, amount);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>At the EVM level, role checks are \u003Ccode>mapping(bytes32 =&gt; mapping(address =&gt; bool))\u003C\u002Fcode> storage lookups — two keccak256 hash computations and an SLOAD per check.\u003C\u002Fp>\n\u003Ch3>Multisig and Timelock\u003C\u002Fh3>\n\u003Cp>For high-value operations, combine RBAC with:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Multisig\u003C\u002Fstrong> — Require M-of-N signatures (Gnosis Safe pattern)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Timelock\u003C\u002Fstrong> — Delay execution to allow community review (Compound’s Timelock pattern)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"return-value-checks\">Return Value Checks\u003C\u002Fh2>\n\u003Cp>The CALL opcode pushes 1 (success) or 0 (failure) onto the stack. If you do not check this return value, a failed call is silently ignored:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F DANGEROUS: unchecked low-level call\n(bool success, ) = target.call(data);\n\u002F\u002F If success is false and you don't check, execution continues!\n\n\u002F\u002F SAFE: always check\n(bool success, ) = target.call(data);\nrequire(success, \"Call failed\");\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This also applies to ERC-20 tokens. Some tokens (like USDT) do not return a boolean on \u003Ccode>transfer()\u003C\u002Fcode>. Use OpenZeppelin’s \u003Ccode>SafeERC20\u003C\u002Fcode> or check return data length:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">(bool success, bytes memory data) = token.call(\n    abi.encodeWithSelector(IERC20.transfer.selector, to, amount)\n);\nrequire(success &amp;&amp; (data.length == 0 || abi.decode(data, (bool))));\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"integer-overflow-and-underflow\">Integer Overflow and Underflow\u003C\u002Fh2>\n\u003Cp>Solidity 0.8+ includes automatic overflow\u002Funderflow checks. Before 0.8, and in raw Yul\u002Fassembly, arithmetic silently wraps:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">\u002F\u002F In Yul, there are no overflow checks!\nlet a := sub(0, 1)  \u002F\u002F a = 2^256 - 1 (max uint256)\nlet b := add(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1)  \u002F\u002F b = 0\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>If you write Yul for gas optimization (as many MEV bots do), you must manually validate arithmetic bounds or prove that overflow is impossible for your input domain.\u003C\u002Fp>\n\u003Ch2 id=\"practical-security-checklist-for-mev-bots\">Practical Security Checklist for MEV Bots\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\u003Cstrong>Never use tx.origin\u003C\u002Fstrong> — Use msg.sender or signature-based authentication.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>CEI pattern everywhere\u003C\u002Fstrong> — Update state before making external calls.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Check all return values\u003C\u002Fstrong> — Especially for low-level calls and non-standard tokens.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Validate DELEGATECALL targets\u003C\u002Fstrong> — Only delegate to trusted, audited implementations.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Use reentrancy guards\u003C\u002Fstrong> — Especially for functions that transfer ETH or tokens.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Validate calldata\u003C\u002Fstrong> — Ensure function selectors and parameters are within expected ranges.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Protect against sandwich attacks\u003C\u002Fstrong> — Your bot is a smart contract too; it can be attacked.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Use access control\u003C\u002Fstrong> — Only your EOA\u002Fmultisig should be able to call profit-extraction functions.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Implement circuit breakers\u003C\u002Fstrong> — Pause functionality if unexpected behavior is detected.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Audit Yul\u002FHuff code extra carefully\u003C\u002Fstrong> — No compiler safety nets in raw assembly.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"conclusion\">Conclusion\u003C\u002Fh2>\n\u003Cp>Security in the EVM is not a layer you add — it is a property of how you structure your state transitions relative to external calls. The checks-effects-interactions pattern, reentrancy guards, and careful delegatecall usage are not best practices — they are survival requirements. In the next article, we leave Solidity behind and enter the world of Yul, Solidity’s inline assembly language.\u003C\u002Fp>\n","en","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:22.674289Z","EVM security deep dive: msg.sender vs tx.origin, reentrancy attacks and defenses, delegatecall risks, and access control patterns for smart contracts.","EVM security reentrancy",null,"index, follow",[21,26,30],{"id":22,"name":23,"slug":24,"created_at":25},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":27,"name":28,"slug":29,"created_at":25},"c0000000-0000-0000-0000-000000000013","Security","security",{"id":31,"name":32,"slug":33,"created_at":25},"c0000000-0000-0000-0000-000000000014","Solidity","solidity","Blockchain",[36,43,49],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":41,"published_at":42},"d0200000-0000-0000-0000-000000000003","Why Bali Is Becoming Southeast Asia's Impact-Tech Hub in 2026","why-bali-becoming-southeast-asia-impact-tech-hub-2026","Bali ranks #16 among Southeast Asian startup ecosystems. With a growing concentration of Web3 builders, AI sustainability startups, and eco-travel tech companies, the island is carving a niche as the region's impact-tech capital.","Engineering","2026-03-28T10:44:37.748283Z",{"id":44,"title":45,"slug":46,"excerpt":47,"locale":12,"category_name":41,"published_at":48},"d0200000-0000-0000-0000-000000000002","ASEAN Data Protection Patchwork: A Developer's Compliance Checklist","asean-data-protection-patchwork-developer-compliance-checklist","Seven ASEAN countries now have comprehensive data protection laws, each with different consent models, localization requirements, and penalty structures. Here is a practical compliance checklist for developers building multi-country applications.","2026-03-28T10:44:37.374741Z",{"id":50,"title":51,"slug":52,"excerpt":53,"locale":12,"category_name":41,"published_at":54},"d0200000-0000-0000-0000-000000000001","Indonesia's $29 Billion Digital Transformation: Opportunities for Software Companies","indonesia-29-billion-digital-transformation-opportunities-software-companies","Indonesia's IT services market is projected to reach $29.03 billion in 2026, up from $24.37 billion in 2025. Cloud infrastructure, AI, e-commerce, and data centers are driving the fastest growth in Southeast Asia.","2026-03-28T10:44:37.349311Z",{"id":13,"name":56,"slug":57,"bio":58,"photo_url":18,"linkedin":18,"role":59,"created_at":60,"updated_at":60},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]