Module 11: Advanced Development

Module 11: Advanced Smart Contract Development

11.1 Introduction to Advanced Smart Contract Development

After mastering the fundamentals, intermediate concepts, and design patterns in Solidity, it's time to explore advanced techniques that will enable you to build sophisticated decentralized applications. This module covers cutting-edge topics that experienced Solidity developers need to know.

Advanced smart contract development involves:

  1. Integration with External Systems: Connecting smart contracts to real-world data and systems
  2. Complex Architectures: Building multi-contract systems with specialized components
  3. Advanced Security Techniques: Implementing sophisticated security measures
  4. Optimization Strategies: Fine-tuning contracts for maximum efficiency
  5. Interoperability: Enabling communication between different blockchain networks

11.2 Oracles and External Data

Smart contracts are deterministic and cannot directly access external data. Oracles solve this problem by providing a bridge between blockchains and the outside world.

Chainlink Oracles

Chainlink is the most widely used oracle network in the Ethereum ecosystem:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract PriceFeed {
    AggregatorV3Interface internal priceFeed;

    /**
     * Network: Ethereum Mainnet
     * Aggregator: ETH/USD
     * Address: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
     */
    constructor() {
        priceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
    }

    /**
     * Returns the latest price of ETH in USD
     */
    function getLatestPrice() public view returns (int) {
        (
            /* uint80 roundID */,
            int price,
            /* uint startedAt */,
            /* uint timeStamp */,
            /* uint80 answeredInRound */
        ) = priceFeed.latestRoundData();
        return price;
    }

    /**
     * Returns the price of ETH in USD with proper decimals
     */
    function getLatestPriceWithDecimals() public view returns (int) {
        (
            /* uint80 roundID */,
            int price,
            /* uint startedAt */,
            /* uint timeStamp */,
            /* uint80 answeredInRound */
        ) = priceFeed.latestRoundData();

        uint8 decimals = priceFeed.decimals();
        return price / int(10 ** uint(decimals));
    }
}

Chainlink VRF (Verifiable Random Function)

Chainlink VRF provides cryptographically secure randomness:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

contract RandomNumberConsumer is VRFConsumerBaseV2 {
    VRFCoordinatorV2Interface COORDINATOR;

    // Your subscription ID
    uint64 s_subscriptionId;

    // Goerli coordinator
    address vrfCoordinator = 0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D;

    // The gas lane to use, which specifies the maximum gas price to bump to
    bytes32 keyHash = 0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;

    // Depends on the number of requested values that you want sent to the
    // fulfillRandomWords() function. Storing each word costs about 20,000 gas,
    // so 100,000 is a safe default for this example contract. Test and adjust
    // this limit based on the network that you select, the size of the request,
    // and the processing of the callback request in the fulfillRandomWords()
    // function.
    uint32 callbackGasLimit = 100000;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFCoordinatorV2.MAX_NUM_WORDS.
    uint32 numWords = 2;

    uint256[] public s_randomWords;
    uint256 public s_requestId;

    constructor(uint64 subscriptionId) VRFConsumerBaseV2(vrfCoordinator) {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        s_subscriptionId = subscriptionId;
    }

    // Assumes the subscription is funded sufficiently.
    function requestRandomWords() external {
        // Will revert if subscription is not set and funded.
        s_requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
    }

    function fulfillRandomWords(
        uint256, /* requestId */
        uint256[] memory randomWords
    ) internal override {
        s_randomWords = randomWords;
    }
}

Custom Oracle Implementation

