Web3 Interview Questions - Hard

Hard-level Web3 interview questions covering MEV, zero-knowledge proofs, advanced DeFi, and protocol design.

Q1: Explain MEV (Maximal Extractable Value) and mitigation strategies.

Answer:

Sandwich Attack

MEV Mitigation

 1// ✅ Use private mempools (Flashbots)
 2// ✅ Implement slippage protection
 3contract MEVProtected {
 4    function swapWithSlippage(
 5        address tokenIn,
 6        address tokenOut,
 7        uint256 amountIn,
 8        uint256 minAmountOut, // Minimum acceptable output
 9        uint256 deadline
10    ) external {
11        require(block.timestamp <= deadline, "Expired");
12        
13        uint256 amountOut = swap(tokenIn, tokenOut, amountIn);
14        require(amountOut >= minAmountOut, "Slippage too high");
15    }
16}
17
18// ✅ Use commit-reveal schemes
19contract CommitReveal {
20    mapping(address => bytes32) public commitments;
21    
22    function commit(bytes32 hash) external {
23        commitments[msg.sender] = hash;
24    }
25    
26    function reveal(
27        uint256 amount,
28        bytes32 secret
29    ) external {
30        require(
31            keccak256(abi.encodePacked(amount, secret)) == commitments[msg.sender],
32            "Invalid"
33        );
34        // Execute trade
35    }
36}

Flashbots Integration

 1const { FlashbotsBundleProvider } = require('@flashbots/ethers-provider-bundle');
 2
 3async function sendPrivateTransaction() {
 4  const flashbotsProvider = await FlashbotsBundleProvider.create(
 5    provider,
 6    authSigner,
 7    'https://relay.flashbots.net'
 8  );
 9  
10  const signedTransactions = await flashbotsProvider.signBundle([
11    {
12      signer: wallet,
13      transaction: {
14        to: contractAddress,
15        data: txData,
16        gasLimit: 200000
17      }
18    }
19  ]);
20  
21  const simulation = await flashbotsProvider.simulate(
22    signedTransactions,
23    targetBlockNumber
24  );
25  
26  if (simulation.firstRevert) {
27    console.log('Simulation failed');
28    return;
29  }
30  
31  const bundleSubmission = await flashbotsProvider.sendRawBundle(
32    signedTransactions,
33    targetBlockNumber
34  );
35}

Q2: Explain zero-knowledge proofs and zk-SNARKs.

Answer:

ZK Proof Properties

zk-SNARK Example

 1// Verifier contract (generated by circom/snarkjs)
 2contract Verifier {
 3    struct VerifyingKey {
 4        Pairing.G1Point alpha;
 5        Pairing.G2Point beta;
 6        Pairing.G2Point gamma;
 7        Pairing.G2Point delta;
 8        Pairing.G1Point[] gamma_abc;
 9    }
10    
11    struct Proof {
12        Pairing.G1Point a;
13        Pairing.G2Point b;
14        Pairing.G1Point c;
15    }
16    
17    function verify(
18        uint[2] memory a,
19        uint[2][2] memory b,
20        uint[2] memory c,
21        uint[1] memory input
22    ) public view returns (bool) {
23        Proof memory proof;
24        proof.a = Pairing.G1Point(a[0], a[1]);
25        proof.b = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
26        proof.c = Pairing.G1Point(c[0], c[1]);
27        
28        VerifyingKey memory vk = verifyingKey();
29        
30        // Verify the proof
31        return verifyProof(proof, input, vk);
32    }
33}
34
35// Usage: Private transactions
36contract PrivateTransfer {
37    Verifier public verifier;
38    
39    mapping(bytes32 => bool) public nullifiers;
40    
41    function transfer(
42        uint[2] memory a,
43        uint[2][2] memory b,
44        uint[2] memory c,
45        bytes32 nullifier,
46        bytes32 commitment
47    ) external {
48        require(!nullifiers[nullifier], "Already spent");
49        require(verifier.verify(a, b, c, [uint(nullifier)]), "Invalid proof");
50        
51        nullifiers[nullifier] = true;
52        // Transfer without revealing amount or recipient
53    }
54}

ZK Applications


Q3: Design a complex DeFi protocol (lending with liquidations).

Answer:

Protocol Architecture

  1contract LendingProtocol {
  2    struct Market {
  3        uint256 totalSupply;
  4        uint256 totalBorrows;
  5        uint256 supplyRate;
  6        uint256 borrowRate;
  7        uint256 collateralFactor; // 75% = 0.75e18
  8        uint256 liquidationThreshold; // 80% = 0.80e18
  9        uint256 liquidationBonus; // 5% = 0.05e18
 10    }
 11    
 12    mapping(address => Market) public markets;
 13    mapping(address => mapping(address => uint256)) public supplied;
 14    mapping(address => mapping(address => uint256)) public borrowed;
 15    
 16    // Supply assets
 17    function supply(address token, uint256 amount) external {
 18        IERC20(token).transferFrom(msg.sender, address(this), amount);
 19        supplied[msg.sender][token] += amount;
 20        markets[token].totalSupply += amount;
 21        
 22        updateInterestRates(token);
 23    }
 24    
 25    // Borrow assets
 26    function borrow(address token, uint256 amount) external {
 27        require(
 28            getAccountLiquidity(msg.sender) >= amount,
 29            "Insufficient collateral"
 30        );
 31        
 32        borrowed[msg.sender][token] += amount;
 33        markets[token].totalBorrows += amount;
 34        
 35        IERC20(token).transfer(msg.sender, amount);
 36        updateInterestRates(token);
 37    }
 38    
 39    // Calculate account liquidity
 40    function getAccountLiquidity(address account) 
 41        public 
 42        view 
 43        returns (uint256) 
 44    {
 45        uint256 totalCollateral = 0;
 46        uint256 totalBorrowed = 0;
 47        
 48        address[] memory tokens = getMarketTokens();
 49        
 50        for (uint256 i = 0; i < tokens.length; i++) {
 51            address token = tokens[i];
 52            
 53            uint256 supplyValue = supplied[account][token] * 
 54                getPrice(token) * 
 55                markets[token].collateralFactor / 1e18;
 56            
 57            uint256 borrowValue = borrowed[account][token] * 
 58                getPrice(token);
 59            
 60            totalCollateral += supplyValue;
 61            totalBorrowed += borrowValue;
 62        }
 63        
 64        return totalCollateral > totalBorrowed ? 
 65            totalCollateral - totalBorrowed : 0;
 66    }
 67    
 68    // Liquidate under-collateralized position
 69    function liquidate(
 70        address borrower,
 71        address borrowToken,
 72        address collateralToken,
 73        uint256 repayAmount
 74    ) external {
 75        require(
 76            isLiquidatable(borrower),
 77            "Account not liquidatable"
 78        );
 79        
 80        // Repay borrowed amount
 81        IERC20(borrowToken).transferFrom(msg.sender, address(this), repayAmount);
 82        borrowed[borrower][borrowToken] -= repayAmount;
 83        markets[borrowToken].totalBorrows -= repayAmount;
 84        
 85        // Calculate collateral to seize
 86        uint256 collateralValue = repayAmount * 
 87            getPrice(borrowToken) * 
 88            (1e18 + markets[collateralToken].liquidationBonus) / 
 89            (getPrice(collateralToken) * 1e18);
 90        
 91        // Transfer collateral to liquidator
 92        supplied[borrower][collateralToken] -= collateralValue;
 93        IERC20(collateralToken).transfer(msg.sender, collateralValue);
 94    }
 95    
 96    function isLiquidatable(address account) public view returns (bool) {
 97        uint256 totalCollateral = 0;
 98        uint256 totalBorrowed = 0;
 99        
100        address[] memory tokens = getMarketTokens();
101        
102        for (uint256 i = 0; i < tokens.length; i++) {
103            address token = tokens[i];
104            
105            uint256 supplyValue = supplied[account][token] * 
106                getPrice(token) * 
107                markets[token].liquidationThreshold / 1e18;
108            
109            uint256 borrowValue = borrowed[account][token] * 
110                getPrice(token);
111            
112            totalCollateral += supplyValue;
113            totalBorrowed += borrowValue;
114        }
115        
116        return totalBorrowed > totalCollateral;
117    }
118    
119    // Interest rate model (simplified)
120    function updateInterestRates(address token) internal {
121        Market storage market = markets[token];
122        
123        uint256 utilizationRate = market.totalSupply > 0 ?
124            market.totalBorrows * 1e18 / market.totalSupply : 0;
125        
126        // Simple linear model
127        market.borrowRate = 2e16 + (utilizationRate * 18e16 / 1e18); // 2% + utilization * 18%
128        market.supplyRate = market.borrowRate * utilizationRate / 1e18;
129    }
130    
131    function getPrice(address token) internal view returns (uint256) {
132        // Get price from oracle (Chainlink, etc.)
133        return IOracle(oracle).getPrice(token);
134    }
135    
136    function getMarketTokens() internal view returns (address[] memory) {
137        // Return list of supported tokens
138    }
139}

