Solidity: Transaction-Ordering Attacks

Chris Coverdale
Coinmonks
4 min readMar 6, 2018

--

This article will explain an attack vector in Ethereum Smart Contracts known as Transaction Ordering/Front Running Attacks.

Why?

  • Best practices to create secure Smart Contracts
  • Identify a common attack
  • Ensure funds aren’t lost to programming errors

Transaction-Ordering Attack

  • A Transaction-Ordering Attack is a race condition attack
  • To put it simply if you purchase an item at a price advertised, you expect to pay that price
  • A transaction-ordering attack will change the price during the processing of your transaction because some one else (the contract owner, miner or another user) has sent a transaction modifying the price before your transaction is complete
  • Two transactions can be sent to the mempool/tx-pool, the order in which they arrive is irrelevant
  • The gas sent with the transaction is vital in this scenario as it determines which transaction is mined first
  • An attacker could also be a miner, as the miner can choose the order in which the transactions are mined
  • This creates a problem in Smart Contracts that rely on the state of storage variables to remain at certain value according to the order of transactions

Contract

  • The contract below will act as an intermediary that can provide a digital asset for a certain price
  • The price is recorded in the storage variable uint256 price
  • The price can be changed by the owner of the contract by calling the setPrice(uint256 _price) function
pragma solidity ^0.4.18;

contract TransactionOrdering {
uint256 price;
address owner;

event Purchase(address _buyer, uint256 _price);
event PriceChange(address _owner, uint256 _price);

modifier ownerOnly() {
require(msg.sender == owner);
_;
}

function TransactionOrdering() {
// constructor
owner = msg.sender;
price = 100;
}

function buy() returns (uint256) {
Purchase(msg.sender, price);
return price;
}

function setPrice(uint256 _price) ownerOnly() {
price = _price;
PriceChange(owner, price);
}
}

Contract is deployed at: 0xfd3673a4fd729ee501cbacd4aac97741e287d318

Get Best Software Deals Directly In Your Inbox

Attack Scenario:

  1. The buyer of the digital asset will call the buy() function, to set a purchase at the price specified in the storage variable, with a starting price=100.

2. The contract owner will call setPrice() and update the price storage variable to price=150.

3. The contract owner will send the transaction with a higher gas fee.

4. The contract owner’s transaction will be mined first, updating the state of the contract due to the higher gas fee.

4. The buyers transaction gets mined soon after, but now the buy() function will be using the new updated price=150.

Solution

  • A solution is to use a transaction counter to “lock” in the agreed price
pragma solidity ^0.4.18;

contract SolutionTransactionOrdering {
uint256 price;
uint256 txCounter;
address owner;

event Purchase(address _buyer, uint256 _price);
event PriceChange(address _owner, uint256 _price);

modifier ownerOnly() {
require(msg.sender == owner);
_;
}
function getPrice() constant returns (uint256) {
return price;
}

function getTxCounter() constant returns (uint256) {
return txCounter;
}

function SolutionTransactionOrdering() {
// constructor
owner = msg.sender;
price = 100;
txCounter = 0;
}

function buy(uint256 _txCounter) returns (uint256) {
require(_txCounter == txCounter);
Purchase(msg.sender, price);
return price;
}

function setPrice(uint256 _price) ownerOnly() {
price = _price;
txCounter += 1;
PriceChange(owner, price);
}
}
  1. The buyer now makes a request to buy at the price shown at the time of sending the transaction.
  2. This is achieved by the buyer sending an integer which reflects the current state of the txCounter (function buy(uint256 _txCounter) )

3. When the contract owner updates the price, the txCounter is incremented (function setPrice(uint256 _price) ownerOnly() {... txCounter += 1} )

4. Now in the scenario of a transaction ordering attack, the buyers transaction would be reverted if the buyers txCounter integer was not the same as the current state in the contract

5. This means the price has changed during the processing of the buyers transaction and therefore would not be accepted

Contract is deployed at: 0x1abfe2f12447e877cb1bfe91a4e7eed0251b4d56

Conclusion

  • Transaction-Ordering Attacks are a common attack vector
  • Smart Contracts are inherently asynchronous since we have time discrepancies between sending transactions and the time to mine
  • All transactions are NOT created equal, different levels of gas associated with a transaction will incentivise miners to prioritise the order
  • If a contract provides a time sensitive service, state should be locked according to the order of transactions

--

--

Chris Coverdale
Coinmonks

Blockchain Developer - Founder - Pale Fire Technologies, working on Bitcoin and LND