You can also implement your own oracle system:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract CustomOracle {
    address public owner;
    address public oracleOperator;

    struct DataRequest {
        address requester;
        string dataType;
        bytes parameters;
        uint256 timestamp;
        bool fulfilled;
        bytes result;
    }

    mapping(uint256 => DataRequest) public requests;
    uint256 public nextRequestId = 1;

    event DataRequested(
        uint256 indexed requestId,
        address indexed requester,
        string dataType,
        bytes parameters
    );

    event DataFulfilled(
        uint256 indexed requestId,
        bytes result
    );

    constructor() {
        owner = msg.sender;
        oracleOperator = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    modifier onlyOracleOperator() {
        require(msg.sender == oracleOperator, "Not oracle operator");
        _;
    }

    function setOracleOperator(address _oracleOperator) external onlyOwner {
        oracleOperator = _oracleOperator;
    }

    function requestData(string calldata dataType, bytes calldata parameters) external returns (uint256) {
        uint256 requestId = nextRequestId++;

        requests[requestId] = DataRequest({
            requester: msg.sender,
            dataType: dataType,
            parameters: parameters,
            timestamp: block.timestamp,
            fulfilled: false,
            result: new bytes(0)
        });

        emit DataRequested(requestId, msg.sender, dataType, parameters);

        return requestId;
    }

    function fulfillData(uint256 requestId, bytes calldata result) external onlyOracleOperator {
        DataRequest storage request = requests[requestId];
        require(!request.fulfilled, "Request already fulfilled");

        request.fulfilled = true;
        request.result = result;

        emit DataFulfilled(requestId, result);
    }

    function getRequestResult(uint256 requestId) external view returns (bytes memory) {
        DataRequest storage request = requests[requestId];
        require(request.fulfilled, "Request not fulfilled");

        return request.result;
    }
}

contract OracleConsumer {
    CustomOracle public oracle;

    mapping(uint256 => bool) public myRequests;
    bytes public lastResult;

    event RequestSent(uint256 requestId);
    event ResultReceived(bytes result);

    constructor(address oracleAddress) {
        oracle = CustomOracle(oracleAddress);
    }

    function requestStockPrice(string calldata symbol) external {
        bytes memory parameters = abi.encode(symbol);
        uint256 requestId = oracle.requestData("stock_price", parameters);

        myRequests[requestId] = true;

        emit RequestSent(requestId);
    }

    function checkResult(uint256 requestId) external {
        require(myRequests[requestId], "Not your request");

        bytes memory result = oracle.getRequestResult(requestId);
        lastResult = result;

        emit ResultReceived(result);
    }
}

11.3 Cross-Chain Communication

As the blockchain ecosystem grows, the ability to communicate between different chains becomes increasingly important.

Layer 2 Solutions

Layer 2 solutions like Optimism and Arbitrum extend Ethereum's capabilities:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// Interface for the L1 contract
interface IL1Contract {
    function sendMessageToL2(bytes calldata message) external;
}

// L1 contract that sends messages to L2
contract L1Messenger {
    address public l2Target;
    IL1Contract public messenger;

    constructor(address _messenger, address _l2Target) {
        messenger = IL1Contract(_messenger);
        l2Target = _l2Target;
    }

    function sendMessage(string calldata message) external {
        bytes memory encodedMessage = abi.encodeWithSignature(
            "receiveMessage(string)",
            message
        );

        messenger.sendMessageToL2(encodedMessage);
    }
}

// L2 contract that receives messages from L1
contract L2Receiver {
    address public l1Source;
    string public lastMessage;

    event MessageReceived(string message);

    constructor(address _l1Source) {
        l1Source = _l1Source;
    }

    function receiveMessage(string calldata message) external {
        // In a real implementation, this would verify the sender
        lastMessage = message;
        emit MessageReceived(message);
    }
}

Bridges and Cross-Chain Messaging

Bridges enable asset and data transfer between different blockchains:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// Interface for a cross-chain bridge
interface IBridge {
    function sendMessage(address targetContract, bytes calldata message, uint256 gasLimit) external payable;
}

// Contract that sends messages across chains
contract CrossChainSender {
    IBridge public bridge;
    address public targetContract;

    constructor(address _bridge, address _targetContract) {
        bridge = IBridge(_bridge);
        targetContract = _targetContract;
    }

    function sendCrossChainMessage(string calldata message, uint256 gasLimit) external payable {
        bytes memory encodedMessage = abi.encodeWithSignature(
            "receiveMessage(string)",
            message
        );

        bridge.sendMessage{value: msg.value}(targetContract, encodedMessage, gasLimit);
    }
}

// Contract that receives messages from other chains
contract CrossChainReceiver {
    address public trustedBridge;
    string public lastMessage;

    event MessageReceived(string message);

    constructor(address _trustedBridge) {
        trustedBridge = _trustedBridge;
    }

    function receiveMessage(string calldata message) external {
        require(msg.sender == trustedBridge, "Not from trusted bridge");

        lastMessage = message;
        emit MessageReceived(message);
    }
}

11.4 Advanced Security Techniques

Beyond basic security practices, advanced techniques can further protect your contracts.

Formal Verification

Formal verification mathematically proves the correctness of your contract:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

/// @notice invariant totalSupply == sum(balances)
contract VerifiableToken {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;

    constructor(uint256 initialSupply) {
        balances[msg.sender] = initialSupply;
        totalSupply = initialSupply;
    }

    /// @notice postcondition balances[msg.sender] == old(balances[msg.sender]) - amount
    /// @notice postcondition balances[to] == old(balances[to]) + amount
    function transfer(address to, uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;
        balances[to] += amount;
    }
}

Timelock Controllers

Timelock controllers add a delay to sensitive operations:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract TimelockController {
    address public admin;
    uint256 public delay;

    mapping(bytes32 => bool) public queuedTransactions;

    event QueueTransaction(bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta);
    event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta);
    event CancelTransaction(bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta);

    constructor(uint256 _delay) {
        admin = msg.sender;
        delay = _delay;
    }

    modifier onlyAdmin() {
        require(msg.sender == admin, "TimelockController: caller is not admin");
        _;
    }

    function queueTransaction(
        address target,
        uint256 value,
        string memory signature,
        bytes memory data,
        uint256 eta
    ) public onlyAdmin returns (bytes32) {
        require(eta >= block.timestamp + delay, "TimelockController: eta must satisfy delay");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        queuedTransactions[txHash] = true;

        emit QueueTransaction(txHash, target, value, signature, data, eta);

        return txHash;
    }

    function cancelTransaction(
        address target,
        uint256 value,
        string memory signature,
        bytes memory data,
        uint256 eta
    ) public onlyAdmin {
        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        require(queuedTransactions[txHash], "TimelockController: transaction not queued");

        queuedTransactions[txHash] = false;

        emit CancelTransaction(txHash, target, value, signature, data, eta);
    }

    function executeTransaction(
        address target,
        uint256 value,
        string memory signature,
        bytes memory data,
        uint256 eta
    ) public onlyAdmin returns (bytes memory) {
        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        require(queuedTransactions[txHash], "TimelockController: transaction not queued");
        require(block.timestamp >= eta, "TimelockController: transaction hasn't surpassed time lock");

        queuedTransactions[txHash] = false;

        bytes memory callData;
        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        (bool success, bytes memory returnData) = target.call{value: value}(callData);
        require(success, "TimelockController: transaction execution reverted");

        emit ExecuteTransaction(txHash, target, value, signature, data, eta);

        return returnData;
    }
}

Secure Randomness

