Solidity基础复习笔记一


Solidity基础语法

环境准备

remix:https://remix.ethereum.org(在线IDE)
参考资料:https://github.com/inoutcode/ethereum_book(精通以太坊)

两种账户

EOA:Externally Owned Account,与⼀个私钥⼀⼀对应,ex:metamask账户
CA:Contract Account,合约账户,没有私钥与之对应,合约就是⼀个CA,它也可以持有资⾦。

第⼀个dapp

1. 写状态变量(上链)是⼀笔交易(tx),需要矿⼯打包,所以需要花费资⾦(gas);
2. 读取状态变量,是从区块链中获取数据,不是⼀笔交易,所以免费。(必须加上view
// 指定编译器版本,版本标识符(指该solidity文件能够被高于0.8.13和低于0.9.0版本的solidity编译。)
pragma solidity ^0.8.13;
// 关键字 contract 跟python、java的class⼀样
contract Inbox{
    // 状态变量,存在链上
     string public message;
    // 构造函数
     constructor(string memory initMessage) {
    // 本地变量
    string memory tmp = initMessage;
    message = tmp;
    }
    // 写操作,需要⽀付gas
    function setMessage(string memory _newMessage) public {
        message = _newMessage;
    }
    // 读操作,不需要⽀付gas
    function getMessage() public view returns(string memory) {
        return message;
    }
}

基础数据类型

  • int(有符号整型,有正有负)int默认为int256
  • uint(⽆符号整型,⽆负数)uint默认为uint256
  • 以8位为区间,⽀持int8,int16,int24 ⾄ int256(uint同理)
  • bool类型:true,false
  • 定⻓字节:bytes1~bytes32
  • 地址:address(20个字节,40个16进制字符,共160位),如:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Primitives {
    bool public flag = true;
    /*
     uint stands for unsigned integer, meaning non negative integers(代表无符号整数,即正数)
     different sizes are available(各自的取值范围)
     uint8 ranges from 0` to 2 ** 8 - 1 (0到2**8-1)
     uint16 ranges from 0 to 2 ** 16 - 1(0到2**16-1)
     ...
     uint256 ranges from 0 to 2 ** 256 - 1(0到2**256-1)
     */
    uint8 public u8 = 1;
    uint public u256 = 456;
    uint public u = 123; // uint is an alias for uint256
    /*
     Negative numbers are allowed for int types.(int类型包含负数)
     Like uint, different ranges are available from int8 to int256
     int256 ranges from -2 ** 255 to 2 ** 255 - 1
     int128 ranges from -2 ** 127 to 2 ** 127 - 1
     */
    int8 public i8 = -1;
    int public i256 = 456;
    int public i = -123; // int is same as int256
    // minimum and maximum of int
    int public minInt = type(int).min;
    int public maxInt = type(int).max;
    address public addr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;

    /*In Solidity, the data type byte represent a sequence of bytes.
     Solidity presents two type of bytes types :
     - fixed-sized byte arrays
     - dynamically-sized byte arrays.
     The term bytes in Solidity represents a dynamic array of bytes.
     It’s a shorthand for byte[] .
     */
    bytes1 a = 0xb5; // [10110101]
    bytes1 b = 0x56; // [01010110]
    // Default values 初始值
    // Unassigned variables have a default value
    bool public defaultBoo; // false 默认为false
    uint public defaultUint; // 0 默认为0
    int public defaultInt; // 0 默认为0
    address public defaultAddr; // 0x0000000000000000000000000000000000000000
}

变量variables

  • 状态变量(state)
  • 定义在合约内,函数外
  • 存储在链上
  • 本地变量(local)
  • 定义在函数内
  • 不会存储在链上
  • 全局变量(global)
  • 与当前合约⽆关,描述整个区块链的信息(时间、块⾼等)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Variables {
    // State variables are stored on the blockchain.
    string public msg = "Hello";
    uint public age = 26;
    function test() public {
        // Local variables are not saved to the blockchain.
        uint i = 456;
        // Here are some global variables
        uint height = block.blocks; // Current block height
        address sender = msg.sender; // address of the caller
    }
}
  • 描述区块链信息的全局变量,常⽤如下:
函数 含义 备注
blockhash(uint (byte32)哈希值
blockNumber) (byte32)哈希值
block.coinbase (address) 当前块矿⼯的地址
block.difficulty (uint)当前块的难度
block.gaslimit (uint)当前块的gaslimit
block.number (uint)当前区块的块号
block.timestamp (uint)当前块的时间戳 常⽤
gasleft() (uint)当前还剩的gas
tx.origin (address)交易的原始发送者的地址,只能是EOA 常⽤
msg.sender (address)当前调⽤发起⼈的地址(可能是合约CA,也可能是EOA) 常⽤
msg.sig (bytes4)调⽤数据的前四个字节(函数标识符) 常⽤
msg.value (uint)这个消息所附带的货币量,单位为wei 常⽤
msg.data (bytes)完整的调⽤数据(calldata) 常⽤
tx.gasprice (uint) 交易的gas价格

常量constant

  1. 常量与变量相对,需要硬编码在合约中,合约部署之后,⽆法改变。
  2. 常量更加节约gas,⼀般⽤⼤写来代表常量。
  3. ⾼阶⽤法:clone合约时,如果合约内有初始值,必须使⽤constant,否则clone的新合约初始值为空值。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Constants {
    // coding convention to uppercase constant variables
    address public constant MY_ADDRESS = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    uint public constant MY_UINT = 123;
}

不可变量immutable

  1. 与常量类似,但是不必硬编码,可以在构造函数时传值,部署后⽆法改变。
  2. immutable仅⽀持值类型(如:int,address,bytes8),不⽀持⾮值类型(如:string,bytes)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Immutable {
    // coding convention to uppercase constant variables
    address public immutable MY_ADDRESS;
    uint public immutable MY_UINT;
    bytes1 public immutable MY_BYTES1 = 0xff;
    // string public immutable greetings = "hello"; // error
    constructor(uint _myUint) {MY_ADDRESS = msg.sender;MY_UINT = _myUint;
    }
}

ether和wei

  • 常⽤单位为:wei,gwei,ether
  • 不含任何后缀的默认单位是 wei
  • 1 gwei = 10^9 wei
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract EtherUnits {
    uint public oneWei = 1 wei;
    // 1 wei is equal to 1
    bool public isOneWei = 1 wei == 1;
    uint public oneEther = 1 ether;
    // 1 ether is equal to 10^18 wei
    bool public isOneEther = 1 ether == 1e18;
}

msg三⼈组

当⽤户发起⼀笔交易时,相当于向合约发送⼀个消息(msg),这笔交易可能会涉及到三个重要的全局变量,具体如下:
  1. msg.sender:表示这笔交易的调⽤者是谁(地址),同⼀个交易,不同的⽤户调⽤,msg.sender不同;
  2. msg.value:表示调⽤这笔交易时,携带的ether数量,这些以太坊由msg.sender⽀付,转⼊到当前合约 (wei单位整数);
  3. 注意:⼀个函数(或地址)如果想接收ether,需要将其修饰为:payable。
  4. msg.data:表示调⽤这笔交易的信息,由函数签名和函数参数(16进制字符串),组成代理模式时常⽤。

msg.sender

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract MsgSender {
    address public owner;
    uint256 public value;
    address public caller;
    constructor() {
        //在部署合约的时候,设置⼀个全局唯⼀的合约所有者,后⾯可以使⽤权限控制
        owner = msg.sender;
    }
    //1. 对与合约⽽⾔,msg.sender是⼀个可以改变的值,并不⼀定是合约的创造者
    //2. 任何⼈调⽤了合约的⽅法,那么这笔交易中的from就是当前合约中的msg.sender
    function setValue(uint256 input) public {
        value = input;
        caller = msg.sender;
     }
}

msg.value

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract MsgValue {
    // uint256 public money;
    mapping(address=> uint256) public personToMoney;
    // 函数⾥⾯使⽤了msg.value,那么函数要修饰为payable
    function play() public payable {
        // 如果转账不是100wei,那么参与失败
        // 否则成功,并且添加到维护的mapping中
        require(msg.value == 100, "should equal to 100!");
        personToMoney[msg.sender] = msg.value;
    }
    // 查询当前合约的余额
    function getBalance() public view returns(uint256) {
        return address(this).balance;
    }
}

msg.data

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract MsgData {
    event Data(bytes data, bytes4 sig);
    // input0: addr: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
    // input1: amt : 1
    function transfer(address addr, uint256 amt) public {
        bytes memory data = msg.data;
        // msg.sig 表示当前⽅法函数签名(4字节)
        // msg.sig 等价于 this.transfer.selector
        emit Data(data, msg.sig);
     }
    /*output:
     data:0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001
     sig: 0xa9059cbb
     对data进⾏分析:
     0xa9059cbb //前四字节,函数名keccak256
     0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4 //第⼀个参数占位符(32字节)
     0000000000000000000000000000000000000000000000000000000000000001 //第⼆个参数占位符(32字节)*/
}

payable

  1. ⼀个函数(或地址)如果想接收ether,需要将其修饰为:payable。
  2. address常⽤⽅法:
  3. balance(): 查询当前地址的ether余额
  4. transfer(uint): 合约向当前地址转指定数量的ether,如果失败会回滚
  5. send(uint): 合约向当前地址转指定数量的ether,如果失败会返回false,不回滚(不建议使⽤send)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Payable {
    // 1. Payable address can receive Ether
    address payable public owner;
    // 2. Payable constructor can receive Ether
    constructor() payable {
        owner = payable(msg.sender);
     }
    // 3. Function to deposit Ether into this contract.
    function deposit() public payable {}
    // 4. Call this function along with some Ether.
    // The function will throw an error since this function is not payable.
    function notPayable() public {}
    // 5. Function to withdraw all Ether from this contract.
    function withdraw() public {
        uint amount = address(this).balance;
        owner.transfer(amount);
     }
    // 6. Function to transfer Ether from this contract to address from input
    function transfer(address payable _to, uint _amount) public {
        _to.transfer(_amount);
     }
}

gas相关

gas描述执⾏⼀笔交易时需要花费多少ether!(1 ether = 10^18wei)
交易⼿续费 = gas_used * gas_price,其中:
  1. gas:是数量单位,uint
  2. gas_used:表示⼀笔交易实际消耗的gas数量
  3. gas_price:每个gas的价格,单位是wei或gwei
  4. gas limit:表示你允许这⼀笔交易消耗的gas上限,⽤户⾃⼰设置(防⽌因为bug导致的损失)
  5. 如果gas_used⼩于gas_limit,剩余gas会返回给⽤户,这个值不再合约层⾯设置,在交易层⾯设置(如metamask)
  6. 如果gas_used⼤于gas_limit,交易失败,资⾦不退回
  7. block gas limit:表示⼀个区块能够允许的最⼤gas数量,由区块链⽹络设置

view和pure

view和pure⽤于修饰Getter函数(只读取数据的函数),其中:
  1. view:表示函数中不会修改状态变量,只是读取;
  2. pure:表示函数中不会使⽤状态变量,既不修改也不读取。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract ViewAndPure {
    uint public x = 1;
    // Promise not to modify the state.
    function addToX(uint y) public view returns (uint) {
        return x + y;
     }
    // Promise not to modify or read from the state.
    function add(uint i, uint j) public pure returns (uint) {
        return i + j;
     }
}

bytes和string

byteN、bytes、string直接的关系

bytes:

  • bytes是动态数组,相当于byte数组(如:byte[10])
  • ⽀持push⽅法添加
  • 可以与string相互转换
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Bytes {
    bytes public name;
    //1. 获取字节⻓度
    function getLen() public view returns(uint256) {
        return name.length;
    }
    //2. 可以不分空间,直接进⾏字符串赋值,会⾃动分配空间
    function setValue(bytes memory input) public {
        name = input;
    }
    //3. ⽀持push操作,在bytes最后⾯追加元素
    function pushData() public {
        name.push("h");
    }
}

string:

  • string 动态尺⼨的UTF-8编码字符串,是特殊的可变字节数组
  • string 不⽀持下标索引、不⽀持length、push⽅法
  • string 可以修改(需通过bytes转换)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract String {
    string public name = "lily";
    function setName() public {
        bytes(name)[0] = "L";
     }
    function getLength() public view returns(uint256) {
        return bytes(name).length;
     }
}

struct

  • ⾃定义结构类型,将不同的数据类型组合到⼀个结构中,⽬前⽀持参数传递结构体。
  • 枚举和结构体都可以定义在另外⼀个⽂件中,进⾏import后使⽤
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Todos {
    struct Todo {
        string text;
        bool completed;
     }
    // An array of 'Todo' structs
    Todo[] public todos;
     // [["hello", true], ["world", false]]
    function createByElement(Todo[] memory _todos) public {
        for (uint i = 0; i < _todos.length; i++) {
            todos.push(_todos[i]);
         }
     }
    function create(string memory _text) public {
        // 3 ways to initialize a struct
        // - calling it like a function
        todos.push(Todo(_text, false));
        // key value mapping
        todos.push(Todo({text: _text, completed: false}));
        // initialize an empty struct and then update it
        Todo memory todo;
        todo.text = _text;
        // todo.completed initialized to false
        todos.push(todo);
     }
    // Solidity automatically created a getter for 'todos' so
    // you don't actually need this function.
    function get(uint _index) public view returns (string memory text, bool completed){
        Todo storage todo = todos[_index];
        return (todo.text, todo.completed);
     }
    // update text
    function update(uint _index, string memory _text) public {
        Todo storage todo = todos[_index];
        todo.text = _text;
     }
    // update completed
    function toggleCompleted(uint _index) public {
        Todo storage todo = todos[_index];
        todo.completed = !todo.completed;
    }
}

mapping

  • 定义:mapping(keyType => valueType) myMapping
  • key可以是任意类型,value可以是任意类型(value也可以是mapping或者数组)
  • mapping不⽀持迭代器
  • 不需要实例化等,定义后直接可以使⽤
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Mapping {
    // Mapping from address to uint
    mapping(address => uint) public myMap;
    function get(address _addr) public view returns (uint) {
        // Mapping always returns a value.
        // If the value was never set, it will return the default value.
        return myMap[_addr];
     }
    function set(address _addr, uint _i) public {
        // Update the value at this address
        myMap[_addr] = _i;
     }
    function remove(address _addr) public {
        // Reset the value to the default value.
        delete myMap[_addr];
     }
}
contract NestedMapping {
    // Nested mapping (mapping from address to another mapping)
    mapping(address => mapping(uint => bool)) public nested;
    function get(address _addr1, uint _i) public view returns (bool) {
        // You can get values from a nested mapping
        // even when it is not initialized
        return nested[_addr1][_i];
     }
    function set(address _addr1,uint _i,bool _boo) public {
        nested[_addr1][_i] = _boo;
     }
    function remove(address _addr1, uint _i) public {
        delete nested[_addr1][_i];
     }
}

修饰器modifier

修饰器⽤于修饰函数,在函数执⾏前或执⾏后进⾏调⽤,经常⽤于:
  1. 权限控制
  2. 参数校验
  3. 防⽌重⼊攻击等
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract FunctionModifier {
    // We will use these variables to demonstrate how to use modifiers.
    address public owner;
    uint public x = 10;
    bool public locked;
    constructor() {
        // Set the transaction sender as the owner of the contract.
        owner = msg.sender;
     }
    // 1. Modifier to check that the caller is the owner of the contract.
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        // Underscore is a special character only used inside
        // a function modifier and it tells Solidity to
        // execute the rest of the code.
        _;
     }
    // 2. Modifiers can take inputs. This modifier checks that the
    // address passed in is not the zero address.
    modifier validAddress(address _addr) {
        require(_addr != address(0), "Not valid address");
        _;
     }
    function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {
        owner = _newOwner;
     }
    // Modifiers can be called before and / or after a function.
    // This modifier prevents a function from being called while
    // it is still executing.
    modifier noReentrancy() {
        require(!locked, "No reentrancy");
        locked = true;
        _;
        locked = false;
     }
    function decrement(uint i) public noReentrancy {
        x -= i;
        if (i > 1) {
        decrement(i - 1);
        }
    }
}

