基本类型

布尔类型

布尔类型取值只能为 truefalse。支持常见的逻辑操作符:

ValueTypeBoolean.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.24;

contract ValueTypeBoolean {
  function boolType() private pure {
    bool True = true;
    bool False = false;

    // ! 取反操作
    assert(!False); // 断言成功
    assert(!True);  // 断言失败

    // && 与操作
    assert(True && False); // 断言失败

    // || 或操作
    assert(True || False); // 断言成功

    // == 比较相等
    assert(True == False); // 断言失败

    // != 比较不等
    assert(True != False); // 断言成功
  }
}

&&|| 两种操作支持提前短路:即 a && b,如果 a 为假,则 b 不再处理。对 a || b,如果 a 为真,则 b 不再处理。

整数类型

Solidity 中一个字为 256 位。因此,带符号整数 int 与无符号整数 uint 默认为 256 位,即 intint256uintuint256 是等价的。

除了上面四种类型,Solidity 还支持 intN, uintN,其中 8N2568 \le N \le 256,且 N 为 8 的整数倍。

整数比较

支持常见的比较符:

  • 不等:!=
  • 相等:==
  • 小于:<
  • 大于:>
  • 小于等于:<=
  • 大于等于:>=

判断结果为一个布尔型。

四则运算

支持四则运算:

  • 加法:+
  • 减法:-
  • 乘法:*
  • 除法:/

需要注意,整数相除结果仍为整数,向0取整。

取值范围与溢出检查

要获取某一种类型的最大值或最小值,使用 type(T).mintype(T).max

ValueTypeIntegerRange.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract ValueTypeIntegerRange {
  function range() public pure returns (int8, int8) {
    return (type(int8).min, type(int8).max);
  }
}

以上 range 函数将返回:(-128, 127),分别为 8 位补码整数最小值及最大值。

当数值运算过程中出现溢出,Solidity 默认会进行检查,从而导致失败错误。这是因为在智能合约中,数值溢出,用户可能会蒙受极大损失。例如,一个 uint8 变量由 255 加 1 后变为了 0,这样可能会清空用户的数字资产。

如果允许溢出,则需要使用 unchecked {} 将代码显式包裹起来:

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

contract ValueTypeUnchecked {
    function defaultChecked() public pure returns (uint8) {
        return uint8(0) - 1;
    }

    function uncheckedWrap() public pure returns (uint8) {
        unchecked {
            return uint8(0) - 1;
        }
    }
}

调用以上 defaultChecked 提示:

shell
复制
call to ValueTypeUnchecked.defaultChecked errored: Error occurred: revert.

revert
	The transaction has been reverted to the initial state.

由于发生了错误,整个调用被撤销,revert(恢复)到原来的状态。

调用 uncheckedWrap ,返回 255

由于有符号数使用补码,补码的负数取值范围比正数取值范围大1,如 uint8 取值范围为 [128,127][-128, 127]。因此对 -type(uint8).min 也会导致溢出错误,因为 128 无法保存在 uint8 类型中:

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

contract ValueTypeSignedRange {
    function defaultChecked() public pure returns (int8) {
        return -type(int8).min;
    }

    function uncheckedWrap() public pure returns (int8) {
        unchecked {
            return -type(int8).min;
        }
    }
}

执行上面的 defaultChecked 将导致溢出错误,交易被撤销;执行 uncheckedWrap 会发现,-type(int8).min 结果仍是原值 -128

type(int8).min / (-1) 也会导致以上情况,读者可自行验证。

求余操作

求余操作,如 a % b ,等价于 a - int(a / b) * b。其中 int(a / b) 表示对 a / b 取整,根据上面提及的整数除法规则,向 0 取整:

  • int(5) % int(2) == int(1)int(5/2)=2522=1int(5 / 2) = 2\Rightarrow 5 - 2 * 2 = 1
  • int(5) % int(-2) == int(1)int(5/2)=int(2.5)=25(2)(2)=1int(5 / -2) = int(-2.5) = -2\Rightarrow 5 - (-2) * (-2) = 1
  • int(-5) % int(2) == int(-1)int(5/2)=int(2.5)=25(22)=1int(-5 / 2) = int(-2.5) = -2\Rightarrow -5 - (2 * -2) = -1
  • int(-5) % int(-2) == int(-1)int(5/2)=int(2.5)=25(22)=1int(-5 / -2) = int(2.5) = 2\Rightarrow -5 - (2 * -2) = -1