Implementing secure randomness in a deterministic environment:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract SecureRandomness {
    // Commitment scheme for randomness
    struct Commitment {
        bytes32 commit;
        uint256 blockNumber;
        bool revealed;
        bytes32 random;
    }

    mapping(address => Commitment) public commitments;

    // Step 1: User commits a hash (random value + salt)
    function commitRandom(bytes32 commitment) public {
        commitments[msg.sender] = Commitment({
            commit: commitment,
            blockNumber: block.number,
            revealed: false,
            random: bytes32(0)
        });
    }

    // Step 2: User reveals the random value and salt
    function revealRandom(bytes32 random, bytes32 salt) public {
        Commitment storage commitment = commitments[msg.sender];

        require(commitment.commit != bytes32(0), "No commitment found");
        require(!commitment.revealed, "Already revealed");
        require(block.number > commitment.blockNumber, "Cannot reveal in same block");

        bytes32 computedCommit = keccak256(abi.encodePacked(random, salt, msg.sender));
        require(computedCommit == commitment.commit, "Invalid reveal");

        commitment.revealed = true;
        commitment.random = random;
    }

    // Combine user randomness with blockchain randomness
    function generateSecureRandom() public view returns (bytes32) {
        Commitment storage commitment = commitments[msg.sender];
        require(commitment.revealed, "Randomness not revealed");

        return keccak256(abi.encodePacked(
            commitment.random,
            blockhash(block.number - 1),
            block.timestamp,
            block.difficulty
        ));
    }
}

11.5 Advanced Contract Architectures

Complex applications often require sophisticated contract architectures.

Diamond Pattern (EIP-2535)

The Diamond Pattern allows for modular contract development:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// Interface for the Diamond Cut
interface IDiamondCut {
    enum FacetCutAction {Add, Replace, Remove}

    struct FacetCut {
        address facetAddress;
        FacetCutAction action;
        bytes4[] functionSelectors;
    }

    function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external;
}

// Diamond storage
library LibDiamond {
    bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

    struct DiamondStorage {
        // Function selector => facet address
        mapping(bytes4 => address) facetAddressAndSelectorPosition;
        bytes4[] selectors;
        mapping(address => bool) supportedInterfaces;
        // owner of the contract
        address contractOwner;
    }

    function diamondStorage() internal pure returns (DiamondStorage storage ds) {
        bytes32 position = DIAMOND_STORAGE_POSITION;
        assembly {
            ds.slot := position
        }
    }

    function setContractOwner(address _newOwner) internal {
        DiamondStorage storage ds = diamondStorage();
        ds.contractOwner = _newOwner;
    }

    function contractOwner() internal view returns (address) {
        return diamondStorage().contractOwner;
    }

    function enforceIsContractOwner() internal view {
        require(msg.sender == contractOwner(), "LibDiamond: Not contract owner");
    }
}

// Diamond contract
contract Diamond {
    constructor(address _contractOwner, address _diamondCutFacet) {
        LibDiamond.setContractOwner(_contractOwner);

        // Add DiamondCutFacet
        IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
        bytes4[] memory functionSelectors = new bytes4[](1);
        functionSelectors[0] = IDiamondCut.diamondCut.selector;
        cut[0] = IDiamondCut.FacetCut({
            facetAddress: _diamondCutFacet,
            action: IDiamondCut.FacetCutAction.Add,
            functionSelectors: functionSelectors
        });
        LibDiamondCut.diamondCut(cut, address(0), new bytes(0));
    }

    // Find facet for function that is called and execute the
    // function if a facet is found and return any value.
    fallback() external payable {
        LibDiamond.DiamondStorage storage ds;
        bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION;
        assembly {
            ds.slot := position
        }

        address facet = ds.facetAddressAndSelectorPosition[msg.sig];
        require(facet != address(0), "Diamond: Function does not exist");

        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }

    receive() external payable {}
}

// Diamond Cut Facet
contract DiamondCutFacet is IDiamondCut {
    function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external override {
        LibDiamond.enforceIsContractOwner();
        LibDiamondCut.diamondCut(_diamondCut, _init, _calldata);
    }
}

// Library for diamond cuts
library LibDiamondCut {
    function diamondCut(
        IDiamondCut.FacetCut[] memory _diamondCut,
        address _init,
        bytes memory _calldata
    ) internal {
        for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) {
            IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action;

            if (action == IDiamondCut.FacetCutAction.Add) {
                addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
            } else if (action == IDiamondCut.FacetCutAction.Replace) {
                replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
            } else if (action == IDiamondCut.FacetCutAction.Remove) {
                removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
            } else {
                revert("LibDiamondCut: Incorrect FacetCutAction");
            }
        }

        if (_init != address(0)) {
            // Call initialization function
            (bool success, ) = _init.delegatecall(_calldata);
            require(success, "LibDiamondCut: _init function reverted");
        }
    }

    function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
        require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to add");
        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();

        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector];
            require(oldFacetAddress == address(0), "LibDiamondCut: Function already exists");
            ds.facetAddressAndSelectorPosition[selector] = _facetAddress;
            ds.selectors.push(selector);
        }
    }

    function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
        require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to replace");
        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();

        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector];
            require(oldFacetAddress != address(0), "LibDiamondCut: Function doesn't exist");
            require(oldFacetAddress != _facetAddress, "LibDiamondCut: Cannot replace function with same function");
            ds.facetAddressAndSelectorPosition[selector] = _facetAddress;
        }
    }

    function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
        require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to remove");
        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();

        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector];
            require(oldFacetAddress != address(0), "LibDiamondCut: Function doesn't exist");
            delete ds.facetAddressAndSelectorPosition[selector];

            // Remove selector from selectors array
            for (uint256 i; i < ds.selectors.length; i++) {
                if (ds.selectors[i] == selector) {
                    ds.selectors[i] = ds.selectors[ds.selectors.length - 1];
                    ds.selectors.pop();
                    break;
                }
            }
        }
    }
}

Factory Pattern

The Factory Pattern creates contract instances dynamically:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// Interface for the created contracts
interface IProduct {
    function initialize(address owner, string calldata name) external;
    function getName() external view returns (string memory);
    function getOwner() external view returns (address);
}