事件Event

事件是区块链上的⽇志,每当⽤户发起操作的时候,可以发送相应的事件,常⽤于:
  1. 监听⽤户对合约的调⽤
  2. 便宜的存储(⽤合约存储更加昂贵)
通过链下程序(如:subgraph)对合约进⾏事件监听,可以对Event进⾏搜集整理,从⽽做好数据统计,常⽤⽅式:
  1. 合约触发后发送事件
  2. subgraph对合约事件进⾏监听,计算(如:统计⽤户数量)
  3. 前端程序直接访问subgraph的服务,获得统计数据(这避免了在合约层⾯统计数据的费⽤,并且获取速度更快)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Event {
    // Event declaration
    // Up to 3 parameters can be indexed.
    // Indexed parameters helps you filter the logs by the indexed parameter
    event Log(address indexed sender, string message); // 修饰为indexed
    event AnotherLog(); // ⽆参数的事件
    event TestAnonymous(address indexed sender, uint256 num) anonymous; // 匿名事件
    function test() public {
        emit Log(msg.sender, "Hello World!");
        emit Log(msg.sender, "Hello EVM!");
        emit AnotherLog();
     }
}

可⻅性visibility

合约的⽅法和状态变量需要使⽤关键字进⾏修饰,从⽽决定其是否可以被其他合约调⽤,修饰符包括:
  • public:所有的合约和外部账户(EOA)都可以调⽤;
  • private:只允许合约内部调⽤;
  • internal:仅允许合约内部以及⼦合约中调⽤;
  • external:仅允许外部地址(EOA或CA)调⽤,合约内部及⼦合约都不能调⽤;(早期版本可以使⽤this调⽤external⽅法)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Base {
    // Private function can only be called
    // - inside this contract
    // Contracts that inherit this contract cannot call this function.
    function privateFunc() private pure returns (string memory) {
        return "private function called";
     }
    function testPrivateFunc() public pure returns (string memory) {
        return privateFunc();
     }
    // Internal function can be called
    // - inside this contract
    // - inside contracts that inherit this contract
    function internalFunc() internal pure returns (string memory) {
        return "internal function called";
     }
    function testInternalFunc() public pure virtual returns (string memory) {
        return internalFunc();
     }
    // Public functions can be called
    // - inside this contract
    // - inside contracts that inherit this contract
    // - by other contracts and accounts
    function publicFunc() public pure returns (string memory) {
        return "public function called";
     }
    // External functions can only be called
    // - by other contracts and accounts
    function externalFunc() external pure returns (string memory) {
        return "external function called";
     }
    // This function will not compile since we're trying to call
    // an external function here.
    // function testExternalFunc() public pure returns (string memory) {
    // return externalFunc();
    // }
    // State variables
    string private privateVar = "my private variable";
    string internal internalVar = "my internal variable";
    string public publicVar = "my public variable";
    // State variables cannot be external so this code won't compile.
    // string external externalVar = "my external variable";
}
contract Child is Base {
    // Inherited contracts do not have access to private functions
    // and state variables.
    // function testPrivateFunc() public pure returns (string memory) {
    // return privateFunc();
    // }
    // Internal function call be called inside child contracts.
    function testInternalFunc() public pure override returns (string memory) {
        return internalFunc();
    }
}

