Solidity

Solidity #

  • Etherum contracts: write code in Solidity (most common) or other frontend languages
    • Compiles to EVM bytecode
    • Validators use EVM to execute contract bytecode in response to Tx
  • An example contract: NameCoin
    contract nameCoin {
        struct nameEntry {
            address owner;
            bytes32 value;
        }
    
        mapping (bytes32 => nameEntry) data;
    
        // insecure: front-running bug, can be solved using committments
        function nameNew(bytes32 name) {
            // registration cost is 100 Wei
            if (data[name] == 0 && msg.value >= 100) {
                data[name].owner = msg.sender; // record domain owner
                emit Register(msg.sender, name); // log event
            }
        }
    
        function nameUpdate(bytes32 name, bytes32 newValue, address newOwner) {
            if (data[name].owner == msg.sender && msg.value >= 10) {
                data[name].value = newValue;
                data[name].owner = newOwner;
            }
        }
    
        // humans do not need this (i.e. etherscan.io)
        // used by other contracts
        function nameLookup(bytes32 name) {
            return data[name];
        }
    }
    

Contract structure #

Inheritance #

  • Everything is a contract, but some can be degenerate contracts (i.e., those that only contain interfaces and no implementations)
interface IERC20 {
    function transfer(address _to, uint256 _value) external returns (bool);
    function totalSupply() external view returns (uint256);
}

contract ERC20 is IERC20 { // inheritance
    address owner;
    constructor() public {
        owner = msg.sender;
    }

    function transfer(address _to, uint256 _value) external returns (bool) {
        // implementation
    }
}

Value types #

  • uint256
  • bytes32
  • address (bytes32)
    • Functions: _address.balance, _address.send(value), _address.transfer(value)
    • Calling other contracts to send Tx:
      bool success = _address.call{value: msg.value/2, gas: 100}(args);
      
    • delegatecall: load code from other contract into current context
  • bool

Reference types #

  • structs
  • arrays
  • bytes
  • strings
  • mapping (associative array):
    • Declaration: mapping (address => uint256) balances;
    • Assignment: balances[addr] = value;
    • All initialize at 0

Globally available variables #

  • block; fields:
    • blockHash
    • coinbase
    • gaslimit
    • number
    • timestamp
  • gasLeft()
  • msg (Tx data); fields:
    • data (raw data)
    • sender
    • sig
    • value
  • tx; fields:
    • gasprice
    • origin
  • abi; fields:
    • encode
    • encodePacked
    • encodeWithSelector
    • encodeWithSignature
  • Cryptographic functions:
    • keccack256()
    • sha256()
    • sha3()
  • require
    • e.g. require(msg.value > 100, "insufficient funds sent") - required run conditions
  • assert
    • Should never fail in prod: this is just for dev code testing

Function visibilities #

  • external: function can only be called from outside contract
    • Arguments read from calldata (costs 16 gas/byte)
  • public: function can be called externally and internally
    • If called externally: args copied from calldata to memory (expensive)
  • private: only visible inside contract
  • internal: only visible in this contract and contracts deriving from it
  • view: only read storage (no writes)
  • pure: does not touch storage

Note: difference between tx.origin and msg.sender:

  • Imagine Tx chain A->B->C->D, then at D msg.sender == C but tx.origin == A

Notes on publicity of Solidity contracts #

  • Despite compiling to EVM bytecode (only bytecode stored on-chain) - people want to see the source and verify the compiled contract is doing what it should be
    • Therefore - Solidity code submitted to sites like Etherscan, which verify the compiled contract matches the on-chain bytecode
    • Solidity variables stored in S[] array on chain
      • This means that contracts cannot keep secrets!!