Skip to content

Instantly share code, notes, and snippets.

@jtriley2p
Last active June 3, 2023 21:23
Show Gist options
  • Select an option

  • Save jtriley2p/2643ae1b534d8c1be7d529311816e68d to your computer and use it in GitHub Desktop.

Select an option

Save jtriley2p/2643ae1b534d8c1be7d529311816e68d to your computer and use it in GitHub Desktop.

Revisions

  1. jtriley2p revised this gist Oct 21, 2022. 1 changed file with 174 additions and 15 deletions.
    189 changes: 174 additions & 15 deletions YulERC20.sol
    Original file line number Diff line number Diff line change
    @@ -6,8 +6,6 @@ pragma solidity 0.8.17;
    bytes32 constant nameLength = 0x0000000000000000000000000000000000000000000000000000000000000009;
    bytes32 constant nameData = 0x59756c20546f6b656e0000000000000000000000000000000000000000000000;

    uint256 constant maxUint256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

    // Used in the `symbol()` function
    // "YUL"
    bytes32 constant symbolLength = 0x0000000000000000000000000000000000000000000000000000000000000003;
    @@ -19,10 +17,16 @@ bytes32 constant insufficientBalanceSelector = 0xf4d678b800000000000000000000000
    // `bytes4(keccak256("InsufficientAllowance(address,address)"))`
    bytes32 constant insufficientAllowanceSelector = 0xf180d8f900000000000000000000000000000000000000000000000000000000;

    // max uint256 value, used to mint EVERYTHING to the deployer lol
    uint256 constant maxUint256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

    error InsufficientBalance();
    error InsufficientAllowance(address owner, address spender);

    // `keccak256("Transfer(address,address,uint256)")`
    bytes32 constant transferHash = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

    // `keccak256("Approval(address,address,uint256)")`
    bytes32 constant approvalHash = 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;

    /// @title Yul ERC20
    @@ -33,205 +37,360 @@ contract YulERC20 {
    event Transfer(address indexed sender, address indexed receiver, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 amount);

    // owner -> balance
    // account -> balance
    // `slot = keccak(account, 0x00))`
    mapping(address => uint256) internal _balances;

    // owner -> spender -> allowance
    // `slot = keccak256(owner, keccak256(spender, 0x01))`
    mapping(address => mapping(address => uint256)) internal _allowances;

    // `slot = 0x02`
    uint256 internal _totalSupply;

    // Mint maxUint256 tokens to the `msg.sender`.
    constructor() {
    assembly {
    // store the caller address at memory index zero
    mstore(0x00, caller())

    // store zero (storage index) at memory index 32
    mstore(0x20, 0x00)

    // hash the first 64 bytes of memory to generate the balance slot
    let slot := keccak256(0x00, 0x40)

    // store maxUint256 as caller's balance
    sstore(slot, maxUint256)

    // store maxUint256 as total supply
    sstore(0x02, maxUint256)

    // store maxUint256 in memory to log
    mstore(0x00, maxUint256)

    // log transfer event
    log3(0x00, 0x20, transferHash, 0x00, caller())
    }
    }

    function name() public pure returns (string memory) {
    assembly {
    // get free memory pointer from memory index `0x40`
    let memptr := mload(0x40)

    // store string pointer (0x20) in memory
    mstore(memptr, 0x20)

    // store string length in memory 32 bytes after the pointer
    mstore(add(memptr, 0x20), nameLength)

    // store string data 32 bytes after the length
    mstore(add(memptr, 0x40), nameData)

    // return from memory the three 32 byte slots (ptr, len, data)
    return(memptr, 0x60)
    }
    }

    function symbol() public pure returns (string memory) {
    assembly {
    // get free memory pointer from memory index `0x40`
    let memptr := mload(0x40)

    // store string pointer (0x20) in memory
    mstore(memptr, 0x20)

    // store string length in memory 32 bytes after the pointer
    mstore(add(memptr, 0x20), symbolLength)

    // store string data 32 bytes after the length
    mstore(add(memptr, 0x40), symbolData)

    // return from memory the three 32 byte slots (ptr, len, data)
    return(memptr, 0x60)
    }
    }

    function decimals() public pure returns (uint8) {
    assembly {
    // store `18` in memory at slot zero
    mstore(0, 18)

    // return 32 bytes from memory at slot zero
    return(0x00, 0x20)
    }
    }

    function totalSupply() public view returns (uint256) {
    assembly {
    // load the total supply from storage slot 0x02 and store in memory
    mstore(0x00, sload(0x02))

    // return 32 bytes from memory at index zero
    return(0x00, 0x20)
    }
    }

    function balanceOf(address) public view returns (uint256) {
    assembly {
    // load calldata offset 4 (first arg after selector) and store in memory at index zero
    mstore(0x00, calldataload(4))

    // store zero (storage index) at memory index 32
    mstore(0x20, 0x00)

    // load from storage the hash of the first 64 bytes of memory,
    // then store the value in memory at offset zero
    mstore(0x00, sload(keccak256(0x00, 0x40)))

    // return the first 32 bytes from memory (loaded balance)
    return(0x00, 0x20)
    }
    }

    function transfer(address receiver, uint256 amount) public returns (bool) {
    assembly {
    // mem stuff
    // load free memory pointer from index 64
    let memptr := mload(0x40)

    // load caller balance, assert sufficient
    // store the caller address at the free memory pointer
    mstore(memptr, caller())

    // store zero (storage index) in the next memory index
    mstore(add(memptr, 0x20), 0x00)

    // hash 64 bytes of memory to generate the caller's balance slot
    let callerBalanceSlot := keccak256(memptr, 0x40)

    // load the caller's balance
    let callerBalance := sload(callerBalanceSlot)

    // if the caller's balance is less than the amount
    if lt(callerBalance, amount) {
    // store the insufficient balance selector in memory at slot zero
    mstore(0x00, insufficientBalanceSelector)

    // revert with the 4 byte selector from memory
    revert(0x00, 0x04)
    }

    // if the caller == receiver, revert
    if eq(caller(), receiver) {
    // we should have a better error message here,
    // but we were short on time
    revert(0x00, 0x00)
    }

    // decrease caller bal
    // decrease the caller's balance
    let newCallerBalance := sub(callerBalance, amount)

    // store the caller's balance in its slot
    sstore(callerBalanceSlot, newCallerBalance)

    // load receiver balance
    // store the receiver address in memory at the memory pointer
    // (overwrites some of the memory we have written to, but we don't need it anymore)
    mstore(memptr, receiver)

    // store zero (storage index) at a 32 byte offset
    mstore(add(memptr, 0x20), 0x00)

    // hash 64 bytes of memory to generate the receiver's balance slot
    let receiverBalanceSlot := keccak256(memptr, 0x40)

    // load the receiver's balance
    let receiverBalance := sload(receiverBalanceSlot)

    // increase receiver bal
    // increase receiver balance
    let newReceiverBalance := add(receiverBalance, amount)

    // store
    // store the receiver's balance
    sstore(receiverBalanceSlot, newReceiverBalance)

    // log
    // store the amount in memory to be logged
    mstore(0x00, amount)

    // log the transfer event
    log3(0x00, 0x20, transferHash, caller(), receiver)

    // ret
    // store `true` in memory at index zero
    mstore(0x00, 0x01)

    // return the first 32 byte word of memory
    return(0x00, 0x20)
    }
    }

    function allowance(address owner, address spender) public view returns (uint256) {
    // keccak256(spender, keccak256(owner, slot)))
    assembly {
    // store owner address at memory index zero
    mstore(0x00, owner)

    // store one (storage index) at memory index 32
    mstore(0x20, 0x01)

    // hash the first 64 bytes of memory to generate the inner hash
    let innerHash := keccak256(0x00, 0x40)

    // store the spender address at memory index zero
    mstore(0x00, spender)

    // store the inner hash at memory index 32
    mstore(0x20, innerHash)

    // hash the first 64 bytes of memory to generate the allowance slot
    let allowanceSlot := keccak256(0x00, 0x40)

    // load the allowance from storage
    let allowanceAmount := sload(allowanceSlot)

    // store the allowance at memory index zero
    mstore(0x00, allowanceAmount)

    // return the first 32 byte word from memory
    return(0x00, 0x20)
    }
    }

    function approve(address spender, uint256 amount) public returns (bool) {
    assembly {
    // store the caller address
    mstore(0x00, caller())

    // store one (storage index) at memory index 32
    mstore(0x20, 0x01)

    // hash the first 64 bytes of memory to generate the inner hash
    let innerHash := keccak256(0x00, 0x40)

    // store the spender address at memory index zero
    mstore(0x00, spender)

    // store the inner hash at memory index 32
    mstore(0x20, innerHash)

    // hash the first 64 bytes of memory to generate the allowance slot
    let allowanceSlot := keccak256(0x00, 0x40)

    // store the new allowance in the allowance slot
    sstore(allowanceSlot, amount)

    // store the amount at memory index zero to be logged
    mstore(0x00, amount)

    // log the approval event
    log3(0x00, 0x20, approvalHash, caller(), spender)

    // store `true` at memory index zero
    mstore(0x00, 0x01)

    // return the first 32 byte word from memory
    return(0x00, 0x20)
    }
    }

    function transferFrom(address sender, address receiver, uint256 amount) public returns (bool) {
    assembly {
    // load the free memory pointer from memory index 64
    let memptr := mload(0x40)


    // store the sender address at memory index zero
    mstore(0x00, sender)

    // store one (storage index) at memory index 32
    mstore(0x20, 0x01)

    // hash the first 64 bytes of memory to generate the inner hash
    let innerHash := keccak256(0x00, 0x40)

    // store the caller (spender) at memory index zero
    mstore(0x00, caller())

    // store the inner hash at memory index 32
    mstore(0x20, innerHash)

    // hash the first 64 bytes of memory to generate the allowance slot
    let allowanceSlot := keccak256(0x00, 0x40)

    // load the caller's allowance to spend on behalf of the sender
    let callerAllowance := sload(allowanceSlot)


    // if the caller's allowance is less than the amount
    if lt(callerAllowance, amount) {

    // store the insufficient allowance error selector at the free memory pointer
    mstore(memptr, insufficientAllowanceSelector)

    // store the sender in memory after the four byte selector
    mstore(add(memptr, 0x04), sender)

    // store the caller in memory after the sender
    mstore(add(memptr, 0x24), caller())

    // revert with 68 (4 + 32 + 32) bytes of memory
    revert(memptr, 0x44)
    }


    // if the caller allowance is less than the max uint256 value (infinite)
    if lt(callerAllowance, maxUint256) {
    // subtract the amount from the allowance and store it in storage
    sstore(allowanceSlot, sub(callerAllowance, amount))
    }

    // load sender balance, assert sufficient
    // store the sender address in memory at the free memory pointer
    mstore(memptr, sender)

    // store zero (storage index) after the sender address
    mstore(add(memptr, 0x20), 0x00)

    // hash 64 bytes of memory starting at the free memory pointer to generate the balance slot
    let senderBalanceSlot := keccak256(memptr, 0x40)

    // load the sender balance
    let senderBalance := sload(senderBalanceSlot)


    // if the sender balance is less than the amount
    if lt(senderBalance, amount) {
    // store the insufficient balance selector in memory
    mstore(0x00, insufficientBalanceSelector)

    // revert with the error selector
    revert(0x00, 0x04)
    }


    // subtract the amount from the sender balance and store it
    sstore(senderBalanceSlot, sub(senderBalance, amount))

    // receiver balance
    // store the receiver address in memory at the free memory pointer
    mstore(memptr, receiver)

    // store zero (storage index) after the receiver address
    mstore(add(memptr, 0x20), 0x00)

    // hash 64 bytes of memory starting at the free memory pointer to generate the balance slot
    let receiverBalanceSlot := keccak256(memptr, 0x40)

    // load the sender balance
    let receiverBalance := sload(receiverBalanceSlot)

    // add the amount and the receiver balance and store it
    sstore(receiverBalanceSlot, add(receiverBalance, amount))

    // store the amount in memory to be logged
    mstore(0x00, amount)

    // log the transfer event
    log3(0x00, 0x20, transferHash, sender, receiver)


    // store `true` in memory at slot zero
    mstore(0x00, 0x01)

    // return the first 32 byte word from memory
    return(0x00, 0x20)
    }
    }
  2. jtriley2p created this gist Oct 20, 2022.
    238 changes: 238 additions & 0 deletions YulERC20.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,238 @@
    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.17;

    // Used in the `name()` function
    // "Yul Token"
    bytes32 constant nameLength = 0x0000000000000000000000000000000000000000000000000000000000000009;
    bytes32 constant nameData = 0x59756c20546f6b656e0000000000000000000000000000000000000000000000;

    uint256 constant maxUint256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

    // Used in the `symbol()` function
    // "YUL"
    bytes32 constant symbolLength = 0x0000000000000000000000000000000000000000000000000000000000000003;
    bytes32 constant symbolData = 0x59554c0000000000000000000000000000000000000000000000000000000000;

    // `bytes4(keccak256("InsufficientBalance()"))`
    bytes32 constant insufficientBalanceSelector = 0xf4d678b800000000000000000000000000000000000000000000000000000000;

    // `bytes4(keccak256("InsufficientAllowance(address,address)"))`
    bytes32 constant insufficientAllowanceSelector = 0xf180d8f900000000000000000000000000000000000000000000000000000000;

    error InsufficientBalance();
    error InsufficientAllowance(address owner, address spender);

    bytes32 constant transferHash = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
    bytes32 constant approvalHash = 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;

    /// @title Yul ERC20
    /// @author <your name here>
    /// @notice For demo purposes ONLY.
    /// @dev Some optimizations and best practices omitted here for the sake of demonstration.
    contract YulERC20 {
    event Transfer(address indexed sender, address indexed receiver, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 amount);

    // owner -> balance
    mapping(address => uint256) internal _balances;

    // owner -> spender -> allowance
    mapping(address => mapping(address => uint256)) internal _allowances;

    uint256 internal _totalSupply;

    constructor() {
    assembly {
    mstore(0x00, caller())
    mstore(0x20, 0x00)
    let slot := keccak256(0x00, 0x40)
    sstore(slot, maxUint256)

    sstore(0x02, maxUint256)

    mstore(0x00, maxUint256)
    log3(0x00, 0x20, transferHash, 0x00, caller())
    }
    }

    function name() public pure returns (string memory) {
    assembly {
    let memptr := mload(0x40)
    mstore(memptr, 0x20)
    mstore(add(memptr, 0x20), nameLength)
    mstore(add(memptr, 0x40), nameData)
    return(memptr, 0x60)
    }
    }

    function symbol() public pure returns (string memory) {
    assembly {
    let memptr := mload(0x40)
    mstore(memptr, 0x20)
    mstore(add(memptr, 0x20), symbolLength)
    mstore(add(memptr, 0x40), symbolData)
    return(memptr, 0x60)
    }
    }

    function decimals() public pure returns (uint8) {
    assembly {
    mstore(0, 18)
    return(0x00, 0x20)
    }
    }

    function totalSupply() public view returns (uint256) {
    assembly {
    mstore(0x00, sload(0x02))
    return(0x00, 0x20)
    }
    }

    function balanceOf(address) public view returns (uint256) {
    assembly {
    mstore(0x00, calldataload(4))
    mstore(0x20, 0x00)
    mstore(0x00, sload(keccak256(0x00, 0x40)))
    return(0x00, 0x20)
    }
    }

    function transfer(address receiver, uint256 amount) public returns (bool) {
    assembly {
    // mem stuff
    let memptr := mload(0x40)

    // load caller balance, assert sufficient
    mstore(memptr, caller())
    mstore(add(memptr, 0x20), 0x00)
    let callerBalanceSlot := keccak256(memptr, 0x40)
    let callerBalance := sload(callerBalanceSlot)

    if lt(callerBalance, amount) {
    mstore(0x00, insufficientBalanceSelector)
    revert(0x00, 0x04)
    }

    if eq(caller(), receiver) {
    revert(0x00, 0x00)
    }

    // decrease caller bal
    let newCallerBalance := sub(callerBalance, amount)
    sstore(callerBalanceSlot, newCallerBalance)

    // load receiver balance
    mstore(memptr, receiver)
    mstore(add(memptr, 0x20), 0x00)

    let receiverBalanceSlot := keccak256(memptr, 0x40)
    let receiverBalance := sload(receiverBalanceSlot)

    // increase receiver bal
    let newReceiverBalance := add(receiverBalance, amount)

    // store
    sstore(receiverBalanceSlot, newReceiverBalance)

    // log
    mstore(0x00, amount)
    log3(0x00, 0x20, transferHash, caller(), receiver)

    // ret
    mstore(0x00, 0x01)
    return(0x00, 0x20)
    }
    }

    function allowance(address owner, address spender) public view returns (uint256) {
    // keccak256(spender, keccak256(owner, slot)))
    assembly {
    mstore(0x00, owner)
    mstore(0x20, 0x01)
    let innerHash := keccak256(0x00, 0x40)

    mstore(0x00, spender)
    mstore(0x20, innerHash)
    let allowanceSlot := keccak256(0x00, 0x40)

    let allowanceAmount := sload(allowanceSlot)
    mstore(0x00, allowanceAmount)
    return(0x00, 0x20)
    }
    }

    function approve(address spender, uint256 amount) public returns (bool) {
    assembly {
    mstore(0x00, caller())
    mstore(0x20, 0x01)
    let innerHash := keccak256(0x00, 0x40)

    mstore(0x00, spender)
    mstore(0x20, innerHash)
    let allowanceSlot := keccak256(0x00, 0x40)

    sstore(allowanceSlot, amount)

    mstore(0x00, amount)
    log3(0x00, 0x20, approvalHash, caller(), spender)

    mstore(0x00, 0x01)
    return(0x00, 0x20)
    }
    }

    function transferFrom(address sender, address receiver, uint256 amount) public returns (bool) {
    assembly {
    let memptr := mload(0x40)

    mstore(0x00, sender)
    mstore(0x20, 0x01)
    let innerHash := keccak256(0x00, 0x40)

    mstore(0x00, caller())
    mstore(0x20, innerHash)
    let allowanceSlot := keccak256(0x00, 0x40)

    let callerAllowance := sload(allowanceSlot)

    if lt(callerAllowance, amount) {
    mstore(memptr, insufficientAllowanceSelector)
    mstore(add(memptr, 0x04), sender)
    mstore(add(memptr, 0x24), caller())
    revert(memptr, 0x44)
    }

    if lt(callerAllowance, maxUint256) {
    sstore(allowanceSlot, sub(callerAllowance, amount))
    }

    // load sender balance, assert sufficient
    mstore(memptr, sender)
    mstore(add(memptr, 0x20), 0x00)
    let senderBalanceSlot := keccak256(memptr, 0x40)
    let senderBalance := sload(senderBalanceSlot)

    if lt(senderBalance, amount) {
    mstore(0x00, insufficientBalanceSelector)
    revert(0x00, 0x04)
    }

    sstore(senderBalanceSlot, sub(senderBalance, amount))

    // receiver balance
    mstore(memptr, receiver)
    mstore(add(memptr, 0x20), 0x00)
    let receiverBalanceSlot := keccak256(memptr, 0x40)
    let receiverBalance := sload(receiverBalanceSlot)

    sstore(receiverBalanceSlot, add(receiverBalance, amount))

    mstore(0x00, amount)
    log3(0x00, 0x20, transferHash, sender, receiver)

    mstore(0x00, 0x01)
    return(0x00, 0x20)
    }
    }
    }