# Week 18 at Blockfuse Labs Week 18 at Blockfuse Labs was one of those weeks where mentorship met muscle: our boss and mentor came through to check on his team, hear our blockers, and give targeted advice. After that, we dove hard into concepts that matter for production-grade smart contracts on modern EVM chains (we practiced on Base): **Account Abstraction**, **Gasless Token Transfer (meta-tx/relayer patterns)**, **Upgradeable Proxy** designs, **Simple Bytecode / low-level deployment**, and **Multi-call** patterns. ## 1) Account Abstraction — what, why, how (deeper) **What it is:** Account Abstraction (AA) replaces the model of EOAs (externally owned accounts) + private keys with contract accounts that can validate and dispatch user intent. This unlocks programmable wallets (batching, social recovery, gas payment flexibility, session keys, custom signature schemes). **Why it matters:** It enables gasless UX, multi-factor authorization, automated transactions, and richer wallet policies. Instead of expecting users to hold native gas, AA lets relayers or paymasters sponsor gas and still verify that the user intended the operation. **How to implement (core idea):** A contract-based wallet exposes `validateUserOp` / `execute` logic and an off-chain bundler/relayer submits a UserOperation to an EntryPoint contract (EIP-4337 model). Below is a *high-level* minimal wallet contract that demonstrates the contract-account pattern and an `execute` entry: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; /// @notice Minimal contract-account (wallet) pattern: owner can execute arbitrary calls. /// @dev This is NOT a full EIP-4337 implementation. It demonstrates the basic contract-account idea. contract SimpleWallet { address public owner; event Executed(address indexed target, uint256 value, bytes data, bool success); constructor(address _owner) { owner = _owner; } modifier onlyOwner() { require(msg.sender == owner, "Not authorized"); _; } /// @notice Execute arbitrary calls from this wallet. function execute(address target, uint256 value, bytes calldata data) external onlyOwner returns (bytes memory) { (bool success, bytes memory ret) = target.call{value: value}(data); emit Executed(target, value, data, success); require(success, "Execution failed"); return ret; } /// @notice Example of a custom validation hook a bundler could query (pseudo). /// In a real AA system the EntryPoint will call a validation function that checks signatures, nonce, paymaster, etc. function validate(bytes calldata signature) external view returns (bool) { // Pseudo-logic: verify signature matches owner and internal nonce, expiry, etc. // Left intentionally minimal: production needs replay protection & signature schemes. return signature.length > 0; } // Wallet can receive native tokens receive() external payable {} } ``` **Notes & best practices:** * AA wallets must include nonce/replay protection and anti-front-running (e.g., unique `userOpHash`). * Signature verification may use ECDSA, aggregated sigs, or newer schemes (BLS, Schnorr). * Real implementations separate validation, execution, and paymaster logic; audit carefully. # **Gasless Token Transfers (meta-transactions & relayers) # **What it is:** Letting users initiate token transfers without paying gas themselves. A relayer submits a signed intent (meta-transaction) to the chain, pays gas, and (optionally) is reimbursed in token or subsidized by a paymaster. **Two common methods:** 1. **Meta-tx relayer + custom token contract** (token contract exposes `transferWithSig`). 2. **ERC-2612 / `permit` + relayer** (use `permit` to approve a relayer to move tokens — gasless UX for approvals). **Minimal meta-tx transfer example:** user signs `(to, amount, nonce)`; relayer calls `transferWithSig`. The token contract verifies the signature and executes `transfer`. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; /// @notice ERC20 with a simple gasless transfer entrypoint (meta-tx). contract GaslessToken is ERC20 { using ECDSA for bytes32; mapping(address => uint256) public nonces; // DOMAIN_SEPARATOR and EIP-712 typed data are recommended in production constructor(string memory name, string memory symbol) ERC20(name, symbol) {} /// @notice Relayer calls this with a user's signature to perform a transfer on their behalf. function transferWithSig( address from, address to, uint256 amount, uint256 nonce, bytes calldata signature ) external { require(nonce == nonces[from], "Invalid nonce"); bytes32 hash = keccak256(abi.encodePacked(from, to, amount, nonce, address(this))); address signer = hash.toEthSignedMessageHash().recover(signature); require(signer == from, "Invalid signature"); nonces[from] += 1; _transfer(from, to, amount); // relayer pays gas; optionally reimburse or charge fee here } } ``` **Security & UX tips:** * Use EIP-712 typed structured data for secure and human-readable signing. * Include expiry/timestamps in signed payloads. * Use nonces or replay-protected `userOpHash`. * Consider paymaster models to handle fees centrally. # Upgradeable Proxy architecture & implementation details **Why upgradeability:** Fix bugs, add features, or alter logic without migrating state or losing user balances. The typical pattern separates **storage** (proxy contract) from **logic** (implementation contracts). **Key patterns:** * **Transparent Proxy / UUPS / ERC1967**: standardized storage slots and upgrade mechanisms. * **Delegatecall**: proxy forwards calls to implementation but preserves storage layout of the proxy. **Minimal proxy (ERC-1967 style) with an admin upgrade function:** ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; /// @notice Minimal upgradeable proxy using a fixed implementation slot (similar idea to ERC1967). contract SimpleProxy { // keccak256("eip1967.proxy.implementation") - 1 bytes32 private constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); address public admin; event Upgraded(address indexed implementation); constructor(address _implementation) { admin = msg.sender; _setImplementation(_implementation); } modifier onlyAdmin() { require(msg.sender == admin, "Not admin"); _; } function _setImplementation(address impl) internal { assembly { sstore(IMPLEMENTATION_SLOT, impl) } } function upgradeTo(address newImpl) external onlyAdmin { _setImplementation(newImpl); emit Upgraded(newImpl); } fallback() external payable { assembly { let impl := sload(IMPLEMENTATION_SLOT) // copy calldata calldatacopy(0, 0, calldatasize()) // delegatecall into implementation let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) // copy returned data returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable {} } ``` **Important considerations:** * **Storage layout**: Implementation contracts must preserve the same storage layout (order and types) as expected by the proxy. * **Initializer pattern**: When using constructors in implementation, replace with `initialize(...)` to avoid constructor-only initialization. * **Access control**: Admin keys must be protected; consider multisig or timelocks for upgrades. * **UUPS**: lets implementation contain upgrade logic (more compact); must guard against malicious implementation upgrades. ## 4) Simple Bytecode / Low-level deployment **Why study it:** Understanding how contracts are created (`CREATE`, `CREATE2`) and what constructor bytecode looks like demystifies deployment, gas costs, and factory patterns. **Key ideas:** * Contracts are deployed by sending creation bytecode to the chain. The returned `address` is the created contract address. * `CREATE2` enables deterministic addresses (useful for meta-factories, counterfactual interactions). **Minimal deployer using `create` (raw bytecode):** ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; /// @notice Deploy arbitrary bytecode using CREATE. Returns new contract address. contract BytecodeDeployer { event Deployed(address addr); function deploy(bytes memory code) public payable returns (address addr) { assembly { // code is a pointer to the data, 0x20 offset = length prefix addr := create(callvalue(), add(code, 0x20), mload(code)) } require(addr != address(0), "deploy failed"); emit Deployed(addr); } /// @notice Deploy with CREATE2 for deterministic address: salt controls address function deployDeterministic(bytes memory code, bytes32 salt) public payable returns (address addr) { assembly { addr := create2(callvalue(), add(code, 0x20), mload(code), salt) } require(addr != address(0), "create2 failed"); emit Deployed(addr); } } ``` **Practical use-cases:** * Factories that batch-create many instances. * Counterfactual wallets where address is known pre-deployment. * Gas & size optimization experiments (constructor bytecode vs. runtime code). ## 5) Multi-call / Batching — efficiency & safety **What it does:** Batch multiple calls into one transaction to reduce round-trips and sometimes save gas (especially for overlapping initialization or when relayers sponsor gas). **Design choices:** * Use `delegatecall` to run calls in caller contract context (preserve storage) — powerful but riskier. * Use `call` to invoke other contracts (safer isolation). * Carefully handle failures: either atomic all-or-nothing (revert on failure) or capture per-call results. **Multi-call example (atomic, aggregate results):** ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; /// @notice Atomic multicall: runs all calls sequentially, reverts on any failure. contract MultiCall { struct Call { address target; bytes data; } /// @notice Execute multiple calls atomically. function aggregate(Call[] calldata calls) external returns (bytes[] memory results) { results = new bytes[](calls.length); for (uint i = 0; i < calls.length; i++) { (bool success, bytes memory ret) = calls[i].target.call(calls[i].data); require(success, "Call failed"); results[i] = ret; } } } ``` **Advanced variants:** * `try/catch` style aggregator that returns per-call success flag and data (non-atomic). * `multicall` with `delegatecall` for modular upgradeable flows (careful with storage collisions). * Gas and reentrancy considerations: batchers should guard reentrancy and ensure operations don't inadvertently allow approvals or token transfers to be exploited. Week 18 was dense but practical. The mentor’s check-in kept things grounded: learning must map to problems we can solve for users. The code snippets above are intentionally minimal to highlight the core ideas in production you’ll add replay protection, robust signature schemes (EIP-712), tests, audits, and gas optimizations.