Solidity Pitfalls and Hazards Part Four CS 1951

















































![function sell. Tokens(uint tokens) public { require( balances[msg. sender] >= tokens); uint eth = function sell. Tokens(uint tokens) public { require( balances[msg. sender] >= tokens); uint eth =](https://slidetodoc.com/presentation_image/d0ba66a2d09297d249af60e9beeee92c/image-50.jpg)















- Slides: 65
Solidity Pitfalls and Hazards (Part Four) CS 1951 L Spring 2019 5 March 2019 Maurice Herlihy Brown University
Today’s Pitfalls: Denial of Service Block Timestamp Manipulation Constructor Names Uninitialized Storage References Floating Point Precision Tx. Origin and Phishing 2
Denial of Service Attacks Parties leave a contract inoperable for short period … or sometimes a long period … trapping ether in contract 3
function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); // 5 times the wei sent } function distribute() public { require(ms function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); // 5 times the wei sent } function distribute() public { require( Inflatable Data Structures function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); } function distribute() public { require(msg. sender == owner); for(uint i=0; i<investors. length; i++) { Load with ether transfer. Token(investors[i], investor. Tokens[i]); } } 4
function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); // 5 times the wei sent } function distribute() public { require(ms function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); // 5 times the wei sent } function distribute() public { require( Inflatable Data Structures function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); } function distribute() public { require(msg. sender == owner); for(uint i=0; i<investors. length; i++) { accumulate investments transfer. Token(investors[i], investor. Tokens[i]); } } 5
function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); // 5 times the wei sent } function distribute() public { require(ms function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); // 5 times the wei sent } function distribute() public { require( Inflatable Data Structures function invest() public payable { investors. push(msg. sender); attacker can inflate array size until * 5); investor. Tokens. push(msg. value gas required by loop exceeds block gas limit } locking up tokens forever function distribute() public { require(msg. sender == owner); for(uint i=0; i<investors. length; i++) { transfer. Token(investors[i], investor. Tokens[i]); } } 6
function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); // 5 times the wei sent } function distribute() public { require(ms function invest() public payable { investors. push(msg. sender); investor. Tokens. push(msg. value * 5); // 5 times the wei sent } function distribute() public { require( Inflatable Data Structures function invest() public payable { distribute tokens to investors. push(msg. sender); investor. Tokens. push(msg. value * 5); } function distribute() public { require(msg. sender == owner); for(uint i=0; i<investors. length; i++) { transfer. Token(investors[i], investor. Tokens[i]); } } 7
External Call Progression to next state requires call sending ether to investor? Investor’s contract doesn’t accept ether? Input from external source? Source malicious, or DDOS victim 8
Prevention Use individual withdrawal pattern Use multisigs to avoid single point of failure Or use timelock to finalize 9
This Happened Govern. Mental was a Ponzi scheme Accumulated lots of ether Payoff function deletes a mapping so large … Gas cost exceeds max block limit. 1100 ether stuck 10
Fomo 3 D Satirizes ICO and Ponzi schemes, while maybe being those things. “socioeconomic performance art” more traffic than Crypto. Kitties 11
Goal: Purchase last key before the timer goes to zero Purchsing key increases timer, key price If timer reach zero, winner gets half the rest is complicated: dividends, etc. 12
Classical “dollar auction” paradox Say, pot has $100 bid $1, pot is $101, and you stand to win it. Bargain! Someone else bids $1, now $102 and you won’t win for only $1 above your sunk costs, you win again. . That counter should never go to zero! 13
Forensic Evidence Block containing winner txn is normal: 92 txns But next 11 blocks had many fewer txns! All with unusually high txn fees all to same contract 14
Issues lots of transactions where High gas price means … transaction likely to be chosen first for block High gas limit means … Transaction uses up block gas limit … and squeezes out other transactions 15
Contract also called get. Current. Round. Info() to check timer state to check whether attacker’s key is last If good, revert() to use up gas, squeeze out later transactions If bad, wait and don’t use gas 16
In Summary winner launched many txns high gas price, high gas limit squeezes out competing transactions Consumes gas limit only when promising many contracts, different addresses, limits … Spent about $11 K fees to win $3 M 17
Block Timestamp Manipulation Block timestamps used for randomness (bad idea) escrowing funds time-dependent state changes Danger: miners can manipulate slightly 18
contract Roulette { uint public past. Block. Time; constructor() public payable {} function () public payable { require(msg. value == 10 ether); require(now != past. Block. Time); past. Block. Time = now; if (now % 15 == 0) { msg. sender. transfer(this. balance); }}} 19
contract Roulette { uint public past. Block. Time; constructor() public payable {} function () public payable { forces one bet per block == 10 ether); require(msg. value require(now != past. Block. Time); past. Block. Time = now; if (now % 15 == 0) { msg. sender. transfer(this. balance); }}} 20
contract Roulette { uint public past. Block. Time; constructor() public payable {} function () public payable { require(msg. value == 10 ether); require(nowinitially fund contract != past. Block. Time); past. Block. Time = now; if (now % 15 == 0) { msg. sender. transfer(this. balance); }}} 21
contract Roulette { uint public past. Block. Time; constructor() public payable {} function () public payable { require(msg. value == 10 ether); require(nowbet via fallback function != past. Block. Time); past. Block. Time = now; if (now % 15 == 0) { msg. sender. transfer(this. balance); }}} 22
contract Roulette { uint public past. Block. Time; constructor() public payable {} function () public payable { require(msg. value == 10 ether); require(now != past. Block. Time); past. Block. Time = now; must bet 10 ether to play if (now % 15 == 0) { msg. sender. transfer(this. balance); }}} 23
contract Roulette { uint public past. Block. Time; constructor() public payable {} only one transaction per block function () public payable { require(msg. value == 10 ether); require(now != past. Block. Time); past. Block. Time = now; if (now % 15 == 0) { msg. sender. transfer(this. balance); }}} 24
contract Roulette { uint public past. Block. Time; constructor() public payable {} function () public payable { require(msg. value == 10 ether); synonym for block. timestamp require(now != past. Block. Time); past. Block. Time = now; if (now % 15 == 0) { msg. sender. transfer(this. balance); }}} 25
contract Roulette { uint public past. Block. Time; constructor() public payable {} function () public payable { require(msg. value == 10 ether); Winner? require(now != past. Block. Time); past. Block. Time = now; if (now % 15 == 0) { msg. sender. transfer(this. balance); }}} What’s wrong with this? 26
Miners can adjust timestamp within limits increasing not “too far” in future Miner can ensure any bet loses … … Collecting block reward, increasing contract pool (Also front-running attack) 27
Prevention Never use timestamps for entropy Avoid sensitive decisions based on small timestamp differences For time-sensitive logic, maybe use (block. number X average block time) 28
This Happened Govern. Mental Ponzi again Pays back one player per round Last to join round for at least 1 min wins Easy for miners to manipulate winner 29
Fun With Constructors In early Solidity, a constructor was syntactically just a function with same name as the contract. Semantically, constructors not just another function If contract name changes, but constructor doesn’t, then constructor becomes normal, callable function This was fixed after the predictable disasters 30
contract Owner. Wallet { address public owner; function owner. Wallet(address _owner) public { owner = _owner; } function () payable {} function withdraw() public { require(msg. sender == owner); msg. sender. transfer(this. balance); }} 31
contract Owner. Wallet { address public owner; function owner. Wallet(address _owner) public { collect ether via fallback function owner = _owner; } function () payable {} function withdraw() public { require(msg. sender == owner); msg. sender. transfer(this. balance); }} 32
contract Owner. Wallet { address public owner; function owner. Wallet(address _owner) public { only owner can collect ether owner = _owner; } function () payable {} function withdraw() public { require(msg. sender == owner); msg. sender. transfer(this. balance); }} 33
contract Owner. Wallet { address public owner; function owner. Wallet(address _owner) public { owner = _owner; } function () payable {} constructor initializes owner function withdraw() public { require(msg. sender == owner); msg. sender. transfer(this. balance); }} 34
contract Owner. Wallet { address public owner; BOOM! function owner. Wallet(address _owner) public { owner = _owner; } Anyone can call owner. Wallet, become function () payable {} functionthe owner and take all the ether. withdraw() public { require(msg. sender == owner); msg. sender. transfer(this. balance); }} 35
Prevention Latest compiler requires constructor syntax Included here as language design lesson “Do not put a stumbling block before the blind” 36
This Happened Rubixi: worst Ethereum Ponzi ever? Originally named Dynamic. Pyramid They forgot to change the constructor name before deploying Code is law! Predators repeatedly called Dynamic. Pyramid() to confiscate investments by gullable public 37
Uninitialized Storage Pointers Early versions of Solidity allowed unitialized storage variables These variables could secretly point to other storage variables Leading to (intentional or not) vulnerabilities This is no longer allowed by recent compilers 38
contract Hodl. Fraud { … constructor() public payable { … HODL contract … } function pay. In(uint hold. Time) Users pay ether … public payable { … Locked up for some duration } function withdraw () public { … Avoid temptation to sell in volatile times } function owner. Withdrawal () public { And, of course, it steals your money … 39 }}
contract Hodl. Fraud { uint owner. Amount; uint number. Of. Payouts; address owner; struct Hold. Record { uint amount; uint unlock. Time; } mapping (address => Hold. Record) … } 40
contract Hodl. Fraud { uint owner. Amount; balance information uint number. Of. Payouts; address owner; struct Hold. Record { uint amount; uint unlock. Time; } mapping (address => Hold. Record) … } 41
contract Hodl. Fraud { uint owner. Amount; uint number. Of. Payouts; address owner; … function pay. In(uint hold. Time) public payable { require(msg. value > 0); Hold. Record storage new. Record; new. Record. amount += msg. value; new. Record. unlock. Time = now + hold. Time; balance[msg. sender] = new. Record; } unintialized reference to storage … (actually location 0) 42 }
contract Hodl. Fraud { uint owner. Amount; uint number. Of. Payouts; address owner; … function pay. In(uint hold. Time) public payable { require(msg. value. Increments value at location 0 !? > 0); Hold. Record storage new. Record; new. Record. amount += msg. value; new. Record. unlock. Time = now + hold. Time; balance[msg. sender] = new. Record; } … 43 }
contract Hodl. Fraud { uint owner. Amount; uint number. Of. Payouts; address owner; … function pay. In(uint hold. Time) public payable { Contract owner actually credits own account! require(msg. value > 0); Hold. Record storage new. Record; new. Record. amount += msg. value; new. Record. unlock. Time = now + hold. Time; balance[msg. sender] = new. Record; } … 44 }
Prevention Recent Solidity compilers no longer permit unitialized references to storage 45
This Happened Honeypot lotteries … With easily-predictable randomness … Pathetically vulnerable … Except for unitialized storage vars … That overwrite random values! Compiler fixes will not affect honeypots! 46
Floating Point Precision Solidity does not support fixed or floating point numbers Beware of rolling your own Round-off errors cause vulnerabilities 47
contract Hodl. Fraud { uint owner. Amount; uint number. Of. Payouts; address owner; … function pay. In(uint hold. Time) public payable { require(msg. value > 0); Hold. Record storage new. Record; new. Record. amount += msg. value; new. Record. unlock. Time = now + hold. Time; balance[msg. sender] = new. Record; } … 48 }
function buy. Tokens() public payable { uint tokens = msg. value /wei. Per. Eth * tokens. Per. Eth; balances[msg. sender] += tokens; } The math is correct … But if value is less than 1 ether … The number of tokens is 0 49
function sell. Tokens(uint tokens) public { require( balances[msg. sender] >= tokens); uint eth = tokens/tokens. Per. Eth; balances[msg. sender] -= tokens; msg. sender. transfer(eth*wei. Per. Eth); } The math is correct … Any tokens less than 10 will result in 0 ether Rounding is always down 50
Prevention Re-invent numerical analysis Commutative ops don’t commute convert variables into high precision ratios and division are dangerous 51
This Happened “Bancor ended up reimplementing their own functions for arithmetic. That is, their own add, subtract, multiply, and exponentiation. ” “It's a mishmash of special numbers baked into the code. ” “Special magic constants litter Bancor code. So much so that we found it difficult to test this code for correctness ourselves. ” “And even simple rounding errors can be problematic in this game. ” 52
Tx. Origin Authentication Global variable tx. origin Address of account that originated call Do not use this variable for authentication Phishing attack vulnerability 53
contract Phishable { address public owner; constructor (address _owner) { owner = _owner; } function () public payable {} function withdraw. All(address _recipient) public { require(tx. origin == owner); _recipient. transfer(this. balance); } } 54
Victim Contract contract Phishable { address public owner; constructor (address _owner) { Authorize withdrawall() using tx. origin owner = _owner; } function () public payable {} function withdraw. All(address _recipient) public { require(tx. origin == owner); _recipient. transfer(this. balance); } } 55
Attacker Contract contract Attack. Contract { Phishable phishable. Contract; address attacker; constructor (Phishable _phishable. Contract, address _attacker. Address) { phishable. Contract = _phishable. Contract; attacker = _attacker. Address; } function () { phishable. Contract. withdraw. All(attacker); } } 56
Attacker Contract contract Attack. Contract { Phishable phishable. Contract; address attacker; constructor (Phishable _phishable. Contract, Use Social Engineering … address _attacker. Address) { phishable. Contract = _phishable. Contract; attacker = _attacker. Address; To get victim to send small sum to attacker } function () { phishable. Contract. withdraw. All(attacker); } } 57
Attacker Contract contract Attack. Contract { Phishable phishable. Contract; address attacker; constructor (Phishable _phishable. Contract, address _attacker. Address) { victim’s transfer calls fallback function … phishable. Contract = _phishable. Contract; attacker = _attacker. Address; } function () { phishable. Contract. withdraw. All(attacker); } } 58
Attacker Contract contract Attack. Contract { Phishable phishable. Contract; address attacker; constructor (Phishable _phishable. Contract, address _attacker. Address) { victim’s transfer calls fallback function … phishable. Contract = _phishable. Contract; which calls viction’s withdrawall function. . . attacker = _attacker. Address; } function () { phishable. Contract. withdraw. All(attacker); } } 59
Attacker Contract contract Attack. Contract { Phishable phishable. Contract; address attacker; constructor (Phishable _phishable. Contract, victim’s transfer calls fallback function … address _attacker. Address) { which calls viction’s withdrawall function. . . phishable. Contract = _phishable. Contract; with tx. origin set to victim! attacker = _attacker. Address; } function () { BOOM! phishable. Contract. withdraw. All(attacker); } } 60
One Use for tx. origin function attack() { uint intial. Balance = this. balance; attack_contract(); require (this. balance > initial. Balance); } Safe way to attack a suspected honeypot? Try attack … If it does not succeed … Revert! 61
One Legit Use for tx. origin contract Honey. Pot { … function payoff() public : require (msg. sender == tx. origin); … } Ensures that caller is an external account (a person, who cannot revert), not an attack contract, which can revert 62
This Happened Wallets used tx. origin to authenticate caller Instead of msg. sender AFAIK, no coins lost 63
Ideas we covered in this lecture Denial of Service Block Timestamp Manipulation Constructor Names Uninitialized Storage References Floating Point Precision Tx. Origin and Phishing 64
65

































































