Skip to content

Instantly share code, notes, and snippets.

@ritzdorf
Last active September 27, 2022 12:03
Show Gist options
  • Save ritzdorf/1c6bd72955391e831f8a397d3152b4e0 to your computer and use it in GitHub Desktop.
Save ritzdorf/1c6bd72955391e831f8a397d3152b4e0 to your computer and use it in GitHub Desktop.
Recently Executed Transactions affected by EIP1884

Recently Executed Contracts with Issues in EIP 1884

EIP 1884 is set to be implemented into the upcoming Ethereum 'Istanbul' hard fork. It

  • increases the cost of opcode SLOAD from 200 to 800 gas
  • increases the cost of BALANCE and EXTCODEHASH from 400 to 700 gas
  • adds a new opcode SELFBALANCE with cost 5.

What is this?

  • Here we list our analysis on contracts might stop working with EIP-1884.
  • This is not to blame the developers (as they generally followed best practices) but to raise awareness of the issue so developers can redeploy or take other measures.
  • In our analysis we have focused on recently executed contracts, it is therefore not complete
  • Below we list contracts that get called by other contracts with a fixed amount of gas. The most common case for calls with a fixed amount of gas is Solidity's .transfer() function, but Solidity also allows to add .gas(X) to calls to only pass X amount of gas. And there are several other sources. The receiving contracts currently successfully execute but would run out-of-gas due to the higher gas costs of EIP-1884.
  • Frequency of affected transactions:
    • ~0.15 before block 8476979
    • roughly 0.093 between blocks 8476979 and 8572236
    • 0.172 between blocks 8572241 and 8784933
    • Mostly these transactions are related to the first two contracts listed below.
  • For more analysis see: https://github.com/holiman/eip-1884-security
  • For issues regarding ugradability and OpenZeppelin Proxies, see: https://forum.openzeppelin.com/t/openzeppelin-upgradeable-contracts-affected-by-istanbul-hardfork/1616
  • Please contact @HRitzdorf on Telegram or Twitter in case of questions or comments
  • Called primarily through internal transactions, > 2,8 million message calls
  • Fallback function:
    function() public payable {
        require(reserveType[msg.sender] != ReserveType.NONE);
        EtherReceival(msg.sender, msg.value);
    }
  • 4,263 ETH, over 70,000 internal transactions
  • Fallback function:
    function () public payable {
        require(total() + msg.value <= limit);
    }
  • Triggers 2 SLOADS and 1 BALANCE
  • Currently has 1342 gas left, would break with EIP 1884
  • Recently deployed, August 12th, 200 Transactions since
    // Gas stipends for acceptRelayedCall, preRelayedCall and postRelayedCall
    uint256 constant private acceptRelayedCallMaxGas = 50000;
    uint256 constant private preRelayedCallMaxGas = 100000;
    uint256 constant private postRelayedCallMaxGas = 100000;
  • Example Issue: Used to call 0x557f91c8ea60aaf2c33b9be3a80ac691103515f4, currently consumes 33538 of 50000 gas, but performs 96 SLOADs and hence would break in the future, Example TX
  • Deployed once per Aragon organisation (rougly 600)
  • Relevant Code:
    function isDepositable() public view returns (bool) {
        return DEPOSITABLE_POSITION.getStorageBool();
    }

    event ProxyDeposit(address sender, uint256 value);

    function () external payable {
        // send / transfer
        if (gasleft() < FWD_GAS_LIMIT) {
            require(msg.value > 0 && msg.data.length == 0);
            require(isDepositable());
            emit ProxyDeposit(msg.sender, msg.value);
        } else { // all calls except for send or transfer
            address target = implementation();
            delegatedFwd(target, msg.data);
        }
    }
}
  • The current execution of the fallback function in case of a send/transfer consumes 1759 gas. It contains one SLOAD due to isDepositable(). Therefore, it will break with EIP-1884.
  • Example TX
  • Issue at Aragon
  • This contract calls the trade function of the very popular TokenStore contract with 100,000 gas - Source
  • Contract had over 2,000 transactions at time of writing
  • For certain trades the 100,000 gas will still be sufficient after the hard fork, but for certain trades the 100,000 gas won't be sufficient where they currently are
  • Example transaction that would fail in the future:
    • Action 6 in parity trace: 100,000 gas provided, 95,728 gas used, 13 SLOADs, hence will break
    • Action 8 in parity trace: 95,728 gas provided (all the remaining gas), 95,728 gas used, 13 SLOADs, hence will break even if 100,000 gas would be provided
  • This is an example where the sender contract should be redeployed
  • MonsterBit is a collectible game on Ethereum
  • The affected contract had been the entry point of over 36,000 transactions at the time of writing
  • Problematic fallback function can have different number of SLOADs: (Source)
