常量&不变量

常量

常量可以声明在合约中,也可以声明在文件级别:

Constants.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

// 文件级别常量
uint constant KG = 1 * 1000;

contract Constants {
    // 合约内定义常量
    uint constant TON = 1 * 1000 * KG;

    function tenTons() public pure returns (uint) {
        return 1 * TON;
    }
}

常量的值必须是在编译时,就可以确定下来的。

常量并不是变量,因此即使是声明在合约内部,实际上也不会为常量分配存储槽。相反,由于它的值编译时就确定下来且永远不变,因此编译器会把它直接复制到任何一个使用它的地方。

它类似于我们在 C/C++ 中使用的 #define KG (1000) 宏定义常量:在每一个使用 KG 的地方都会原地展开,替换为 (1000)

因此,常量实际上是内联到编译后的操作码 opcode 中的。

常量目前支持所有 值类型以及字节数组(包括 string)。

不变量

不变量与常量相比,不变量不要求其值必须在编译时就可以确定下来,而是要求其值在合约实例化(或部署时)执行构造函数 constructor 之后确定下来。

因此,不变量只能声明在合约内:

Immutable.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

contract Immutable {
    uint256 tenTons;

    /* 合约内定义不变量 */
    // 可以在声明时赋值,相当于在 constructor 开始时就赋值
    uint256 immutable KG = 1 * 1000;
    uint256 immutable TON;
    address immutable owner;
    uint256 immutable neverAssigned;

    constructor() {
        // 可以多次赋值
        TON = 100 * KG;
        TON = 1000 * KG;

        // 使用 immutable 的 owner,后续无法再修改
        owner = msg.sender;
        tenTons = 10 * TON;
    }
}

可以在 constructor 内为不变量赋值,可以多次赋值。甚至也可以一次都不赋值,此时不变量的值就是它的初始零值。

如果在合约内声明不变量的同时为它赋值,那么就相当于在 constructor 执行时首先为这些不变量赋值。

与常量一样的是,不变量虽然声明在合约内,实际上也不会为它分配存储槽。

不变量与常量直接原地展开不同。不变量在编译时,编译器会标识代码中何处引用了该不变量,并为不变量的引用预留 32 字节的空间。当部署合约时,EVM 执行 constructor 函数,执行完毕后不变量的值就确定了下来,接着 EVM 将修改字节码,检查所有引用了不变量地方,将不变量的值填充到该位置。最后修改完成的代码作为合约代码写入到合约地址中去。

不变量只支持 值类型,而且无论该值类型长度有多少字节,不变量在栈中参与运算时,都会扩展到32字节。