ERC20(标准Token)

  1. EIP: Ethereum Improvement Propose
  2. 任何遵从EIP-20协议(ERC20标准)的Contract都属于ERC20 Token

标准接⼝

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
interface IERC20{
    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)
    // 2 REQUIRED EVENTS
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event Approval(address indexed _owner, address indexed _spender, uint256 _value)
    // 3. OPTIONAL FUNCTIONS
    function name() public view returns (string)
    function symbol() public view returns (string)
    function decimals() public view returns (uint8)
}

发⾏Token

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// https://github.com/OpenZeppelin/openzeppelincontracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
interface IERC20 {
     // REQUIRED FUNCTIONS
     // 总发⾏量
    function totalSupply() external view returns (uint); // -> 总发⾏量 100000000 *10**decimals
     // 标准decimal: 18
     // USDT: 6
     // WBTC: 8
    function balanceOf(address account) external view returns (uint); // 指定账户的余额
    // 币的持有⼈直接调⽤,进⾏转账
    function transfer(address recipient, uint amount) external returns (bool);
     // 最常⽤的!!
     // 1. 我这个owner对合约进⾏approve,此时approve内部会修改allowance变量
     // 2. 合约内部调⽤transferFrom来⽀配owner的token
    function transferFrom( // spender就是这个合约
        address sender, // owner
        address recipient, // 转给谁
        uint amount // ⾦额
     ) external returns (bool);
    // owner: 币的持有⼈
     // spender: 是指定帮助花费的代理⼈(被授权的⼈)
    function allowance(address owner, address spender) external view returns (uint); //授权的额度
    // decimals view,这是⼀个public 的变量,⾃动提供了⼀个读取的⽅法 // 返回精度
     // 持有⼈对spender进⾏授权,在approve内部,会调⽤msg.sender来知道owner是谁
    function approve(address spender, uint amount) external returns (bool);
     // 2 REQUIRED EVENTS
     // 事件
    event Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);
    // 3. OPTIONAL FUNCTIONS
    function name() public view returns (string)
    function symbol() public view returns (string)
    function decimals() public view returns (uint8)
}
以下是ERC20的案例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "./IERC20.sol";
contract ERC20 is IERC20 {
    uint public totalSupply;
    mapping(address => uint) public balanceOf;
    mapping(address => mapping(address => uint)) public allowance;
    string public name = "Solidity by Example";
    string public symbol = "SOLBYEX";
    uint8 public decimals = 18;
    function transfer(address recipient, uint amount) external returns (bool) {
        balanceOf[msg.sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(msg.sender, recipient, amount);
        return true;
     }
    function approve(address spender, uint amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }
    function transferFrom(
        address sender,
        address recipient,
        uint amount
         ) external returns (bool) {
        allowance[sender][msg.sender] -= amount;
        balanceOf[sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(sender, recipient, amount);
        return true;
    }
    function mint(uint amount) external {
        balanceOf[msg.sender] += amount;
        totalSupply += amount;
        emit Transfer(address(0), msg.sender, amount);
    }
    function burn(uint amount) external {
        balanceOf[msg.sender] -= amount;
        totalSupply -= amount;
        emit Transfer(msg.sender, address(0), amount);
     }
}
可以使⽤openzeppelin库进⾏创建⾃⼰的token:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// import "https://github.com/OpenZeppelin/openzeppelincontracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        // Mint 100 tokens to msg.sender
        // Similar to how
        // 1 dollar = 100 cents
        // 1 token = 1 * (10 ** decimals)
        _mint(msg.sender, 100 * 10**uint(decimals()));
     }
    // 默认是18,可以进⾏override
    function decimals() public view override returns (uint8) {
        return 6;
    }
}

Token