// Product contract
contract Product is IProduct {
    address public owner;
    string public name;
    bool public initialized;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    function initialize(address _owner, string calldata _name) external override {
        require(!initialized, "Already initialized");
        owner = _owner;
        name = _name;
        initialized = true;
    }

    function getName() external view override returns (string memory) {
        return name;
    }

    function getOwner() external view override returns (address) {
        return owner;
    }

    function updateName(string calldata _name) external onlyOwner {
        name = _name;
    }
}

// Factory contract
contract ProductFactory {
    event ProductCreated(address indexed product, address indexed owner, string name);

    address[] public products;
    mapping(address => address[]) public ownerProducts;

    function createProduct(string calldata name) external returns (address) {
        Product product = new Product();
        product.initialize(msg.sender, name);

        products.push(address(product));
        ownerProducts[msg.sender].push(address(product));

        emit ProductCreated(address(product), msg.sender, name);

        return address(product);
    }

    function getProductCount() external view returns (uint256) {
        return products.length;
    }

    function getOwnerProductCount(address owner) external view returns (uint256) {
        return ownerProducts[owner].length;
    }
}

Clone Factory (Minimal Proxy)

The Clone Factory creates minimal proxy contracts for gas efficiency:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// Interface for the implementation contract
interface IImplementation {
    function initialize(address owner, string calldata name) external;
}