def _fallback() payable: # default function
  if saleAuctionAddress != caller:
      if siringAuctionAddress != caller:
          if unknowne4c73abeAddress != caller:
              require caller == unknowna6531d34Address
  • Any invocation with three or more SLOADs will break. Current cost is 964 gas in case of three SLOADs.
  • Example TX: One call has 3 and another has 4 SLOADs
  • 488 transactions and 11.8 ETH
  • Problematic fallback function only emits a single event: (Source)
def _fallback() payable: # default function
  log Transfer(
        address from=call.value,
        address to=caller,
        uint256 value=owner)
  • Performs single SLOAD for owner
  • Current cost 2135 gas, would break after hard fork
  • Example TX
  • PickFlix is a Weekly Fantasy Movie Game
  • Affected Contracts have an expensive event inside their fallback function: (Source)
def _fallback() payable: # default function
  log Received(
        address user=call.value,
        uint256 ethers=eth.balance(this.address),
        uint256 tokens=caller)
  • It is an example where the gas cost increase of BALANCE is the problem
  • Current cost: 2149 gas, and hence would break after hard fork
  • Example TXs: 1, 2, 3
  • Totle combines multiple Decentralized Exchange Platforms (e.g. KyberSwap)
  • When the TotlePrimary contract executes the performSwap function, it might execute the minimumRateFailed function, which internally calls getDecimals on the target token contract. As part of this call, getDecimals only passes 5000 gas:
 let success := call( 
                     5000, // Amount of gas 
                     token, // Address to call 
                     0, // ether to send 
  • If this the token is Augur's REP token, then the Delegator will be called. This call currently consumes 4511 gas and successfully returns 18. However, this call also contains three invocations of SLOAD, so it will cost 6311 after the activation of the Istanbul hard fork. Hence, it will break after the hard fork.
  • As certain tokens do not implement the decimals function the failing call (due to out-of-gas) will be interpreted as 0 decimals. As the decimals are used to calculate token quantities, incorrect token quantities will be computed. Therefore, users would be as the security check for the minimum rate could be circumvented.
  • Issue at Totle
  • Example TX trading 0.06978 Ether into 1.497 Reputation (REP) tokens
  • Involved in complex transactions
  • Currently holds ~6.8 ETH
  • Fallback function is called with 2300 gas (Source):
def _fallback() payable: # default function
  if stor3 != tx.origin:
      if stor4 != tx.origin:
          require tx.origin == stor5
  • Currently consumes 1363 in case the tx.origin is stor5 (== 0xdead3b4bdb1c7160196c41fd1bab168f71486123). Hence, it would break with EIP-1884.
  • Example TXs (note that the TXs fail, but that is unrelated of this contract): 1, 2, 3
  • The BZxProxy is part of BZx
  • According to the documentation it provides the entry point to the bzx system, which enables trading and lending
  • As part of more complex trading transactions (Example) it sometimes withdraws WETH. This causes the ETH to be sent using .transfer() to the BZxProxy
  • The execution of the fallback function currently consumes 2135 but would break after Istanbul as the proxy does 2 storage lookups, hence triggering 2 SLOADs
  • Therefore, transactions that include such WETH withdrawals or similar calls would break with EIP-1884
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment