Solidity-101

课程主页: https://www.wtf.academy/zh/course/solidity101

  1. sol文件第一行为注释license,第二行为pragma,第三行为contract
  2. 类型: bool, int, uint, int256, uint256, address(20 byte), address payable(增加transfer和send,balance, code, codehash用于ETH转移), bytes32, bytes1, bytes8, bytes, enum, string
function <function name>([parameter types[, ...]]) {internal|external|public|private} [pure|view|payable] [virtual|override] [<modifiers>]
[returns (<return types>)]{ <function body> }
  1. 函数可见性
  • public: 内部和外部均可见
  • private: 只能从本合约内部访问,继承的合约也不能使用
  • external: 只能从合约外部访问(但内部可以通过this.f()来调用, f是函数名)
  • interval: 只能从合约内部访问,继承的合约可以用,default
    合约中定义的函数需要明确指定可见性,它们没有默认值
    public|private|interval也可用于修饰状态变量,public变量会自动生成同名的getter函数,用于查询数值。未标明可见性类型的状态变量,默认未internal
  1. 权限/功能关键字,pure|view|payable
  • payable(可支付的),带着它的函数,运行的时候可以给合约转入ETH,可以读和写链上的状态变量。
    如果函数标记为 payable,用户调用时可以附带 ETH,这些 ETH 会存入合约地址(address(this).balance 会增加)。
  • pure 该关键字的函数不改写链上状态,用户直接掉应用它们不需要付gas,不能读或写链上的状态变量
  • view 该关键字的函数不改写链上状态,用户直接掉应用它们不需要付gas,可以读但不能写链上的状态变量
  1. <modifiers>: 自定义的修饰器,可以有0个或多个修饰器
  2. [returns ()]: 函数返回的变量类型和名称
  3. 在以太坊中,以下语句被视为修改链上状态:
    1. 写入状态变量
    1. 释放时间
    1. 创建其他合约
    1. 使用selfdestruct
    1. 通过调用发送以太币
    1. 调用任何未标记viewpure的函数
    1. 使用低级调用(low-level calls)
    1. 使用包含某些操作码的内联汇编
  1. pragma solidity >=0.8.18 <0.9.0
  2. 解构式赋值
function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array) {
    // 如果写_number - 2会提示运行revert错误
    _number = 2;
    _bool = false;
    _array = [uint256(3), 2, 1];
}

function readReturn() public pure {
    uint256 _number;
    bool _bool;
    uint256[3] memory _array;
    (_number, _bool, _array) = returnNamed();

    (, _bool2, ) = returnNamed();
}
  1. array和struct是引用类型,由于这些变量比较复杂,占用空间大,使用时必须声明数据存储位置.
  2. data localtion,不同存储位置的gas成本不同。整体消耗gas从多到少为storage>memory>calldata
  • storage 存储在链上,类似计算机硬盘,消耗gas多,合约里的状态变量默认都是storage
  • memory 临时存在内存里,不上链,返回数据是变长的情况下,必须加memory修饰,例如: string, bytes, array和自定义结构
  • calldata 临时存在内存里,和memory类似,不同点是不能修改immutable,一般用于函数参数
  1. 数据位置和赋值规则
    在不同存储类型相互赋值时候,有时会产生独立的副本,有时会产生引用。
  • storage赋值给本地函数storage,会创建引用,改变新变量会影响原变量
  • memory赋值给memory,会创建引用,改变新变量会影响原变量
  1. 变量的作用域
  • state variable 状态变量,也叫合约变量
  • local variable 局部变量,在函数里定义
  • global variable 全局变量,通过预留关键字访问,如:
    blockhash(uint blockNumber): (bytes32) 给定区块的哈希值 – 只适用于最近的256个区块, 不包含当前区块。
    block.coinbase: (address payable) 当前区块矿工的地址
    block.gaslimit: (uint) 当前区块的gaslimit
    block.number: (uint) 当前区块的number
    block.timestamp: (uint) 当前区块的时间戳,为unix纪元以来的秒
    gasleft(): (uint256) 剩余 gas
    msg.data: (bytes calldata) 完整call data
    msg.sender: (address payable) 消息发送者 (当前 caller)
    msg.sig: (bytes4) calldata的前四个字节 (function identifier)
    msg.value: (uint) 当前交易发送的 wei 值
    block.blobbasefee: (uint) 当前区块的blob基础费用。这是Cancun升级新增的全局变量。
    blobhash(uint index): (bytes32) 返回跟当前交易关联的第 index 个blob的版本化哈希(第一个字节为版本号,当前为0x01,后面接KZG承诺的SHA256哈希的最后31个字节)。若当前交易不包含blob,则返回空字节。这是Cancun升级新增的全局变量
  1. 以太单位, wei, gwei, ehter
assert(1 wei = 1e0)
assert(1 gwei = 1e9)
assert(1 ether = 1e18)
  1. 时间单位, seconds, minutes, hours, days, weeks
  2. bytesbytes1省gas
  3. 数据的创建
    1. 对于memory修饰的动态数组,可以用new操作符来创建,但是必须声明长度,声明后长度不能改变
uint[] memory array8 = new uint[](5);
bytes memory array9 = new bytes(9);
uint256[3] memory _array = [uint256(1), 2, 3]; // 静态数组赋值
    1. 动态数组赋值只能先分配空间再一个一个赋值
  1. 数组成员
  • length
  • push()
  • push(x)
  • pop()
  1. mapping的存储位置必须时storage,因此可以用于合约的状态变量,函数中的storage变量和library函数的参数。不能用于public函数的参数或返回结果中。如果映射声明为public,那么Solidity会自动给你创建一个getter函数,可以通过Key来查询对应的Value
  2. 映射不存储任何键Key的资讯。真的?????
  3. 对应映射使用keccak256(h(key) . slot)计算存取value的值
  4. delete操作符会让变量变为初始值
  5. constantimmutable状态声明变量后,不能在初始化后更改数值。这样做可以提升合约安全性和节省gas。只有数值变量可以声明constantimmutablestringbytes可以声明为constant,但不能为immutable
  • constant变量必须在声明的时候初始化,之后再也不能改变。
  • immutable可以在声明或/构造函数/中初始化
  1. 修饰器modifier是Solidity的特殊语法,类似面向对象的decorator,声明函数拥有的特性,并减少代码冗余。
modifier onlyOwner {
    require(msg.sender == owner);
    _;
}
function changeOwner(address _newOwner) external onlyOwner {
    owner = _nerOwner;
}
  1. 继承的属性和方法
  • virtual:父合约中的函数,如果希望子合约重写,需要加上virtual关键字
  • override: 子合约重写了父合约中的函数,需要加上override关键字
  • 注意:用override修饰public变量,会重写与变量同名的getter函数
  1. 继承写法
  • 继承时要按辈分最高到低的顺序排序
  • 如果一个函数在/多个继承/的合约里都存在,必须重写,不要会报错
  • 重写在多个父合约中都重名的函数时,override关键字后面要加上所有父合约名字,例如override(Yeye, Baba)
  1. Solidity中修饰器modifier同样可以继承,用法与函数继承类似,在相应的地方加virtualoverride关键字即可
  2. 构造函数的继承
abstract contract A {
    uint public a;
    constructor(uint _a) {
        a = _a;
    }
}
  • 在继承时声明父构造函数的参数,例如contract B is A(1)
  • 在子合约的构造函数中声明构造函数的参数,例如
contract C is A {
    constructor(uint _c) A(_c * _c) {}
}
  1. 子合约调用父合约的函数有直接调用和super调用两种方式
  • 直接调用:子合约可以直接用父合约名.函数名()方式调用父合约函数,例如Yeye.pop()
  • super关键字: 子合约可以利用super.函数名()来调用/最近的父合约函数/。Solidity继承关系按声明时从右到左的顺序
  1. 钻石继承:在多重+钻石继承链条上使用super关键字时,super会调用继承链条上的每一个合约相关函数,而不是只调用最近的父合约,但是钻石继承的重复父节点的方法只会调用一次。 //// 这句话例子似乎有问题
  2. 抽象合约abstract和接口interface
  • 一个智能合约至少有一个未实现的函数,该合约必须标为abstract,不然编译会报错,另外未实现的函数需要加virtual,以便子合约重写
  • 接口interface类似于抽象合约,但它不实现任何功能。接口的规则:
      1. 不能包含状态变量
      1. 不能包含构造函数
      1. 不能继承除接口外的其他合约
      1. 所有函数都必须是external且不能有函数体
      1. 继承接口的非抽象合约必须实现接口定义的所有功能
  1. 如果智能合约实现了某种接口,其他Dapps和智能合约就知道如何与它交互。因为接口提供了两个重要的信息:
    1. 合约里每个函数的bytes4选择器,以及函数签名函数名(每个参数类型)
    1. 接口id
  1. 另外接口与合约ABI等价,可以相互转换:编译接口可以得到合约的ABI,利用abi-to-sol工具,也可以将ABI json文件转换未接口sol文件。
  2. 如果我们知道一个合约实现了IERA721接口,不需要知道它具体代码实现,就可以与它交互。
IERC721 BAYC = IERC721(/*合约地址*/)
  1. 三种抛出异常的方法: error, requireassert,gas消耗不同
  • error,0.8.4版本后增加,gas少,可以在contract之外定义异常,可以带参数,error必须搭配revert使用
error TransferNotOwner(address sender);
function transferOwner(uint256 tokenId, address newOwner) public {
    if(_owners[tokenId] != msg.sender) {
        revert TransferNotOwner();
    }
    _owners[tokenId] = newOwner;
}
  • require,0.8版本增加,gas消耗随着异常的字符串长度增加,比error命令要高。require(检查条件, "异常的描述")
  • assert,一般用于断言,assert(检查条件),条件不成立的时候,抛出异常
posted @ 2025-08-09 00:05  Nameless_gb  阅读(21)  评论(0)    收藏  举报