Lesson 9 of 23
In Progress

Transaction-Ordering Dependence (TOD)

Transaction-ordering dependence, also known as TOD, is a type of vulnerability that can occur in smart contracts that rely on the order in which transactions are processed. These vulnerabilities can allow an attacker to manipulate the order of transactions and potentially exploit a contract’s logic. In this article, we’ll explore what transaction-ordering dependence is and how to prevent it in your smart contracts.

What is Transaction-Ordering Dependence?

Transaction-ordering dependence occurs when a contract’s behavior depends on the order in which transactions are processed. This can happen if a contract’s logic checks the state of the contract before and after a transaction is processed, and the behavior of the contract depends on the difference between these two states.

Here is an example of a contract that is vulnerable to transaction-ordering dependence:

pragma solidity ^0.6.0;

contract TODVulnerability {
    uint public balance;

    function deposit() public payable {
        balance += msg.value;
    }

    function withdraw(uint amount) public {
        require(balance >= amount);
        balance -= amount;
    }
}

In this contract, the deposit function allows a caller to send ether to the contract and increments the balance variable to track the total amount of ether received. The withdraw function allows a caller to withdraw a specified amount of ether, as long as the balance variable is greater than or equal to the amount being withdrawn.

However, this contract is vulnerable to transaction-ordering dependence because the withdraw function does not check the current balance before processing the transaction. An attacker could potentially exploit this vulnerability by calling the deposit function after calling the withdraw function, causing the withdraw function to incorrectly allow the withdrawal of more ether than the contract has on hand.

How to Prevent Transaction-Ordering Dependence

There are several ways to prevent transaction-ordering dependence in your smart contracts. One common method is to use a mutex, which is a mechanism that allows only one transaction to be processed at a time.

Here is an example of a contract that uses a mutex to prevent transaction-ordering dependence:

pragma solidity ^0.6.0;

contract TODSafe {
    uint public balance;
    bool mutex;

    function deposit() public payable {
        require(!mutex); // prevent TOD
        mutex = true;
        balance += msg.value;
        mutex = false;
    }

    function withdraw(uint amount) public {
        require(balance >= amount);
        require(!mutex); // prevent TOD
        mutex = true;
        balance -= amount;
        mutex = false;
    }
}

In this contract, the mutex variable acts as a flag that prevents multiple transactions from being processed at the same time. The require statements at the beginning of the deposit and withdraw functions check the value of the mutex variable and throw an exception if it is true, which prevents transaction-ordering dependence.

Another method to prevent transaction-ordering dependence is to use the blockhash function, which returns the hash of a block at a particular block height. By using the blockhash function in your contract’s logic, you can ensure that the contract’s behavior is not dependent on the order of transactions.

Here is an example of a contract that uses the blockhash function to prevent transaction-ordering dependence:

pragma solidity ^0.6.0;

contract TODSafe {
    uint public balance;

    function deposit() public payable {
        balance += msg.value;
    }

    function withdraw(uint amount) public {
        require(balance >= amount);
        require(blockhash(block.number - 1) == 0); // prevent TOD
        balance -= amount;
    }
}

In this contract, the require statement in the withdraw function checks the hash of the previous block to ensure that it is not equal to zero. This ensures that the withdraw function is not called until the deposit function has completed, which prevents transaction-ordering dependence.

Conclusion

Transaction-ordering dependence can be a serious vulnerability in smart contracts if not properly addressed. By using a mutex or the blockhash function, you can ensure that your contracts are not vulnerable to these attacks and maintain their security.

Exercises

To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.

Write a Solidity contract that defines a function named deposit that allows a caller to send ether to the contract and increments a balance variable to track the total amount of ether received. The contract should be vulnerable to transaction-ordering dependence.

pragma solidity ^0.6.0;

contract TODVulnerability {
    uint public balance;

    function deposit() public payable {
        balance += msg.value;
    }
}

Write a Solidity contract that defines a function named withdraw that allows a caller to withdraw a specified amount of ether from the contract. The contract should be vulnerable to transaction-ordering dependence.

pragma solidity ^0.6.0;

contract TODVulnerability {
    uint public balance;

    function withdraw(uint amount) public {
        require(balance >= amount);
        balance -= amount;
    }
}

Modify the TODVulnerability contract from Exercise 1 to use a mutex to prevent transaction-ordering dependence.

pragma solidity ^0.6.0;

contract TODSafe {
    uint public balance;
    bool mutex;

    function deposit() public payable {
        require(!mutex); // prevent TOD
        mutex = true;
        balance += msg.value;
        mutex = false;
    }
}

Modify the TODVulnerability contract from Exercise 2 to use the blockhash function to prevent transaction-ordering dependence.

pragma solidity ^0.6.0;

contract TODSafe {
    uint public balance;

    function withdraw(uint amount) public {
        require(balance >= amount);
        require(blockhash(block.number - 1) == 0); // prevent TOD
        balance -= amount;
    }
}

Write a Solidity contract that defines a function named deposit that allows a caller to send ether to the contract and increments a balance variable to track the total amount of ether received. The contract should define a function named withdraw that allows a caller to withdraw a specified amount of ether from the contract. The contract should use a mutex to prevent transaction-ordering dependence.

pragma solidity ^0.6.0;

contract TODSafe {
    uint public balance;
    bool mutex;

    function deposit() public payable {
        require(!mutex); // prevent TOD
        mutex = true;
        balance += msg.value;
        mutex = false;
    }

    function withdraw(uint amount) public {
        require(balance >= amount);
        require(!mutex); // prevent TOD
        mutex = true;
        balance -= amount;
        mutex = false;
    }
}