// Clone Factory
contract CloneFactory {
    event CloneCreated(address indexed clone, address indexed owner);

    address public implementation;

    constructor(address _implementation) {
        implementation = _implementation;
    }

    function createClone(string calldata name) external returns (address clone) {
        // Create clone
        bytes20 targetBytes = bytes20(implementation);
        assembly {
            let clone_code := mload(0x40)
            mstore(clone_code, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(clone_code, 0x14), targetBytes)
            mstore(add(clone_code, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            clone := create(0, clone_code, 0x37)
        }

        // Initialize clone
        IImplementation(clone).initialize(msg.sender, name);

        emit CloneCreated(clone, msg.sender);

        return clone;
    }
}

// Implementation contract
contract Implementation {
    address public owner;
    string public name;
    bool public initialized;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    function initialize(address _owner, string calldata _name) external {
        require(!initialized, "Already initialized");
        owner = _owner;
        name = _name;
        initialized = true;
    }

    function updateName(string calldata _name) external onlyOwner {
        name = _name;
    }
}

11.6 Advanced Tokenomics

Advanced token economics can create sophisticated incentive structures.

Bonding Curves

Bonding curves create price-supply relationships:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract BondingCurveToken {
    uint256 public totalSupply;
    mapping(address => uint256) public balances;

    // Bonding curve parameters
    uint256 public constant CURVE_FACTOR = 100; // Higher = steeper curve

    event TokensPurchased(address indexed buyer, uint256 amountPurchased, uint256 etherSpent);
    event TokensSold(address indexed seller, uint256 amountSold, uint256 etherReceived);

    // Calculate price based on supply
    function calculatePrice(uint256 supply, uint256 amount) public pure returns (uint256) {
        // Linear bonding curve: price = supply * CURVE_FACTOR
        uint256 startPrice = supply * CURVE_FACTOR;
        uint256 endPrice = (supply + amount) * CURVE_FACTOR;

        // Average price for the amount
        return (startPrice + endPrice) / 2;
    }

    // Buy tokens
    function buy() external payable {
        require(msg.value > 0, "Must send ETH");

        // Calculate how many tokens to mint based on current price
        uint256 tokensToBuy = calculateTokenAmount(msg.value);
        require(tokensToBuy > 0, "Not enough ETH sent");

        // Mint tokens
        totalSupply += tokensToBuy;
        balances[msg.sender] += tokensToBuy;

        emit TokensPurchased(msg.sender, tokensToBuy, msg.value);
    }

    // Calculate token amount for a given ETH amount
    function calculateTokenAmount(uint256 etherAmount) public view returns (uint256) {
        // For simplicity, using a basic formula
        // In a real implementation, this would solve the integral of the price function
        return etherAmount / (totalSupply * CURVE_FACTOR / 1 ether);
    }

    // Sell tokens
    function sell(uint256 tokenAmount) external {
        require(tokenAmount > 0, "Amount must be greater than 0");
        require(balances[msg.sender] >= tokenAmount, "Insufficient balance");

        // Calculate ETH to return based on current price
        uint256 etherToReturn = calculateEtherAmount(tokenAmount);

        // Burn tokens
        balances[msg.sender] -= tokenAmount;
        totalSupply -= tokenAmount;

        // Send ETH
        (bool success, ) = msg.sender.call{value: etherToReturn}("");
        require(success, "ETH transfer failed");

        emit TokensSold(msg.sender, tokenAmount, etherToReturn);
    }

    // Calculate ETH amount for a given token amount
    function calculateEtherAmount(uint256 tokenAmount) public view returns (uint256) {
        // For simplicity, using a basic formula
        // In a real implementation, this would solve the integral of the price function
        return tokenAmount * (totalSupply * CURVE_FACTOR / 1 ether);
    }

    // Get current token price
    function getCurrentPrice() external view returns (uint256) {
        if (totalSupply == 0) {
            return CURVE_FACTOR;
        }
        return totalSupply * CURVE_FACTOR;
    }
}

Quadratic Voting

Quadratic voting gives users voting power proportional to the square root of their tokens:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract QuadraticVoting {
    struct Proposal {
        string description;
        uint256 votesFor;
        uint256 votesAgainst;
        uint256 endTime;
        bool executed;
        mapping(address => uint256) voterCredits;
    }

    mapping(uint256 => Proposal) public proposals;
    uint256 public proposalCount;

    mapping(address => uint256) public tokenBalance;

    event ProposalCreated(uint256 indexed proposalId, string description, uint256 endTime);
    event Voted(uint256 indexed proposalId, address indexed voter, bool support, uint256 credits, uint256 votes);
    event ProposalExecuted(uint256 indexed proposalId, bool passed);

    // Mint tokens for testing
    function mint(uint256 amount) external {
        tokenBalance[msg.sender] += amount;
    }

    // Create a new proposal
    function createProposal(string calldata description, uint256 duration) external returns (uint256) {
        uint256 proposalId = proposalCount++;

        Proposal storage proposal = proposals[proposalId];
        proposal.description = description;
        proposal.endTime = block.timestamp + duration;

        emit ProposalCreated(proposalId, description, proposal.endTime);

        return proposalId;
    }

    // Vote on a proposal
    function vote(uint256 proposalId, bool support, uint256 credits) external {
        Proposal storage proposal = proposals[proposalId];

        require(block.timestamp < proposal.endTime, "Voting period ended");
        require(tokenBalance[msg.sender] >= credits, "Not enough tokens");

        // Calculate votes using quadratic formula
        uint256 votes = sqrt(credits);

        // Update proposal votes
        if (support) {
            proposal.votesFor += votes;
        } else {
            proposal.votesAgainst += votes;
        }

        // Update voter credits
        proposal.voterCredits[msg.sender] += credits;

        // Deduct tokens
        tokenBalance[msg.sender] -= credits;

        emit Voted(proposalId, msg.sender, support, credits, votes);
    }

    // Execute a proposal after voting ends
    function executeProposal(uint256 proposalId) external {
        Proposal storage proposal = proposals[proposalId];

        require(block.timestamp >= proposal.endTime, "Voting period not ended");
        require(!proposal.executed, "Proposal already executed");

        proposal.executed = true;

        bool passed = proposal.votesFor > proposal.votesAgainst;

        emit ProposalExecuted(proposalId, passed);

        // Execute proposal logic here
    }

    // Get voter credits for a proposal
    function getVoterCredits(uint256 proposalId, address voter) external view returns (uint256) {
        return proposals[proposalId].voterCredits[voter];
    }

    // Square root function
    function sqrt(uint256 x) internal pure returns (uint256) {
        if (x == 0) return 0;

        uint256 z = (x + 1) / 2;
        uint256 y = x;

        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }

        return y;
    }
}

Token Vesting

Token vesting gradually releases tokens over time:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract TokenVesting {
    struct VestingSchedule {
        address beneficiary;
        uint256 start;
        uint256 cliff;
        uint256 duration;
        uint256 totalAmount;
        uint256 releasedAmount;
        bool revocable;
        bool revoked;
    }

    IERC20 public token;
    address public owner;

    mapping(bytes32 => VestingSchedule) public vestingSchedules;
    mapping(address => bytes32[]) public beneficiarySchedules;

    event ScheduleCreated(bytes32 indexed scheduleId, address indexed beneficiary, uint256 amount);
    event TokensReleased(bytes32 indexed scheduleId, address indexed beneficiary, uint256 amount);
    event ScheduleRevoked(bytes32 indexed scheduleId, address indexed beneficiary, uint256 returnedAmount);

    constructor(address _token) {
        token = IERC20(_token);
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    // Create a new vesting schedule
    function createVestingSchedule(
        address beneficiary,
        uint256 start,
        uint256 cliffDuration,
        uint256 duration,
        uint256 amount,
        bool revocable
    ) external onlyOwner {
        require(beneficiary != address(0), "Beneficiary cannot be zero address");
        require(duration > 0, "Duration must be > 0");
        require(amount > 0, "Amount must be > 0");
        require(cliffDuration <= duration, "Cliff must be <= duration");

        // Transfer tokens to this contract
        token.transferFrom(msg.sender, address(this), amount);

        bytes32 scheduleId = keccak256(abi.encodePacked(beneficiary, start, block.timestamp));

        vestingSchedules[scheduleId] = VestingSchedule({
            beneficiary: beneficiary,
            start: start,
            cliff: start + cliffDuration,
            duration: duration,
            totalAmount: amount,
            releasedAmount: 0,
            revocable: revocable,
            revoked: false
        });

        beneficiarySchedules[beneficiary].push(scheduleId);

        emit ScheduleCreated(scheduleId, beneficiary, amount);
    }

    // Release vested tokens
    function release(bytes32 scheduleId) external {
        VestingSchedule storage schedule = vestingSchedules[scheduleId];

        require(schedule.beneficiary == msg.sender, "Not beneficiary");
        require(!schedule.revoked, "Schedule revoked");

        uint256 releasable = calculateReleasableAmount(scheduleId);
        require(releasable > 0, "No tokens to release");

        schedule.releasedAmount += releasable;

        token.transfer(schedule.beneficiary, releasable);

        emit TokensReleased(scheduleId, schedule.beneficiary, releasable);
    }

    // Revoke a vesting schedule
    function revoke(bytes32 scheduleId) external onlyOwner {
        VestingSchedule storage schedule = vestingSchedules[scheduleId];

        require(schedule.revocable, "Schedule not revocable");
        require(!schedule.revoked, "Schedule already revoked");

        uint256 releasable = calculateReleasableAmount(scheduleId);
        uint256 refund = schedule.totalAmount - schedule.releasedAmount - releasable;

        schedule.revoked = true;

        if (releasable > 0) {
            schedule.releasedAmount += releasable;
            token.transfer(schedule.beneficiary, releasable);
            emit TokensReleased(scheduleId, schedule.beneficiary, releasable);
        }

        if (refund > 0) {
            token.transfer(owner, refund);
        }

        emit ScheduleRevoked(scheduleId, schedule.beneficiary, refund);
    }

    // Calculate releasable amount
    function calculateReleasableAmount(bytes32 scheduleId) public view returns (uint256) {
        VestingSchedule storage schedule = vestingSchedules[scheduleId];

        if (schedule.revoked) {
            return 0;
        }

        if (block.timestamp < schedule.cliff) {
            return 0;
        }

        if (block.timestamp >= schedule.start + schedule.duration) {
            return schedule.totalAmount - schedule.releasedAmount;
        }

        uint256 timeFromStart = block.timestamp - schedule.start;
        uint256 vestedAmount = (schedule.totalAmount * timeFromStart) / schedule.duration;

        return vestedAmount - schedule.releasedAmount;
    }

    // Get vesting schedule info
    function getVestingSchedule(bytes32 scheduleId) external view returns (
        address beneficiary,
        uint256 start,
        uint256 cliff,
        uint256 duration,
        uint256 totalAmount,
        uint256 releasedAmount,
        bool revocable,
        bool revoked
    ) {
        VestingSchedule storage schedule = vestingSchedules[scheduleId];

        return (
            schedule.beneficiary,
            schedule.start,
            schedule.cliff,
            schedule.duration,
            schedule.totalAmount,
            schedule.releasedAmount,
            schedule.revocable,
            schedule.revoked
        );
    }

    // Get beneficiary's schedule count
    function getBeneficiaryScheduleCount(address beneficiary) external view returns (uint256) {
        return beneficiarySchedules[beneficiary].length;
    }

    // Get beneficiary's schedule ID by index
    function getBeneficiarySchedule(address beneficiary, uint256 index) external view returns (bytes32) {
        require(index < beneficiarySchedules[beneficiary].length, "Index out of bounds");
        return beneficiarySchedules[beneficiary][index];
    }
}

11.7 Practical Example: Decentralized Autonomous Organization (DAO)

Let's create a comprehensive DAO that incorporates many advanced concepts:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

/**
 * @title AdvancedDAO
 * @dev A comprehensive DAO implementation with advanced features
 */
contract AdvancedDAO {
    // Token used for governance
    IERC20 public governanceToken;

    // Timelock delay for proposal execution
    uint256 public timelock;

    // Proposal threshold (minimum tokens to create proposal)
    uint256 public proposalThreshold;

    // Quorum (minimum participation for valid vote)
    uint256 public quorum;

    // Proposal struct
    struct Proposal {
        uint256 id;
        address proposer;
        address[] targets;
        uint256[] values;
        bytes[] calldatas;
        string description;
        uint256 startBlock;
        uint256 endBlock;
        uint256 executionTime;
        bool executed;
        bool canceled;
        uint256 forVotes;
        uint256 againstVotes;
        uint256 abstainVotes;
        mapping(address => Receipt) receipts;
    }

    // Vote receipt
    struct Receipt {
        bool hasVoted;
        uint8 support; // 0 = against, 1 = for, 2 = abstain
        uint256 votes;
    }

    // Delegate info
    struct DelegateInfo {
        address delegatee;
        uint256 delegatedVotes;
    }

    // State variables
    mapping(uint256 => Proposal) public proposals;
    uint256 public proposalCount;

    mapping(address => DelegateInfo) public delegates;
    mapping(address => mapping(uint256 => bool)) public hasVoted;

    // Events
    event ProposalCreated(
        uint256 indexed proposalId,
        address indexed proposer,
        address[] targets,
        uint256[] values,
        bytes[] calldatas,
        string description,
        uint256 startBlock,
        uint256 endBlock
    );

    event VoteCast(
        address indexed voter,
        uint256 indexed proposalId,
        uint8 support,
        uint256 votes,
        string reason
    );

    event ProposalExecuted(uint256 indexed proposalId);
    event ProposalCanceled(uint256 indexed proposalId);
    event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

    // Custom errors
    error InsufficientVotingPower();
    error ProposalAlreadyExists();
    error InvalidProposalState();
    error TimelockNotMet();
    error ExecutionFailed();
    error AlreadyVoted();
    error InvalidVoteType();

    constructor(
        address _governanceToken,
        uint256 _timelock,
        uint256 _proposalThreshold,
        uint256 _quorum
    ) {
        governanceToken = IERC20(_governanceToken);
        timelock = _timelock;
        proposalThreshold = _proposalThreshold;
        quorum = _quorum;
    }

    // Create a proposal
    function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    ) external returns (uint256) {
        // Check proposal threshold
        uint256 votingPower = getVotingPower(msg.sender);
        if (votingPower < proposalThreshold) {
            revert InsufficientVotingPower();
        }

        // Validate proposal parameters
        require(targets.length > 0, "Empty proposal");
        require(targets.length == values.length && targets.length == calldatas.length, "Mismatched proposal parameters");

        uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description)));

        // Check if proposal already exists
        if (proposals[proposalId].startBlock != 0) {
            revert ProposalAlreadyExists();
        }

        uint256 startBlock = block.number + 1;
        uint256 endBlock = startBlock + 40320; // ~1 week at 15s blocks

        Proposal storage proposal = proposals[proposalId];
        proposal.id = proposalId;
        proposal.proposer = msg.sender;
        proposal.targets = targets;
        proposal.values = values;
        proposal.calldatas = calldatas;
        proposal.description = description;
        proposal.startBlock = startBlock;
        proposal.endBlock = endBlock;

        proposalCount++;

        emit ProposalCreated(
            proposalId,
            msg.sender,
            targets,
            values,
            calldatas,
            description,
            startBlock,
            endBlock
        );

        return proposalId;
    }

    // Cast a vote on a proposal
    function castVote(uint256 proposalId, uint8 support, string calldata reason) external {
        if (hasVoted[msg.sender][proposalId]) {
            revert AlreadyVoted();
        }

        if (support > 2) {
            revert InvalidVoteType();
        }

        Proposal storage proposal = proposals[proposalId];

        // Check if proposal is active
        if (block.number <= proposal.startBlock || block.number > proposal.endBlock) {
            revert InvalidProposalState();
        }

        uint256 votes = getVotingPower(msg.sender);

        // Record vote
        hasVoted[msg.sender][proposalId] = true;
        proposal.receipts[msg.sender] = Receipt({
            hasVoted: true,
            support: support,
            votes: votes
        });

        // Update vote counts
        if (support == 0) {
            proposal.againstVotes += votes;
        } else if (support == 1) {
            proposal.forVotes += votes;
        } else {
            proposal.abstainVotes += votes;
        }

        emit VoteCast(msg.sender, proposalId, support, votes, reason);
    }

    // Queue a successful proposal for execution
    function queue(uint256 proposalId) external {
        Proposal storage proposal = proposals[proposalId];

        // Check if proposal can be queued
        if (proposal.executed || proposal.canceled || block.number <= proposal.endBlock) {
            revert InvalidProposalState();
        }

        // Check if proposal succeeded
        if (!isProposalSucceeded(proposalId)) {
            revert InvalidProposalState();
        }

        // Set execution time
        proposal.executionTime = block.timestamp + timelock;

        // Additional logic for queueing in a real implementation
    }

    // Execute a queued proposal
    function execute(uint256 proposalId) external {
        Proposal storage proposal = proposals[proposalId];

        // Check if proposal can be executed
        if (proposal.executed || proposal.canceled || proposal.executionTime == 0) {
            revert InvalidProposalState();
        }

        // Check timelock
        if (block.timestamp < proposal.executionTime) {
            revert TimelockNotMet();
        }

        proposal.executed = true;

        // Execute each action
        for (uint256 i = 0; i < proposal.targets.length; i++) {
            (bool success, ) = proposal.targets[i].call{value: proposal.values[i]}(proposal.calldatas[i]);
            if (!success) {
                revert ExecutionFailed();
            }
        }

        emit ProposalExecuted(proposalId);
    }

    // Cancel a proposal
    function cancel(uint256 proposalId) external {
        Proposal storage proposal = proposals[proposalId];

        // Only proposer or if proposer's voting power dropped below threshold
        require(
            msg.sender == proposal.proposer || 
            getVotingPower(proposal.proposer) < proposalThreshold,
            "Cannot cancel"
        );

        // Check if proposal can be canceled
        if (proposal.executed || proposal.canceled) {
            revert InvalidProposalState();
        }

        proposal.canceled = true;

        emit ProposalCanceled(proposalId);
    }

    // Delegate voting power
    function delegate(address delegatee) external {
        address currentDelegate = delegates[msg.sender].delegatee;
        delegates[msg.sender].delegatee = delegatee;

        emit DelegateChanged(msg.sender, currentDelegate, delegatee);

        // Update delegated votes
        uint256 votingPower = governanceToken.balanceOf(msg.sender);

        if (currentDelegate != address(0)) {
            delegates[currentDelegate].delegatedVotes -= votingPower;
        }

        if (delegatee != address(0)) {
            delegates[delegatee].delegatedVotes += votingPower;
        }
    }

    // Get voting power (own balance + delegated votes)
    function getVotingPower(address account) public view returns (uint256) {
        uint256 ownVotes = governanceToken.balanceOf(account);
        uint256 delegatedVotes = delegates[account].delegatedVotes;

        return ownVotes + delegatedVotes;
    }

    // Check if a proposal has succeeded
    function isProposalSucceeded(uint256 proposalId) public view returns (bool) {
        Proposal storage proposal = proposals[proposalId];

        // Check quorum
        uint256 totalVotes = proposal.forVotes + proposal.againstVotes + proposal.abstainVotes;
        if (totalVotes < quorum) {
            return false;
        }

        // Check majority
        return proposal.forVotes > proposal.againstVotes;
    }

    // Get proposal state
    function getProposalState(uint256 proposalId) public view returns (string memory) {
        Proposal storage proposal = proposals[proposalId];

        if (proposal.canceled) {
            return "Canceled";
        }

        if (proposal.executed) {
            return "Executed";
        }

        if (block.number <= proposal.startBlock) {
            return "Pending";
        }

        if (block.number <= proposal.endBlock) {
            return "Active";
        }

        if (isProposalSucceeded(proposalId)) {
            if (proposal.executionTime == 0) {
                return "Succeeded";
            } else {
                if (block.timestamp >= proposal.executionTime) {
                    return "Queued";
                } else {
                    return "Timelocked";
                }
            }
        } else {
            return "Defeated";
        }
    }

    // Hash a proposal
    function hashProposal(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public pure returns (uint256) {
        return uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash)));
    }

    // Receive function to accept ETH
    receive() external payable {}
}

