Vincent A Saulys' Blog
Solidity Cheat Sheet
Tags: crypto defi cheat-sheet
July 13, 2021

(subject to updating)

Outline of a Smart Contract

pragma solidity =0.8.0;

import ./path/to/ParentContract;
import "./path/to/OtherContract";

/* 
    library & interface also exist

    library => only has functions, not inherent variables
    interfaces => only function headers are declared
*/
contract ChildContract is ParentContract {

    // _type storage/memory _name = _val;
    // always storage when called outside a function

    // uint -- unsigned integer -- (8 through 256, in multiples of 8)
    // always uint256 outside a function or when uint is called
    uint public myUintVal;
    // int -- signed integer -- (8 through 256, in multiples of 8)
    int public myIntVal = 123456789;
    // bytes -- bytes -- (1 through 32, in steps of 1)
    private bytes4 myBytesVal; // 0x?
    // bool
    bool public myBoolVal;
    // string
    string public myStrVal;
    // address -- special type, ethereum address
    address public myAddressMapping;
    // arrays
    // add with myUnfixedSizeArray.push(1);
    uint[4] public myArray = [1,2,3,4];
    int[] public myUnfixedSizeArray;

    // structs
    struct Person {
        string text;
        bool isOwner;
    }
    Person[] public people;

    // enums
    enum myEnum = { _choice1, _choice2 };
    // private myEnum myEnumVal = myEnum._choice1;

    // mapping
    mapping(address => bool) public owners;
    mapping(address => mapping(uint => bool)) public fancyMapping;

    // optional
    constructor() {
        // msg.sender is a special value
        owners[msg.sender] = true;
    }

    // events -- up to 3 values allowed
    // indexed allows for grouping
    // cheap storage, cannot be read by other contracts
    event someEventName(string _URI, address indexed _caller);

    function _name(uint _arg1, string memory _arg2) public returns (bool) {
        if (_arg1 < 10) {
            // ...
        } else if (_arg2 == "this") {
            // ...
        } else {
            // this will undo any blockchain transactions
            revert("incorrect vals passed"); 
            // assert(_arg1 < 10 && _arg2 == "this", "this will also work");
            // require(_arg1 < 10 && _arg2 == "this", "this will also work");
        }
    }

    /*
        Know that any function with `payable` needs a fallback
        function -- this can be done with a no-name function that is
        payable. This function is called when a non-existent function
        is called, when receive does not exist, or when msg.data is empty
    */
    function _takesMoney() public payable {
        // all amounts sent to a payable function are stored on the contract
        msg.sender; // address of address calling this function
        msg.value;  // only can be called in payable, this will be the ether sent

        // if left blank, this will just update the balance of the contract
        address(this).balance;  // get the store of value at this contract
        account(this).balance += msg.value; // store this into the contract’s value
    }

    modifier onlyOwner() {
        require(owners[msg.sender] == true, "only owners can send money");
        _;  // necesary!
    }

    modifier okAddress(address payable _to) {
        require(2 > 1, "this is a trivial example to show multiple modifiers");
        _;
    }

    function _sendsMoney(address payable _to) 
        public 
        payable 
        onlyOwner 
        okAddress(_to) {
        (bool sent, bytes memory data) = _to.call{value: msg.value, gas: gasLimit}("");
    }

    function _namedReturnVariables() returns (uint _val) {
        _val = 1234; // this will automatically return
    }

    // Generating Hashes
    // keccak256(abi.encodePacked(_val1, _val2, ...)); 
    function calAnotherContractsFunction public {
        bytes memory payload = abi.encodeWithSignature("functionName(uint, uint)", 12, 35);
        (bool success, bytes memory returnData) = address(addr).call(payload);

        // alternately, you can supply a fixed gas or ether -- order does not matter
        address(nameReg).call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature("Test(uint, uint)", 12, 35));

        require(success);
    };


    // you can also use other contracts
    address[] public listOfOtherContracts;

    function createNewContract() public {
        OtherContract newContract = new OtherContract();
        listOfOtherContracts.push(newContract);
        newContract.someFunctionOnThatContract(_val1, _val2);
    }

    /// @notice you use three slashes when writing a function with a docstring
    ///  every other line inserted to this gets relative-indented one space
    /// @dev Some notes to the dev such as what it throws
    /// @param _val1 Some uint value
    /// @param _val2 Some other uint value
    /// @return Always true
    function withDocString(uint _val1, _uint _val2) external returns (bool) {
        return true;
    }
}

Visibility Modifier

who can call? Within Contract? Child Contracts? Outside Contracts?
public
private
internal
external

Pure, Virtual, View Functions

| pure | only computation, does not read or write to blockchain | | virtual | meant to be overriden, typically used in interfaces | | view | only reads from the block | | override | overrides a defined function on the parent class |

Truffle Commands

CLI Commands

CLI What is does
truffle init initializes a blank project
truffle test run tests
truffle console bring up the console
ganache-cli starts a ganache blockchain

Ganache Config

Typical config in a truffle project (./config.json) looks like this for a default ganache-cli startup.

// ... excised
module.exports = {
  networks: {
    development: {
      host: "127.0.0.1", // Localhost (default: none)
      port: 8545, // Standard Ethereum port (default: none)
      network_id: "*" // Any network (default: none)
    }
  },
  db: {
    enabled: false
  }
};
// ...

Truffle Tests

Code

Code notes
const myCoin = artifacts.require("myCoin"); get the coin / contract you deployed
token = await SCoin.new(); deploy a new contract
token._funcionName.call(...); call your function -- do not transact
token._functionName.sendTransaction(.., {}); call contract function -- _does transact (see below)
truffleAssert.eventEmitted(tx, "NameOfEvent", (ev) => {...}); check that an event was emitted -- ev object contains all event variables passed
truffleAssert.fails(_asyncFunc); assert that a called async function fails
accounts[0]...[9] get an ethereum account, defaults to 10 and is configured in ganache

Other assertions from Chai

Know that there's a weird exception with regards to overloaded methods -- you need to specify the function header separately.

const instance = await MyContract.deployed();
instance.methods['setValue(uint256)'](123);
instance.methods['setValue(uint256,uint256)'](11, 55);
Sending Transactions
token._functionName.sendTransaction(_param1, _param2, {
  from: accounts[0],
  to: accounts[1],
  gas: 23000,     // priced in 'gasPrice'
  gasPrice: 1,    // how much each gas is in wei (10^18 wei = 1 ether)
  value: 100,     // how much ethereum, separate from the gas fees, we send -- msg.value
  data: "data"    // any data to associate with the transaction
});

Anataomy of a Test

const SCoin = artifacts.require("SCoin"); // this is auto-deployed in the console
const Callee = artifacts.require("Callee");
const { assert, expect, should } = require("chai"); // optional
const truffleAssert = require("truffle-assertions"); // installed separately

contract("SCoin Unit Tests I", async (accounts) => {
  describe("name", () => {
    it("should return `SCoin NFTs`", async () => {
      const token = await SCoin.new();
      const name = await token.name.call();
      assert.equal(name, "SCoin NFTs");
    });
  });

  // ... more describe
});

Share On:
EmailTwitterHackerNewsRedditLinkedIn