求幂

求x的n次幂:x**n。定义 0 的 0 次幂为 1。

位操作

支持一般的位操作:

  • 取反:~
  • 位与:&
  • 位或:|
  • 异或:^
  • 左移:<<
  • 右移:>>

有符号数使用二进制补码表示,且左、右移不导致溢出:

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

contract ValueTypeBitOps {
    function bitOps() public pure returns (int8, uint8, uint8, uint8, uint8, uint8, uint8) {
      return (
        ~int8(0),     // ~(00000000) = 11111111 = -1
        ~uint8(0),    // ~(00000000) = 11111111 = 255 
        uint8(0xff) & uint8(0), // 11111111 & 00000000 = 00000000 = 0
        uint8(0xff) | uint8(0), // 11111111 | 00000000 = 11111111 = 255
        uint8(0xf0) ^ uint8(0), // 11110000 ^ 00000000 = 11110000 = 240
        uint8(1) << 1, // 00000001 << 1 = 00000010 = 2
        uint8(1) >> 1  // 00000001 >> 1 = 00000000 = 0 ,不导致溢出
      );
    }
}

运行 bitOps 将返回:-1, 255, 0, 255, 240, 2, 0

定点小数

Solidity 定点小数关键字有 fixed/ufixed,它们表示有符号定点小数和无符号定点小数。

fixed/ufixedfixed128x18/ufixed128x18 的等价表示,指该定点小数占用128位,其中18位用于表示小数部分。

定点小数类型可以写为 fixedMxN/ufixedMxN,其中 8M\e256,0<N<808 \le M \e 256, 0 < N < 80,其中 MM 必须为 8 的整数倍。

但是,Solidity 并没有完全支持定点小数。因此如果需要用到小数,需要寻找第三方库使用。

地址类型

使用关键字 address 用于声明一个地址。一个以太坊地址占用 20 字节。因此长度同样为 20 字节的 uint160/bytes20 可以转换为 address 类型。

如果是超过 20 字节的类型转换为 address,则会发生截断。

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

contract ValueTypeAddress {
    function addressConvert() public pure returns (address, address) {
      uint a = 0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC;
      bytes32 b = bytes32(0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC);

      return (
        address(uint160(a)), // 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc 
        address(bytes20(b)) // 0x111122223333444455556666777788889999aAaa
      );
    }
}

以上 uint256 -> uint160 截断为数字截断,保留数字低位部分。

bytes32 -> bytes20 截断为字节数组截断,保留数组低地址数据(数组左边数据)。

除了 uint160 以及 bytes20,合约类型(contract) 也可以被显式转换为地址。转换后的地址即是合约部署的地址。

可支付地址(payable address)

payable 修饰的 address 是可支付地址。表明可以发送以太币到该地址。可支付地址具有 transfersend 方法。您可以调用这两个方法来发送以太币。

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

contract ValueTypePayableAddress {
    function sendCoin() public payable  {
        address payable addr = payable(
            0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
        );
        addr.transfer(msg.value);
    }
}

如果一个合约中具有 receive 函数,或者具有一个 payable 修饰的 fallback 函数,那么该合约可以通过它的地址接受以太币。这类合约可以被显式转换为可支付地址。

实际上,即使合约没有实现 receivefallback 函数,你也可以使用 payable 将合约强制转换为可支付地址,但向该合约发送以太币将触发错误。

可支付地址相比一般地址,是一种 “更严格” 的地址。可支付的地址可以隐式转换为可支付地址,但是一般地址要转换为可支付地址则必须使用 payable 显式转换。