This DAO implementation incorporates numerous advanced concepts:

  1. Governance Token: Uses an ERC20 token for voting power
  2. Timelock: Adds a delay between proposal approval and execution
  3. Delegation: Allows users to delegate their voting power
  4. Proposal Lifecycle: Comprehensive proposal states (Pending, Active, Succeeded, Queued, Executed, Defeated, Canceled)
  5. Quorum: Ensures minimum participation for valid votes
  6. Multi-action Proposals: Supports multiple contract calls in a single proposal
  7. Vote Types: Supports for, against, and abstain votes
  8. Cancellation: Allows cancellation under specific conditions

11.8 Exercise: Create a Cross-Chain NFT Bridge

Task: Create a simplified cross-chain NFT bridge that allows NFTs to be transferred between chains.

Solution:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface IERC721 {
    function ownerOf(uint256 tokenId) external view returns (address);
    function transferFrom(address from, address to, uint256 tokenId) external;
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    function getApproved(uint256 tokenId) external view returns (address);
}

/**
 * @title NFTBridge
 * @dev A simplified cross-chain NFT bridge
 */
contract NFTBridge {
    // Bridge operator (in a real implementation, this would be a multi-sig or DAO)
    address public operator;

    // Mapping of supported NFT contracts
    mapping(address => bool) public supportedNFTs;

    // Mapping to track locked NFTs
    mapping(address => mapping(uint256 => bool)) public lockedNFTs;

    // Mapping to track used transfer hashes (prevent replay)
    mapping(bytes32 => bool) public usedTransferHashes;

    // Chain ID
    uint256 public chainId;

    // Events
    event NFTLocked(address indexed nftContract, uint256 indexed tokenId, address indexed sender, uint256 destinationChainId);
    event NFTReleased(address indexed nftContract, uint256 indexed tokenId, address indexed recipient, uint256 sourceChainId, bytes32 transferHash);

    // Custom errors
    error Unauthorized();
    error UnsupportedNFT();
    error NFTNotLocked();
    error TransferAlreadyProcessed();
    error InvalidSignature();

    constructor(uint256 _chainId) {
        operator = msg.sender;
        chainId = _chainId;
    }

    modifier onlyOperator() {
        if (msg.sender != operator) {
            revert Unauthorized();
        }
        _;
    }

    // Add or remove supported NFT contracts
    function setSupportedNFT(address nftContract, bool supported) external onlyOperator {
        supportedNFTs[nftContract] = supported;
    }

    // Lock an NFT to bridge it to another chain
    function lockNFT(address nftContract, uint256 tokenId, uint256 destinationChainId) external {
        if (!supportedNFTs[nftContract]) {
            revert UnsupportedNFT();
        }

        IERC721 nft = IERC721(nftContract);

        // Verify ownership or approval
        address owner = nft.ownerOf(tokenId);
        require(
            owner == msg.sender || 
            nft.getApproved(tokenId) == msg.sender || 
            nft.isApprovedForAll(owner, msg.sender),
            "Not authorized to transfer NFT"
        );

        // Transfer NFT to this contract
        nft.transferFrom(owner, address(this), tokenId);

        // Mark as locked
        lockedNFTs[nftContract][tokenId] = true;

        // Emit event for off-chain bridge to pick up
        emit NFTLocked(nftContract, tokenId, msg.sender, destinationChainId);
    }

    // Release an NFT that was bridged from another chain
    function releaseNFT(
        address nftContract,
        uint256 tokenId,
        address recipient,
        uint256 sourceChainId,
        bytes32 transferHash,
        bytes calldata signature
    ) external onlyOperator {
        // Verify NFT is supported
        if (!supportedNFTs[nftContract]) {
            revert UnsupportedNFT();
        }

        // Verify transfer hasn't been processed
        if (usedTransferHashes[transferHash]) {
            revert TransferAlreadyProcessed();
        }

        // Verify signature
        bytes32 messageHash = keccak256(abi.encodePacked(
            nftContract,
            tokenId,
            recipient,
            sourceChainId,
            chainId,
            transferHash
        ));

        bytes32 ethSignedMessageHash = keccak256(abi.encodePacked(
            "\x19Ethereum Signed Message:\n32",
            messageHash
        ));

        address signer = recoverSigner(ethSignedMessageHash, signature);
        if (signer != operator) {
            revert InvalidSignature();
        }

        // Mark transfer as processed
        usedTransferHashes[transferHash] = true;

        // If NFT is locked on this chain, unlock it
        if (lockedNFTs[nftContract][tokenId]) {
            lockedNFTs[nftContract][tokenId] = false;
        }

        // Transfer NFT to recipient
        IERC721(nftContract).safeTransferFrom(address(this), recipient, tokenId);

        // Emit event
        emit NFTReleased(nftContract, tokenId, recipient, sourceChainId, transferHash);
    }

    // Recover signer from signature
    function recoverSigner(bytes32 messageHash, bytes memory signature) internal pure returns (address) {
        require(signature.length == 65, "Invalid signature length");

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := byte(0, mload(add(signature, 96)))
        }

        if (v < 27) {
            v += 27;
        }

        return ecrecover(messageHash, v, r, s);
    }

    // Emergency function to recover stuck NFTs
    function emergencyWithdraw(address nftContract, uint256 tokenId, address recipient) external onlyOperator {
        IERC721(nftContract).safeTransferFrom(address(this), recipient, tokenId);
    }
}

This NFT bridge implementation includes:

  1. Cross-Chain Communication: Uses events and signatures for cross-chain messaging
  2. Security Measures: Prevents replay attacks with transfer hashes
  3. Signature Verification: Verifies operator signatures for releasing NFTs
  4. Emergency Recovery: Includes an emergency withdrawal function
  5. Flexible Design: Supports multiple NFT contracts

Conclusion

Advanced smart contract development involves sophisticated techniques and patterns that go beyond basic Solidity programming. By mastering these concepts, you can build complex, secure, and efficient decentralized applications.

Key takeaways from this module:

  1. Oracles provide a bridge between smart contracts and external data
  2. Cross-chain communication enables interoperability between different blockchains
  3. Advanced security techniques like formal verification and timelocks enhance contract security
  4. Complex architectures like the Diamond Pattern enable modular and upgradeable contracts
  5. Advanced tokenomics create sophisticated economic incentives

With these advanced techniques, you can push the boundaries of what's possible with smart contracts and build the next generation of decentralized applications.

In the next module, we'll explore practical examples and projects that apply all the concepts we've learned throughout this course.