Liquidation Flow


Q4: Implement cross-chain bridge protocol.

Answer:

Bridge Architecture

Source Chain Contract

  1contract SourceBridge {
  2    address public validator;
  3    mapping(bytes32 => bool) public processedDeposits;
  4    
  5    event Deposit(
  6        address indexed user,
  7        address indexed token,
  8        uint256 amount,
  9        uint256 indexed destinationChainId,
 10        uint256 nonce
 11    );
 12    
 13    event Withdrawal(
 14        address indexed user,
 15        address indexed token,
 16        uint256 amount,
 17        bytes32 indexed depositHash
 18    );
 19    
 20    function deposit(
 21        address token,
 22        uint256 amount,
 23        uint256 destinationChainId
 24    ) external {
 25        IERC20(token).transferFrom(msg.sender, address(this), amount);
 26        
 27        uint256 nonce = getNonce(msg.sender);
 28        
 29        emit Deposit(
 30            msg.sender,
 31            token,
 32            amount,
 33            destinationChainId,
 34            nonce
 35        );
 36    }
 37    
 38    function withdraw(
 39        address user,
 40        address token,
 41        uint256 amount,
 42        uint256 sourceChainId,
 43        uint256 nonce,
 44        bytes[] memory signatures
 45    ) external {
 46        bytes32 depositHash = keccak256(
 47            abi.encodePacked(user, token, amount, sourceChainId, nonce)
 48        );
 49        
 50        require(!processedDeposits[depositHash], "Already processed");
 51        require(verifySignatures(depositHash, signatures), "Invalid signatures");
 52        
 53        processedDeposits[depositHash] = true;
 54        IERC20(token).transfer(user, amount);
 55        
 56        emit Withdrawal(user, token, amount, depositHash);
 57    }
 58    
 59    function verifySignatures(
 60        bytes32 hash,
 61        bytes[] memory signatures
 62    ) internal view returns (bool) {
 63        require(signatures.length >= 2, "Need 2+ signatures");
 64        
 65        bytes32 ethSignedHash = keccak256(
 66            abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
 67        );
 68        
 69        address[] memory signers = new address[](signatures.length);
 70        
 71        for (uint256 i = 0; i < signatures.length; i++) {
 72            address signer = recoverSigner(ethSignedHash, signatures[i]);
 73            require(isValidator(signer), "Invalid validator");
 74            
 75            // Check for duplicate signers
 76            for (uint256 j = 0; j < i; j++) {
 77                require(signers[j] != signer, "Duplicate signer");
 78            }
 79            
 80            signers[i] = signer;
 81        }
 82        
 83        return true;
 84    }
 85    
 86    function recoverSigner(bytes32 hash, bytes memory signature)
 87        internal
 88        pure
 89        returns (address)
 90    {
 91        (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
 92        return ecrecover(hash, v, r, s);
 93    }
 94    
 95    function splitSignature(bytes memory sig)
 96        internal
 97        pure
 98        returns (bytes32 r, bytes32 s, uint8 v)
 99    {
100        require(sig.length == 65, "Invalid signature length");
101        
102        assembly {
103            r := mload(add(sig, 32))
104            s := mload(add(sig, 64))
105            v := byte(0, mload(add(sig, 96)))
106        }
107    }
108    
109    function getNonce(address user) internal view returns (uint256) {
110        // Implementation
111    }
112    
113    function isValidator(address addr) internal view returns (bool) {
114        // Check if address is authorized validator
115    }
116}

Destination Chain Contract

 1contract DestinationBridge {
 2    mapping(bytes32 => bool) public processedWithdrawals;
 3    
 4    event Mint(
 5        address indexed user,
 6        address indexed token,
 7        uint256 amount,
 8        bytes32 indexed sourceDepositHash
 9    );
10    
11    function mint(
12        address user,
13        address token,
14        uint256 amount,
15        uint256 sourceChainId,
16        uint256 nonce,
17        bytes[] memory signatures
18    ) external {
19        bytes32 depositHash = keccak256(
20            abi.encodePacked(user, token, amount, sourceChainId, nonce)
21        );
22        
23        require(!processedWithdrawals[depositHash], "Already processed");
24        require(verifySignatures(depositHash, signatures), "Invalid signatures");
25        
26        processedWithdrawals[depositHash] = true;
27        
28        // Mint wrapped token
29        IWrappedToken(token).mint(user, amount);
30        
31        emit Mint(user, token, amount, depositHash);
32    }
33    
34    function burn(address token, uint256 amount, uint256 destinationChainId) external {
35        IWrappedToken(token).burn(msg.sender, amount);
36        
37        emit Burn(msg.sender, token, amount, destinationChainId);
38    }
39}

Q5: Explain and implement governance mechanisms.

Answer:

Governance Token

 1import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 2
 3contract GovernanceToken is ERC20Votes {
 4    constructor() ERC20("Governance Token", "GOV") ERC20Permit("Governance Token") {
 5        _mint(msg.sender, 1000000 * 10**18);
 6    }
 7    
 8    function _afterTokenTransfer(
 9        address from,
10        address to,
11        uint256 amount
12    ) internal override(ERC20Votes) {
13        super._afterTokenTransfer(from, to, amount);
14    }
15    
16    function _mint(address to, uint256 amount) internal override(ERC20Votes) {
17        super._mint(to, amount);
18    }
19    
20    function _burn(address account, uint256 amount) internal override(ERC20Votes) {
21        super._burn(account, amount);
22    }
23}

Governor Contract

  1contract Governor {
  2    struct Proposal {
  3        uint256 id;
  4        address proposer;
  5        address[] targets;
  6        uint256[] values;
  7        bytes[] calldatas;
  8        uint256 startBlock;
  9        uint256 endBlock;
 10        uint256 forVotes;
 11        uint256 againstVotes;
 12        uint256 abstainVotes;
 13        bool executed;
 14        bool canceled;
 15        mapping(address => Receipt) receipts;
 16    }
 17    
 18    struct Receipt {
 19        bool hasVoted;
 20        uint8 support; // 0=against, 1=for, 2=abstain
 21        uint256 votes;
 22    }
 23    
 24    GovernanceToken public token;
 25    uint256 public proposalCount;
 26    uint256 public votingDelay = 1; // 1 block
 27    uint256 public votingPeriod = 50400; // ~1 week
 28    uint256 public proposalThreshold = 100000 * 10**18; // 100k tokens
 29    uint256 public quorumVotes = 400000 * 10**18; // 400k tokens
 30    
 31    mapping(uint256 => Proposal) public proposals;
 32    
 33    event ProposalCreated(
 34        uint256 id,
 35        address proposer,
 36        address[] targets,
 37        string description
 38    );
 39    
 40    event VoteCast(
 41        address indexed voter,
 42        uint256 proposalId,
 43        uint8 support,
 44        uint256 votes
 45    );
 46    
 47    function propose(
 48        address[] memory targets,
 49        uint256[] memory values,
 50        bytes[] memory calldatas,
 51        string memory description
 52    ) external returns (uint256) {
 53        require(
 54            token.getPriorVotes(msg.sender, block.number - 1) > proposalThreshold,
 55            "Proposer votes below threshold"
 56        );
 57        
 58        uint256 proposalId = ++proposalCount;
 59        Proposal storage proposal = proposals[proposalId];
 60        
 61        proposal.id = proposalId;
 62        proposal.proposer = msg.sender;
 63        proposal.targets = targets;
 64        proposal.values = values;
 65        proposal.calldatas = calldatas;
 66        proposal.startBlock = block.number + votingDelay;
 67        proposal.endBlock = block.number + votingDelay + votingPeriod;
 68        
 69        emit ProposalCreated(proposalId, msg.sender, targets, description);
 70        
 71        return proposalId;
 72    }
 73    
 74    function castVote(uint256 proposalId, uint8 support) external {
 75        Proposal storage proposal = proposals[proposalId];
 76        
 77        require(block.number >= proposal.startBlock, "Voting not started");
 78        require(block.number <= proposal.endBlock, "Voting ended");
 79        
 80        Receipt storage receipt = proposal.receipts[msg.sender];
 81        require(!receipt.hasVoted, "Already voted");
 82        
 83        uint256 votes = token.getPriorVotes(msg.sender, proposal.startBlock);
 84        
 85        if (support == 0) {
 86            proposal.againstVotes += votes;
 87        } else if (support == 1) {
 88            proposal.forVotes += votes;
 89        } else if (support == 2) {
 90            proposal.abstainVotes += votes;
 91        } else {
 92            revert("Invalid support value");
 93        }
 94        
 95        receipt.hasVoted = true;
 96        receipt.support = support;
 97        receipt.votes = votes;
 98        
 99        emit VoteCast(msg.sender, proposalId, support, votes);
100    }
101    
102    function execute(uint256 proposalId) external payable {
103        Proposal storage proposal = proposals[proposalId];
104        
105        require(!proposal.executed, "Already executed");
106        require(block.number > proposal.endBlock, "Voting not ended");
107        require(proposal.forVotes > proposal.againstVotes, "Proposal defeated");
108        require(proposal.forVotes >= quorumVotes, "Quorum not reached");
109        
110        proposal.executed = true;
111        
112        for (uint256 i = 0; i < proposal.targets.length; i++) {
113            (bool success, ) = proposal.targets[i].call{value: proposal.values[i]}(
114                proposal.calldatas[i]
115            );
116            require(success, "Transaction execution reverted");
117        }
118    }
119}

Timelock

 1contract Timelock {
 2    uint256 public constant MINIMUM_DELAY = 2 days;
 3    uint256 public constant MAXIMUM_DELAY = 30 days;
 4    
 5    mapping(bytes32 => bool) public queuedTransactions;
 6    
 7    event QueueTransaction(
 8        bytes32 indexed txHash,
 9        address indexed target,
10        uint256 value,
11        bytes data,
12        uint256 eta
13    );
14    
15    event ExecuteTransaction(
16        bytes32 indexed txHash,
17        address indexed target,
18        uint256 value,
19        bytes data,
20        uint256 eta
21    );
22    
23    function queueTransaction(
24        address target,
25        uint256 value,
26        bytes memory data,
27        uint256 eta
28    ) public returns (bytes32) {
29        require(eta >= block.timestamp + MINIMUM_DELAY, "Delay too short");
30        
31        bytes32 txHash = keccak256(abi.encode(target, value, data, eta));
32        queuedTransactions[txHash] = true;
33        
34        emit QueueTransaction(txHash, target, value, data, eta);
35        return txHash;
36    }
37    
38    function executeTransaction(
39        address target,
40        uint256 value,
41        bytes memory data,
42        uint256 eta
43    ) public payable returns (bytes memory) {
44        bytes32 txHash = keccak256(abi.encode(target, value, data, eta));
45        
46        require(queuedTransactions[txHash], "Transaction not queued");
47        require(block.timestamp >= eta, "Transaction not ready");
48        require(block.timestamp <= eta + MAXIMUM_DELAY, "Transaction stale");
49        
50        queuedTransactions[txHash] = false;
51        
52        (bool success, bytes memory returnData) = target.call{value: value}(data);
53        require(success, "Transaction execution reverted");
54        
55        emit ExecuteTransaction(txHash, target, value, data, eta);
56        return returnData;
57    }
58}

Q6: Implement EIP-2535 (Diamond Pattern) for modular contracts.

Answer:

Diamond Storage Pattern

 1library LibDiamond {
 2    bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
 3    
 4    struct FacetAddressAndPosition {
 5        address facetAddress;
 6        uint96 functionSelectorPosition;
 7    }
 8    
 9    struct DiamondStorage {
10        mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
11        bytes4[] selectors;
12        mapping(bytes4 => bool) supportedInterfaces;
13        address contractOwner;
14    }
15    
16    function diamondStorage() internal pure returns (DiamondStorage storage ds) {
17        bytes32 position = DIAMOND_STORAGE_POSITION;
18        assembly {
19            ds.slot := position
20        }
21    }
22}
23
24contract Diamond {
25    constructor(address _owner, address _diamondCutFacet) {
26        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
27        ds.contractOwner = _owner;
28        
29        // Add diamondCut function
30        bytes4 selector = IDiamondCut.diamondCut.selector;
31        ds.selectorToFacetAndPosition[selector].facetAddress = _diamondCutFacet;
32        ds.selectors.push(selector);
33    }
34    
35    fallback() external payable {
36        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
37        address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress;
38        require(facet != address(0), "Function does not exist");
39        
40        assembly {
41            calldatacopy(0, 0, calldatasize())
42            let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
43            returndatacopy(0, 0, returndatasize())
44            switch result
45            case 0 { revert(0, returndatasize()) }
46            default { return(0, returndatasize()) }
47        }
48    }
49}
50
51// Facet example
52contract TokenFacet {
53    bytes32 constant TOKEN_STORAGE_POSITION = keccak256("token.storage");
54    
55    struct TokenStorage {
56        mapping(address => uint256) balances;
57        uint256 totalSupply;
58    }
59    
60    function tokenStorage() internal pure returns (TokenStorage storage ts) {
61        bytes32 position = TOKEN_STORAGE_POSITION;
62        assembly {
63            ts.slot := position
64        }
65    }
66    
67    function balanceOf(address account) external view returns (uint256) {
68        return tokenStorage().balances[account];
69    }
70    
71    function transfer(address to, uint256 amount) external returns (bool) {
72        TokenStorage storage ts = tokenStorage();
73        require(ts.balances[msg.sender] >= amount, "Insufficient balance");
74        
75        ts.balances[msg.sender] -= amount;
76        ts.balances[to] += amount;
77        
78        return true;
79    }
80}

Q7: Explain account abstraction (EIP-4337).

Answer:

UserOperation

 1struct UserOperation {
 2    address sender;
 3    uint256 nonce;
 4    bytes initCode;
 5    bytes callData;
 6    uint256 callGasLimit;
 7    uint256 verificationGasLimit;
 8    uint256 preVerificationGas;
 9    uint256 maxFeePerGas;
10    uint256 maxPriorityFeePerGas;
11    bytes paymasterAndData;
12    bytes signature;
13}
14
15interface IAccount {
16    function validateUserOp(
17        UserOperation calldata userOp,
18        bytes32 userOpHash,
19        uint256 missingAccountFunds
20    ) external returns (uint256 validationData);
21}
22
23contract SimpleAccount is IAccount {
24    address public owner;
25    uint256 public nonce;
26    
27    function validateUserOp(
28        UserOperation calldata userOp,
29        bytes32 userOpHash,
30        uint256 missingAccountFunds
31    ) external override returns (uint256) {
32        require(msg.sender == entryPoint, "Only EntryPoint");
33        
34        bytes32 hash = keccak256(abi.encode(userOpHash, block.chainid, address(this)));
35        address signer = ECDSA.recover(hash, userOp.signature);
36        
37        require(signer == owner, "Invalid signature");
38        
39        if (missingAccountFunds > 0) {
40            (bool success, ) = payable(msg.sender).call{value: missingAccountFunds}("");
41            require(success, "Payment failed");
42        }
43        
44        return 0; // Success
45    }
46    
47    function execute(address dest, uint256 value, bytes calldata func) external {
48        require(msg.sender == entryPoint, "Only EntryPoint");
49        (bool success, ) = dest.call{value: value}(func);
50        require(success, "Execution failed");
51    }
52}

Paymaster (Gas Sponsorship)

 1contract Paymaster {
 2    function validatePaymasterUserOp(
 3        UserOperation calldata userOp,
 4        bytes32 userOpHash,
 5        uint256 maxCost
 6    ) external returns (bytes memory context, uint256 validationData) {
 7        // Verify user is eligible for sponsorship
 8        require(isEligible(userOp.sender), "Not eligible");
 9        
10        // Check paymaster has enough funds
11        require(address(this).balance >= maxCost, "Insufficient funds");
12        
13        return ("", 0);
14    }
15    
16    function postOp(
17        PostOpMode mode,
18        bytes calldata context,
19        uint256 actualGasCost
20    ) external {
21        // Post-operation logic (e.g., charge user in tokens)
22    }
23}

Q8: Design a decentralized exchange with concentrated liquidity (Uniswap V3).

Answer:

Position Management

  1contract ConcentratedLiquidityPool {
  2    struct Position {
  3        uint128 liquidity;
  4        int24 tickLower;
  5        int24 tickUpper;
  6        uint256 feeGrowthInside0LastX128;
  7        uint256 feeGrowthInside1LastX128;
  8        uint128 tokensOwed0;
  9        uint128 tokensOwed1;
 10    }
 11    
 12    mapping(bytes32 => Position) public positions;
 13    
 14    int24 public tick;
 15    uint160 public sqrtPriceX96;
 16    
 17    function mint(
 18        address recipient,
 19        int24 tickLower,
 20        int24 tickUpper,
 21        uint128 amount
 22    ) external returns (uint256 amount0, uint256 amount1) {
 23        require(tickLower < tickUpper, "Invalid ticks");
 24        require(amount > 0, "Amount must be > 0");
 25        
 26        bytes32 positionKey = keccak256(
 27            abi.encodePacked(recipient, tickLower, tickUpper)
 28        );
 29        
 30        Position storage position = positions[positionKey];
 31        
 32        // Calculate token amounts needed
 33        (amount0, amount1) = calculateAmounts(
 34            tickLower,
 35            tickUpper,
 36            amount,
 37            tick,
 38            sqrtPriceX96
 39        );
 40        
 41        // Update position
 42        position.liquidity += amount;
 43        position.tickLower = tickLower;
 44        position.tickUpper = tickUpper;
 45        
 46        // Transfer tokens
 47        if (amount0 > 0) token0.transferFrom(msg.sender, address(this), amount0);
 48        if (amount1 > 0) token1.transferFrom(msg.sender, address(this), amount1);
 49        
 50        return (amount0, amount1);
 51    }
 52    
 53    function swap(
 54        bool zeroForOne,
 55        int256 amountSpecified,
 56        uint160 sqrtPriceLimitX96
 57    ) external returns (int256 amount0, int256 amount1) {
 58        SwapState memory state = SwapState({
 59            amountSpecifiedRemaining: amountSpecified,
 60            amountCalculated: 0,
 61            sqrtPriceX96: sqrtPriceX96,
 62            tick: tick,
 63            liquidity: liquidity
 64        });
 65        
 66        while (state.amountSpecifiedRemaining != 0) {
 67            StepComputations memory step;
 68            
 69            step.sqrtPriceStartX96 = state.sqrtPriceX96;
 70            
 71            // Get next tick
 72            (step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord(
 73                state.tick,
 74                tickSpacing,
 75                zeroForOne
 76            );
 77            
 78            // Compute swap step
 79            (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = 
 80                SwapMath.computeSwapStep(
 81                    state.sqrtPriceX96,
 82                    getSqrtRatioAtTick(step.tickNext),
 83                    state.liquidity,
 84                    state.amountSpecifiedRemaining,
 85                    fee
 86                );
 87            
 88            state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount);
 89            state.amountCalculated += step.amountOut;
 90            
 91            // Cross tick if needed
 92            if (state.sqrtPriceX96 == getSqrtRatioAtTick(step.tickNext)) {
 93                int128 liquidityNet = ticks[step.tickNext].liquidityNet;
 94                state.liquidity = zeroForOne ? 
 95                    state.liquidity - liquidityNet : 
 96                    state.liquidity + liquidityNet;
 97                state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
 98            }
 99        }
100        
101        // Update global state
102        tick = state.tick;
103        sqrtPriceX96 = state.sqrtPriceX96;
104        
105        return (amount0, amount1);
106    }
107}

Q9: Implement a decentralized identity system.

Answer:

DID Registry

 1contract DIDRegistry {
 2    struct DIDDocument {
 3        address controller;
 4        string publicKey;
 5        string serviceEndpoint;
 6        uint256 created;
 7        uint256 updated;
 8        bool revoked;
 9    }
10    
11    mapping(bytes32 => DIDDocument) public dids;
12    mapping(bytes32 => mapping(bytes32 => bool)) public credentials;
13    
14    event DIDCreated(bytes32 indexed did, address controller);
15    event DIDUpdated(bytes32 indexed did);
16    event CredentialIssued(bytes32 indexed did, bytes32 indexed credentialHash);
17    
18    function createDID(
19        bytes32 did,
20        string memory publicKey,
21        string memory serviceEndpoint
22    ) external {
23        require(dids[did].controller == address(0), "DID already exists");
24        
25        dids[did] = DIDDocument({
26            controller: msg.sender,
27            publicKey: publicKey,
28            serviceEndpoint: serviceEndpoint,
29            created: block.timestamp,
30            updated: block.timestamp,
31            revoked: false
32        });
33        
34        emit DIDCreated(did, msg.sender);
35    }
36    
37    function issueCredential(
38        bytes32 did,
39        bytes32 credentialHash,
40        bytes memory signature
41    ) external {
42        require(!dids[did].revoked, "DID revoked");
43        require(verifyIssuer(msg.sender), "Not authorized issuer");
44        
45        credentials[did][credentialHash] = true;
46        
47        emit CredentialIssued(did, credentialHash);
48    }
49    
50    function verifyCredential(
51        bytes32 did,
52        bytes32 credentialHash
53    ) external view returns (bool) {
54        return credentials[did][credentialHash] && !dids[did].revoked;
55    }
56    
57    function revokeDID(bytes32 did) external {
58        require(dids[did].controller == msg.sender, "Not controller");
59        dids[did].revoked = true;
60    }
61}

Q10: Explain and implement a DAO treasury management system.

Answer:

Treasury Contract

  1contract DAOTreasury {
  2    struct Budget {
  3        uint256 amount;
  4        uint256 spent;
  5        uint256 startTime;
  6        uint256 endTime;
  7        address recipient;
  8        bool active;
  9    }
 10    
 11    struct VestingSchedule {
 12        uint256 totalAmount;
 13        uint256 released;
 14        uint256 startTime;
 15        uint256 duration;
 16        address beneficiary;
 17    }
 18    
 19    mapping(uint256 => Budget) public budgets;
 20    mapping(uint256 => VestingSchedule) public vestingSchedules;
 21    
 22    uint256 public budgetCount;
 23    uint256 public vestingCount;
 24    
 25    address public governance;
 26    
 27    modifier onlyGovernance() {
 28        require(msg.sender == governance, "Not governance");
 29        _;
 30    }
 31    
 32    function createBudget(
 33        uint256 amount,
 34        uint256 duration,
 35        address recipient
 36    ) external onlyGovernance returns (uint256) {
 37        uint256 budgetId = budgetCount++;
 38        
 39        budgets[budgetId] = Budget({
 40            amount: amount,
 41            spent: 0,
 42            startTime: block.timestamp,
 43            endTime: block.timestamp + duration,
 44            recipient: recipient,
 45            active: true
 46        });
 47        
 48        return budgetId;
 49    }
 50    
 51    function spend(
 52        uint256 budgetId,
 53        uint256 amount,
 54        address to
 55    ) external {
 56        Budget storage budget = budgets[budgetId];
 57        
 58        require(budget.active, "Budget not active");
 59        require(msg.sender == budget.recipient, "Not recipient");
 60        require(block.timestamp <= budget.endTime, "Budget expired");
 61        require(budget.spent + amount <= budget.amount, "Exceeds budget");
 62        
 63        budget.spent += amount;
 64        payable(to).transfer(amount);
 65    }
 66    
 67    function createVesting(
 68        uint256 amount,
 69        uint256 duration,
 70        address beneficiary
 71    ) external onlyGovernance returns (uint256) {
 72        uint256 vestingId = vestingCount++;
 73        
 74        vestingSchedules[vestingId] = VestingSchedule({
 75            totalAmount: amount,
 76            released: 0,
 77            startTime: block.timestamp,
 78            duration: duration,
 79            beneficiary: beneficiary
 80        });
 81        
 82        return vestingId;
 83    }
 84    
 85    function releaseVested(uint256 vestingId) external {
 86        VestingSchedule storage schedule = vestingSchedules[vestingId];
 87        
 88        require(msg.sender == schedule.beneficiary, "Not beneficiary");
 89        
 90        uint256 vested = calculateVested(vestingId);
 91        uint256 releasable = vested - schedule.released;
 92        
 93        require(releasable > 0, "Nothing to release");
 94        
 95        schedule.released += releasable;
 96        payable(schedule.beneficiary).transfer(releasable);
 97    }
 98    
 99    function calculateVested(uint256 vestingId) public view returns (uint256) {
100        VestingSchedule storage schedule = vestingSchedules[vestingId];
101        
102        if (block.timestamp < schedule.startTime) {
103            return 0;
104        }
105        
106        if (block.timestamp >= schedule.startTime + schedule.duration) {
107            return schedule.totalAmount;
108        }
109        
110        uint256 elapsed = block.timestamp - schedule.startTime;
111        return (schedule.totalAmount * elapsed) / schedule.duration;
112    }
113    
114    receive() external payable {}
115}

Summary

Hard Web3 topics:

  • MEV: Sandwich attacks, Flashbots, mitigation
  • Zero-Knowledge: zk-SNARKs, privacy, scalability
  • Complex DeFi: Lending protocols, liquidations
  • Cross-Chain: Bridge protocols, validators
  • Governance: DAO, proposals, voting, timelock
  • Diamond Pattern: Modular contracts, upgradeable
  • Account Abstraction: Smart wallets, paymasters
  • Concentrated Liquidity: Uniswap V3, capital efficiency
  • Decentralized Identity: DIDs, verifiable credentials
  • Treasury Management: Budgets, vesting, multi-sig

These advanced concepts enable building production-grade Web3 protocols.

Related Snippets