Solidity 字面量和基本类型转换完全指南
字面量和基本类型的转换是 Solidity 编程中的基础技能,掌握这些转换规则和技巧可以让您编写更高效、更安全的智能合约。本指南系统地介绍 Solidity 中字面量与各基本类型之间的转换方法、常见陷阱和最佳实践。
目录
- Solidity 字面量概述
- 整型字面量转换
- 字符串字面量转换
- 十六进制字面量转换
- 地址字面量转换
- 布尔字面量转换
- 有理数和科学计数法字面量
- 特殊字面量常量
- 复合转换和类型推断
- 类型转换安全考量
- Gas 优化技巧
- 实际应用示例
1. Solidity 字面量概述
Solidity 中的字面量(Literals)是直接在代码中表示固定值的表示法。理解不同类型的字面量及其转换规则是良好 Solidity 编程的基础。
主要字面量类型
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract LiteralsOverview {
// 整型字面量示例
function integerLiterals() public pure returns (uint256, int256, uint8) {
uint256 a = 123; // 十进制字面量
int256 b = -456; // 负整数字面量
uint8 c = 0xFF; // 十六进制字面量
uint256 d = 0b1010; // 二进制字面量
uint256 e = 1_000_000; // 带下划线分隔的字面量
return (a, b, c);
}
// 字符串字面量示例
function stringLiterals() public pure returns (string memory, string memory) {
string memory a = "Hello, Solidity!"; // 普通字符串
string memory b = 'Single quotes also work'; // 单引号字符串
string memory c = "Line 1\nLine 2"; // 带转义字符的字符串
string memory d = "This is a very long string " // 跨行字符串
"that spans multiple lines";
return (a, d);
}
// 十六进制字面量示例
function hexLiterals() public pure returns (bytes memory, bytes4, bytes32) {
bytes memory a = hex"01020304"; // 十六进制字面量创建字节数组
bytes4 b = hex"01020304"; // 十六进制字面量创建固定大小字节数组
bytes32 c = hex"0102030400000000000000000000000000000000000000000000000000000000";
return (a, b, c);
}
// 地址字面量示例
function addressLiterals() public pure returns (address, address payable) {
address a = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address payable b = payable(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
return (a, b);
}
// 布尔字面量示例
function booleanLiterals() public pure returns (bool, bool) {
bool a = true;
bool b = false;
return (a, b);
}
// 有理数字面量示例
function rationalLiterals() public pure returns (uint256, uint256) {
uint256 a = 0.5 * 2; // 有理数字面量 (计算为 1)
uint256 b = 1e18; // 科学计数法
uint256 c = 2.5e3; // 带小数点的科学计数法 (= 2500)
return (a, b);
}
// 特殊字面量单位
function specialUnits() public pure returns (uint256, uint256, uint256) {
uint256 a = 1 ether; // 1 ether = 10^18 wei
uint256 b = 1 gwei; // 1 gwei = 10^9 wei
uint256 c = 1 wei; // 基本单位
uint256 d = 1 days; // 1 days = 86400 seconds
uint256 e = 1 hours; // 1 hours = 3600 seconds
uint256 f = 1 minutes; // 1 minutes = 60 seconds
return (a, b, f);
}
}
字面量到类型的一般转换规则
- 隐式转换:当字面量自然适合目标类型时发生
- 显式转换:通过指定类型强制转换
- 根据上下文推断:通常情况下,字面量会被推断为最合适的类型
2. 整型字面量转换
整型字面量是最常用的字面量类型,可以表示为十进制、十六进制或二进制形式,并可以转换为各种整型。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract IntegerLiteralConversions {
// 隐式转换示例
function implicitConversions() public pure returns (
uint8, uint16, uint256, int8, int256
) {
uint8 a = 100; // 十进制字面量隐式转换为 uint8
uint16 b = 0xFF; // 十六进制字面量隐式转换为 uint16
uint256 c = 0b1010; // 二进制字面量隐式转换为 uint256
int8 d = -10; // 负整数字面量隐式转换为 int8
int256 e = 1_000_000; // 带下划线的字面量隐式转换为 int256
return (a, b, c, d, e);
}
// 隐式转换边界检查
function boundaryCases() public pure returns (bool, bool) {
// 边界情况:每种类型的最大值
uint8 maxUint8 = 255; // uint8 最大值
// uint8 overflowUint8 = 256; // 错误:字面量不适合 uint8
int8 maxInt8 = 127; // int8 最大值
int8 minInt8 = -128; // int8 最小值
// int8 overflowInt8 = 128; // 错误:字面量不适合 int8
// 编译器会检查字面量与类型兼容性
return (maxUint8 == 255, minInt8 == -128);
}
// 显式转换整型字面量
function explicitConversions() public pure returns (
uint8, int8, uint256, int256
) {
uint8 a = uint8(255); // 显式转换为 uint8
int8 b = int8(-10); // 显式转换为 int8
uint256 c = uint256(0xFF); // 显式转换为 uint256
int256 d = int256(0b1010); // 显式转换为 int256
return (a, b, c, d);
}
// 转换可能会发生的问题
function conversionIssues() public pure returns (
uint8, uint8, int8, int8
) {
// 下面的转换会在编译时检查
// uint8 overflow = uint8(300); // 错误: 超出范围
// 在运行时强制转换可能会导致截断
uint256 largeValue = 300;
uint8 truncated = uint8(largeValue); // 结果为 300 % 256 = 44
uint256 anotherLarge = 257;
uint8 anotherTruncated = uint8(anotherLarge); // 结果为 257 % 256 = 1
// 负数转换
int256 negativeValue = -10;
// uint8 negativeToUnsigned = uint8(negativeValue); // 将会报错或产生意外结果
int256 largeNegative = -150;
int8 truncatedNegative = int8(largeNegative); // 结果为 -150 % 256 = -150 (在范围内)
int256 veryLargeNegative = -200;
int8 truncatedVeryNegative = int8(veryLargeNegative); // 结果为 -200 (在范围内)
return (truncated, anotherTruncated, truncatedNegative, truncatedVeryNegative);
}
// 字面量类型推断
function literalTypeInference() public pure returns (
uint256, int256, uint8, int8
) {
// 字面量默认推断为适当的最小类型
uint256 a = 100; // 字面量 100 推断为 uint8,但变量为 uint256
int256 b = -100; // 字面量 -100 推断为 int8,但变量为 int256
// 当用于计算时,字面量会根据表达式的需要调整类型
uint8 c = 100 + 100; // 尽管 100+100=200 仍能容纳在 uint8 中
int8 d = -100 + 50; // 结果 -50 容纳在 int8 中
return (a, b, c, d);
}
// 整型字面量在表达式中的行为
function literalsInExpressions() public pure returns (
uint256, uint256, uint8
) {
// 字面量在表达式中使用时会具有灵活的类型推断
uint256 a = 1 + 2 * 3; // 正常计算:字面量自动推断为需要的类型
// 混合使用不同类型的字面量
uint256 b = 0xff + 10; // 十六进制和十进制混合:0xff (255) + 10 = 265
// 计算结果仍需要适合目标变量类型
uint8 c = 0xff - 100; // 255 - 100 = 155,适合 uint8
// uint8 d = 0xff + 100; // 错误:255 + 100 = 355 超出 uint8 范围
return (a, b, c);
}
// 整型字面量常量
uint256 public constant SUPPLY = 1_000_000; // 直接使用字面量定义常量
int8 public constant MIN_TEMP = -10;
uint256 public constant MAX_PLAYERS = 4 + 6; // 字面量表达式定义常量
// 进制系统之间的转换
function baseConversions() public pure returns (
uint256, uint256, uint256, bytes memory
) {
// 不同进制表示同一个值
uint256 a = 255; // 十进制 255
uint256 b = 0xff; // 十六进制 ff = 255
uint256 c = 0b11111111; // 二进制 11111111 = 255
// 将整型转换为字节表示
bytes memory d = abi.encodePacked(uint8(255)); // [255]
return (a, b, c, d);
}
// 安全地处理整型字面量转换
function safeConversions(uint256 value) public pure returns (uint8, bool) {
// 检查值是否适合目标类型
if (value <= type(uint8).max) {
return (uint8(value), true);
} else {
return (0, false); // 返回默认值和失败标志
}
}
}
关键要点
-
整型字面量默认推断:
- 非负整型字面量默认推断为
uint8
/uint16
/...,取能容纳该值的最小类型 - 负整型字面量默认推断为
int8
/int16
/...,取能容纳该值的最小类型
- 非负整型字面量默认推断为
-
整型字面量表示法:
- 十进制:
123
,-45
,1_000_000
(下划线用于提高可读性) - 十六进制:
0xFF
,0xaBcD12
- 二进制:
0b1010
,0b0101
- 十进制:
-
整型字面量边界检查:
- 编译时会检查字面量是否适合目标类型
- 运行时强制转换可能导致截断和意外结果
-
常量表达式:
- 常量表达式在编译时计算
- 字面量在常量表达式中保持完全精度,直到最终分配
3. 字符串字面量转换
字符串字面量代表文本数据,可以转换为 string
或 bytes
类型。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract StringLiteralConversions {
// 基本字符串字面量分配
function basicStringAssignment() public pure returns (
string memory, string memory, string memory
) {
string memory a = "Hello, Solidity!"; // 直接分配字符串字面量
string memory b = 'Single quotes work too'; // 单引号字符串
string memory c = "First line\nSecond line"; // 带转义字符的字符串
return (a, b, c);
}
// 字符串字面量连接
function stringConcatenation() public pure returns (string memory) {
// 相邻的字符串字面量会自动连接
string memory result = "Hello, " "Solidity!"; // 等同于 "Hello, Solidity!"
// 多行字符串连接
string memory multiLine = "This is a long string "
"that spans multiple "
"lines in the code.";
return multiLine;
}
// 字符串字面量转换为字节类型
function stringToBytes() public pure returns (
bytes memory, bytes memory, bytes32
) {
// 字符串字面量转换为动态字节数组
bytes memory a = bytes("Hello"); // 创建长度为5的字节数组
// 直接从字符串字面量创建字节数组
bytes memory b = "World"; // 隐式转换为字节数组
// 转换为固定大小的字节数组(需要手动处理填充和截断)
string memory str = "Fixed";
bytes memory strBytes = bytes(str);
bytes32 c = bytes32(0); // 初始化为零
assembly {
c := mload(add(strBytes, 32))
}
return (a, b, c);
}
// 字符串字面量与十六进制字面量的比较
function compareWithHexLiteral() public pure returns (bool, bool, bool) {
// ASCII 字符串 "A" 的十六进制表示是 0x41
bytes1 a1 = bytes("A")[0];
bytes1 a2 = hex"41";
bool equal1 = (a1 == a2); // true
// 字符串 "ABC" 的字节表示
bytes memory b1 = bytes("ABC"); // [65, 66, 67]
bytes memory b2 = hex"414243"; // [65, 66, 67]
bool equal2 = keccak256(b1) == keccak256(b2); // true
// 非 ASCII 字符(UTF-8 编码)
string memory c1 = "€"; // 欧元符号 (UTF-8: e2 82 ac)
bytes memory c2 = hex"e282ac";
bool equal3 = keccak256(bytes(c1)) == keccak256(c2); // true
return (equal1, equal2, equal3);
}
// 字符串长度和编码
function stringLength() public pure returns (
uint256, uint256, uint256
) {
// ASCII 字符串的长度
uint256 asciiLength = bytes("Hello").length; // 5
// Unicode 字符串的长度(注意:长度是字节数,不是字符数)
uint256 unicodeLength = bytes("Hello 世界").length; // 12 (5 ASCII + 2 Unicode * 3 bytes + 1 space)
// 空字符串长度
uint256 emptyLength = bytes("").length; // 0
return (asciiLength, unicodeLength, emptyLength);
}
// 转义字符处理
function escapeSequences() public pure returns (
bytes memory, bytes memory, bytes memory, bytes memory
) {
// 常见转义序列
bytes memory newline = bytes("Line1\nLine2"); // '\n' = ASCII 10
bytes memory tab = bytes("Column1\tColumn2"); // '\t' = ASCII 9
bytes memory quote = bytes("She said, \"Hello!\""); // 转义引号
bytes memory backslash = bytes("C:\\Program Files\\App"); // 转义反斜杠
return (newline, tab, quote, backslash);
}
// 从字符串字面量提取单个字符
function extractCharacters() public pure returns (
bytes1, bytes1, uint8, uint8
) {
string memory text = "ABCD";
bytes memory textBytes = bytes(text);
// 提取单个字符(作为字节)
bytes1 firstChar = textBytes[0]; // 'A'
bytes1 lastChar = textBytes[textBytes.length - 1]; // 'D'
// 转换为 ASCII 码值
uint8 firstCode = uint8(firstChar); // 65
uint8 lastCode = uint8(lastChar); // 68
return (firstChar, lastChar, firstCode, lastCode);
}
// 字符串字面量常量
string public constant GREETING = "Hello, Constant!";
bytes32 public constant STRING_HASH = keccak256("HashMe");
// 字符串与其他类型的交互
function stringWithOtherTypes() public pure returns (
string memory, string memory, bytes memory
) {
// 使用 abi.encodePacked 连接字符串与其他类型
string memory name = "Alice";
uint256 age = 30;
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
bytes memory packed = abi.encodePacked(name, " is ", age, " at ", addr);
// 注意:虽然这可以连接为字节,但没有简单的方法直接转换回可读的字符串
// 一种简化的方法是使用多个转换
string memory numStr = uint2str(age);
string memory addrStr = address2str(addr);
string memory combined = string(abi.encodePacked(name, " is ", numStr, " at ", addrStr));
return (name, combined, packed);
}
// 辅助函数:整数转字符串
function uint2str(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + value % 10));
value /= 10;
}
return string(buffer);
}
// 辅助函数:地址转字符串
function address2str(address addr) internal pure returns (string memory) {
bytes32 value = bytes32(uint256(uint160(addr)));
bytes memory alphabet = "0123456789abcdef";
bytes memory str = new bytes(42);
str[0] = "0";
str[1] = "x";
for (uint256 i = 0; i < 20; i++) {
str[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)];
str[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];
}
return string(str);
}
}
关键要点
-
字符串字面量表示:
- 双引号:
"Text"
- 单引号:
'Text'
- 多行:相邻字符串自动连接
- 双引号:
-
转义序列:
\n
:换行\r
:回车\t
:制表符\"
,\'
:引号\\
:反斜杠
-
字符串到字节转换:
bytes(stringVar)
将字符串转为动态字节数组string(bytesVar)
将字节数组转为字符串
-
编码注意事项:
- 字符串使用 UTF-8 编码
- 非 ASCII 字符占用多个字节
- 字符串长度指字节数,不是字符数
-
字符串比较:
- 直接比较需要使用哈希:
keccak256(bytes(a)) == keccak256(bytes(b))
- 直接比较需要使用哈希:
4. 十六进制字面量转换
十六进制字面量提供了直接表示和操作二进制数据的方式,这在处理哈希、签名和编码数据时特别有用。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract HexLiteralConversions {
// 基本十六进制字面量
function basicHexLiterals() public pure returns (
bytes memory, bytes1, bytes2, bytes32
) {
// 动态大小字节数组
bytes memory a = hex"01020304"; // [1, 2, 3, 4]
// 固定大小字节数组
bytes1 b = hex"0a"; // [10]
bytes2 c = hex"0102"; // [1, 2]
bytes32 d = hex"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
return (a, b, c, d);
}
// 十六进制字面量与整型转换
function hexToIntegers() public pure returns (
uint8, uint16, uint256, uint256
) {
// 从十六进制字面量到整型
uint8 a = uint8(hex"0a"); // 10
uint16 b = uint16(hex"0102"); // 258 (= 1*256 + 2)
uint256 c = uint256(bytes32(hex"0000000000000000000000000000000000000000000000000000000000000001"));
// 从十六进制字面量创建的字节数组到整型
bytes memory d_bytes = hex"000000000000000000000000000000000000000000000000000000000000000a";
uint256 d;
assembly {
d := mload(add(d_bytes, 32))
}
return (a, b, c, d);
}
// 十六进制字面量与地址转换
function hexToAddress() public pure returns (
address, address payable, bool
) {
// 从十六进制字面量到地址
address a = address(hex"5B38Da6a701c568545dCfcB03FcB875f56beddC4");
// 从十六进制字面量到可支付地址
address payable b = payable(address(hex"5B38Da6a701c568545dCfcB03FcB875f56beddC4"));
// 验证转换正确性
bool correctConversion = (a == 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
return (a, b, correctConversion);
}
// 十六进制字面量与字符串互转
function hexAndStringConversion() public pure returns (
string memory, bytes memory, bool
) {
// 十六进制字面量表示 ASCII 字符串 "Hello"
bytes memory hexBytes = hex"48656c6c6f"; // ASCII for "Hello"
// 转换为字符串
string memory str = string(hexBytes);
// 字符串转回字节比较
bytes memory strBytes = bytes(str);
// 验证转换
bool equal = keccak256(hexBytes) == keccak256(strBytes);
return (str, hexBytes, equal);
}
// 十六进制字面量截断和填充
function hexPaddingAndTruncation() public pure returns (
bytes16, bytes32, bytes8
) {
// 小值到大容器(填充)
bytes1 small = hex"0a";
bytes16 padded = bytes16(small); // 结果: 0x0a000000000000000000000000000000
// 大值到大容器(填充)
bytes16 medium = hex"0102030405060708090a0b0c0d0e0f10";
bytes32 paddedLarge = bytes32(medium); // 添加 16 字节的零填充
// 大值到小容器(截断)
bytes16 large = hex"0102030405060708090a0b0c0d0e0f10";
bytes8 truncated = bytes8(large); // 结果: 0x0102030405060708 (截断了后面的字节)
return (padded, paddedLarge, truncated);
}
// 十六进制字面量与函数选择器
function functionSelectors() public pure returns (
bytes4, bytes4, bool
) {
// 方法 1:直接使用十六进制字面量
bytes4 transferSelector = hex"a9059cbb"; // ERC20 transfer 函数选择器
// 方法 2:使用 keccak256 计算
bytes4 calculatedSelector = bytes4(keccak256("transfer(address,uint256)"));
// 比较两种方法
bool selectorsMatch = (transferSelector == calculatedSelector);
return (transferSelector, calculatedSelector, selectorsMatch);
}
// 十六进制字面量在数据编码中的应用
function dataEncoding() public pure returns (
bytes memory, bytes memory, bool
) {
// 手动编码一个 ERC20 transfer 调用
address to = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
uint256 value = 1000;
// 方法 1:使用十六进制字面量和连接
bytes4 selector = hex"a9059cbb";
bytes memory encoded1 = abi.encodePacked(
selector,
bytes32(uint256(uint160(to))),
bytes32(value)
);
// 方法 2:使用 abi.encodeWithSignature
bytes memory encoded2 = abi.encodeWithSignature(
"transfer(address,uint256)",
to,
value
);
// 比较结果(注意:abi.encodeWithSignature 会使用真正的 ABI 编码,可能与手动版本不同)
bool matchingPrefix = bytes4(encoded1) == bytes4(encoded2);
return (encoded1, encoded2, matchingPrefix);
}
// 十六进制字面量在 Gas 优化中的应用
function optimizedOperations() public pure returns (
bytes32, bytes32, bytes32
) {
// 使用十六进制字面量优化掩码操作
bytes32 data = hex"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
// 掩码操作:保留前 4 字节
bytes32 mask4bytes = hex"ffffffff00000000000000000000000000000000000000000000000000000000";
bytes32 firstFourBytes = data & mask4bytes;
// 掩码操作:保留最后 4 字节
bytes32 maskLast4bytes = hex"00000000000000000000000000000000000000000000000000000000ffffffff";
bytes32 lastFourBytes = data & maskLast4bytes;
return (data, firstFourBytes, lastFourBytes);
}
// 十六进制字面量常量
bytes32 public constant HEX_CONSTANT = hex"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
bytes4 public constant FUNCTION_SELECTOR = hex"a9059cbb";
}
关键要点
-
十六进制字面量语法:
- 使用
hex"..."
语法定义 - 每两个十六进制字符代表一个字节
- 使用
-
转换规则:
- 可以转换为
bytes
、bytesN
或通过中间步骤转为其他类型 - 固定大小字节数组转换可能涉及填充或截断
- 可以转换为
-
使用场景:
- 表示二进制数据、哈希值和签名
- 指定函数选择器和 ABI 调用数据
- 定义掩码和位操作常量
-
转换方向:
- 十六进制字面量 → 字节数组:直接赋值
- 十六进制字面量 → 整型:需要通过字节类型中转
- 十六进制字面量 → 地址:通过
address()
- 十六进制字面量 → 字符串:通过
string()
5. 地址字面量转换
地址是 Solidity 中的特殊类型,代表以太坊地址(20 字节/160 位)。地址字面量和各种类型之间的转换是智能合约开发中的常见操作。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract AddressLiteralConversions {
// 基本地址字面量
function basicAddressLiterals() public pure returns (
address, address payable
) {
// 普通地址字面量
address a = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
// 转换为可支付地址
address payable b = payable(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
return (a, b);
}
// 地址字面量与字节数组互转
function addressAndBytes() public pure returns (
bytes20, bytes32, address, address
) {
// 地址到字节
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
bytes20 b20 = bytes20(addr); // 直接转换为 bytes20
// 地址到 bytes32(需要先转为 uint256)
bytes32 b32 = bytes32(uint256(uint160(addr))); // 结果:右对齐,左侧填充零
// 字节到地址
address fromBytes20 = address(b20);
// bytes32 到地址(需要先转为 bytes20,仅使用低 20 字节)
bytes20 truncatedB20 = bytes20(b32); // 截断,保留低 20 字节
address fromBytes32 = address(truncatedB20);
return (b20, b32, fromBytes20, fromBytes32);
}
// 地址字面量与整型互转
function addressAndIntegers() public pure returns (
uint160, uint256, address, address, bool
) {
// 地址到整型
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
uint160 a = uint160(addr); // 直接转换为 uint160
uint256 b = uint256(uint160(addr)); // 转换为 uint256
// 整型到地址
uint160 c = 1234567890;
address fromUint160 = address(c);
uint256 d = 1234567890;
address fromUint256 = address(uint160(d)); // uint256 必须先转为 uint160
// 验证转换可逆性
bool reversible = (addr == address(uint160(uint256(uint160(addr)))));
return (a, b, fromUint160, fromUint256, reversible);
}
// 地址字面量与字符串互转
function addressAndStrings() public pure returns (
string memory, address
) {
// 地址到字符串(需要自定义函数)
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
string memory addrStr = addressToString(addr);
// 字符串到地址(需要解析十六进制)
string memory str = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4";
address parsedAddr = parseAddressString(str);
return (addrStr, parsedAddr);
}
// 辅助函数:地址转字符串
function addressToString(address addr) internal pure returns (string memory) {
bytes memory buffer = new bytes(42);
buffer[0] = "0";
buffer[1] = "x";
bytes memory HEX = "0123456789abcdef";
for (uint256 i = 0; i < 20; i++) {
buffer[2 + i * 2] = HEX[uint8(uint160(addr) / (2**(8 * (19 - i))) / 16)];
buffer[3 + i * 2] = HEX[uint8(uint160(addr) / (2**(8 * (19 - i))) % 16)];
}
return string(buffer);
}
// 辅助函数:解析地址字符串(简化版,仅作说明用途)
function parseAddressString(string memory addrStr) internal pure returns (address) {
bytes memory addrBytes = bytes(addrStr);
require(addrBytes.length == 42, "Invalid address length");
require(addrBytes[0] == "0" && (addrBytes[1] == "x" || addrBytes[1] == "X"), "Invalid address format");
uint160 value = 0;
for (uint256 i = 2; i < 42; i++) {
uint8 digit;
byte b = addrBytes[i];
if (b >= "0" && b <= "9") {
digit = uint8(b) - uint8("0");
} else if (b >= "a" && b <= "f") {
digit = uint8(b) - uint8("a") + 10;
} else if (b >= "A" && b <= "F") {
digit = uint8(b) - uint8("A") + 10;
} else {
revert("Invalid character in address");
}
value = value * 16 + digit;
}
return address(value);
}
// 地址类型转换:address 和 address payable
function addressTypeConversions() public pure returns (
address, address payable, bool
) {
// 普通地址
address a = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
// 转换为可支付地址
address payable b = payable(a);
// address payable 可以隐式转换为 address
address c = b;
// 检查转换是否保留原始地址
bool preserved = (a == c);
return (a, b, preserved);
}
// 常见地址常量
address public constant ZERO_ADDRESS = address(0);
address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
// 地址字面量验证
function validateAddressLiteral(address addr) public pure returns (
bool, bool, bool
) {
// 检查零地址
bool isZero = (addr == address(0));
// 检查是否为合约地址(注意:这不是纯静态检查,实际需要使用 extcodesize)
bool isSelf = (addr == address(this));
// 检查特定地址(例如知名合约或 EOA)
bool isSpecificAddress = (addr == 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
return (isZero, isSelf, isSpecificAddress);
}
// 地址字面量在转账中的应用
function sendFunds() public payable returns (bool) {
// 发送到特定地址
address payable recipient = payable(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
// 转账(在真实合约中应检查失败)
(bool success, ) = recipient.call{value: msg.value}("");
return success;
}
// 使用地址字面量设置权限
address public constant ADMIN = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
modifier onlyAdmin() {
require(msg.sender == ADMIN, "Not admin");
_;
}
// 使用修饰符的特权函数
function privilegedFunction() public view onlyAdmin returns (string memory) {
return "Admin access granted";
}
}
关键要点
-
地址字面量语法:
- 十六进制形式:
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
- 必须是 20 字节(40 个十六进制字符,不包括
0x
前缀)
- 十六进制形式:
-
地址类型:
address
:基本地址类型address payable
:可以接收以太币的地址类型
-
转换规则:
address
→address payable
:使用payable()
显式转换address payable
→address
:隐式转换address
⟷bytes20
:直接转换address
⟷uint160
:直接转换address
→uint256
:需要先转为uint160
uint256
→address
:需要先转为uint160
(确保值适合 160 位)
-
特殊地址:
- 零地址:
address(0)
或0x0000000000000000000000000000000000000000
- 燃烧地址:常用
0x000000000000000000000000000000000000dEaD
- 零地址:
6. 布尔字面量转换
布尔字面量(true
和 false
)虽然简单,但在不同类型间的转换和应用有一些细微差别需要理解。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract BooleanLiteralConversions {
// 基本布尔字面量
function basicBooleanLiterals() public pure returns (
bool, bool
) {
bool a = true;
bool b = false;
return (a, b);
}
// 布尔字面量与条件表达式
function booleanInConditions() public pure returns (
uint256, uint256, uint256
) {
uint256 a = 0;
uint256 b = 0;
// if 语句中的布尔字面量
if (true) {
a = 1;
}
if (false) {
b = 1; // 永远不会执行
}
// 三元操作符中的布尔字面量
uint256 c = true ? 42 : 24; // 结果为 42
return (a, b, c);
}
// 布尔字面量与逻辑运算
function booleanLogicOperations() public pure returns (
bool, bool, bool, bool, bool
) {
// 与布尔字面量进行逻辑运算
bool a = true && true; // true
bool b = true && false; // false
bool c = true || false; // true
bool d = false || false; // false
bool e = !true; // false
return (a, b, c, d, e);
}
// 布尔字面量与整型转换
function booleanAndIntegers() public pure returns (
uint256, uint8, uint256, bool, bool
) {
// 布尔到整型(没有内置直接转换)
uint256 a = true ? 1 : 0; // 使用条件表达式:结果为 1
uint8 b = false ? 1 : 0; // 使用条件表达式:结果为 0
// 通过位运算转换
uint256 c = uint256(true ? bytes32(uint256(1)) : bytes32(uint256(0)));
// 整型到布尔(没有内置直接转换)
bool d = (1 != 0); // 使用比较:结果为 true
bool e = (0 != 0); // 使用比较:结果为 false
return (a, b, c, d, e);
}
// 布尔字面量与字符串转换
function booleanAndStrings() public pure returns (
string memory, string memory
) {
// 布尔到字符串
string memory a = true ? "true" : "false";
string memory b = false ? "true" : "false";
// 更通用的转换方法
string memory c = boolToString(true);
string memory d = boolToString(false);
return (c, d);
}
// 辅助函数:布尔转字符串
function boolToString(bool value) internal pure returns (string memory) {
return value ? "true" : "false";
}
// 布尔字面量与字节转换
function booleanAndBytes() public pure returns (
bytes1, bytes1, bool, bool
) {
// 布尔到字节
bytes1 a = true ? bytes1(0x01) : bytes1(0x00);
bytes1 b = false ? bytes1(0x01) : bytes1(0x00);
// 字节到布尔
bool c = bytes1(0x01) == bytes1(0x00) ? false : true;
bool d = bytes1(0x00) == bytes1(0x00) ? false : true;
return (a, b, c, d);
}
// 布尔字面量在位运算中的应用
function booleanInBitOperations() public pure returns (
uint256, uint256, uint256
) {
uint256 a = 0;
// 使用布尔字面量设置位
a |= true ? 1 : 0; // 设置第一位
a |= true ? (1 << 1) : 0; // 设置第二位
a |= false ? (1 << 2) : 0; // 不设置第三位
// 使用布尔字面量清除位
uint256 b = 7; // 二进制: 111
b &= true ? ~(1 << 1) : ~0; // 清除第二位,结果: 101 (5)
// 使用布尔字面量切换位
uint256 c = 5; // 二进制: 101
c ^= true ? (1 << 1) : 0; // 切换第二位,结果: 111 (7)
return (a, b, c);
}
// 布尔字面量在映射中的应用
mapping(address => bool) public whitelist;
function setWhitelistStatus(address user, bool status) public {
// 直接使用布尔字面量
if (status == true) {
whitelist[user] = true;
} else {
whitelist[user] = false;
}
// 更简洁的写法
// whitelist[user] = status;
}
// 布尔字面量常量
bool public constant DEFAULT_STATUS = true;
// 布尔字面量与函数结果
function booleanFromFunctions() public pure returns (
bool, bool, bool
) {
// 直接返回布尔字面量
bool a = returnTrue();
bool b = returnFalse();
// 函数结果与布尔字面量比较
bool c = returnTrue() == true; // true
return (a, b, c);
}
// 辅助函数:返回布尔值
function returnTrue() internal pure returns (bool) {
return true;
}
function returnFalse() internal pure returns (bool) {
return false;
}
// 使用布尔字面量防止重入
bool private locked;
modifier noReentrant() {
require(locked == false, "Reentrant call");
locked = true;
_;
locked = false;
}
function secureFunction() public noReentrant returns (bool) {
// 安全操作...
return true;
}
}
关键要点
-
布尔字面量:
- 只有两个值:
true
和false
- 默认值为
false
- 只有两个值:
-
隐式转换:
- Solidity 不支持布尔值与整型之间的隐式转换
- 需要使用比较运算符或条件表达式进行显式转换
-
条件上下文:
- 布尔字面量常用于
if
、while
、require
等条件语句 - 布尔表达式可以使用
&&
、||
、!
组合
- 布尔字面量常用于
-
位运算应用:
- 可以使用布尔值控制位操作
- 常用于标志位和访问控制
-
转换技巧:
- 布尔 → 整数:
boolValue ? 1 : 0
- 整数 → 布尔:
intValue != 0
- 布尔 → 字符串:
boolValue ? "true" : "false"
- 布尔 → 整数:
7. 有理数和科学计数法字面量
Solidity 支持有理数和科学计数法字面量,但由于缺乏内置浮点类型,这些字面量的处理有一些特殊规则。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract RationalAndScientificLiterals {
// 基本有理数字面量
function basicRationalLiterals() public pure returns (
int256, int256, int256
) {
// 有理数字面量用于计算,结果必须是整数
int256 a = 5 / 2; // 结果:2(整数除法向下取整)
int256 b = int256(5) / 2; // 结果:2(显式类型不改变结果)
int256 c = 5 / int256(2); // 结果:2(显式类型不改变结果)
return (a, b, c);
}
// 有理数字面量常量表达式
function rationalConstantExpressions() public pure returns (
int256, int256, int256
) {
// 有理数字面量在编译期求值,保持完全精度
int256 a = (1 / 3) * 3; // 结果:1(完全精度计算 1/3 = 0.33...,然后乘以 3)
int256 b = (2 / 3) * 3; // 结果:2(完全精度计算 2/3 = 0.66...,然后乘以 3)
int256 c = (1 / 2) * 3; // 结果:1(完全精度计算 1/2 = 0.5,然后乘以 3)
return (a, b, c);
}
// 科学计数法字面量
function scientificNotationLiterals() public pure returns (
uint256, uint256, uint256
) {
// 基本科学计数法
uint256 a = 1e3; // 1000 (= 1 * 10^3)
uint256 b = 2e18; // 2 * 10^18(常用于表示以太币数量)
uint256 c = 1.5e3; // 1500 (= 1.5 * 10^3)
return (a, b, c);
}
// 科学计数法与单位组合
function scientificWithUnits() public pure returns (
uint256, uint256, uint256
) {
// 科学计数法与货币单位
uint256 a = 1e18 wei; // 10^18 wei (= 1 ether)
uint256 b = 2e9 wei; // 2 * 10^9 wei (= 2 gwei)
uint256 c = 0.5e18 wei; // 0.5 * 10^18 wei (= 0.5 ether)
uint256 d = 1 ether; // 10^18 wei
uint256 e = 1 ether / 2; // 5 * 10^17 wei (0.5 ether)
uint256 f = (2 ether + 0.5 ether); // 2.5 * 10^18 wei
return (a, b, c);
}
// 精度处理与溢出
function precisionAndOverflow() public pure returns (
uint256, uint256, uint256
) {
// 高精度计算
uint256 a = (1e18 * 5) / 10; // 5 * 10^17,精度保持
// 避免中间溢出
uint256 b = 1e18 * (5 / 10); // 0,因为 5/10=0(整数除法)
uint256 c = (5 * 1e18) / 10; // 5 * 10^17,正确的计算
return (a, b, c);
}
// 固定点模拟
function fixedPointSimulation() public pure returns (
uint256, uint256, uint256
) {
// 模拟固定点数:使用 10^18 作为基础单位
uint256 precision = 1e18;
// 表示 0.5
uint256 a = 5 * 1e17; // 5 * 10^17 = 0.5 * 10^18
// 固定点乘法
uint256 b = 5 * 1e17; // 0.5 * 10^18
uint256 c = 3 * 1e17; // 0.3 * 10^18
uint256 product = (b * c) / precision; // (0.5 * 0.3) * 10^18 = 0.15 * 10^18
// 固定点除法
uint256 d = 5 * 1e18; // 5 * 10^18
uint256 e = 2 * 1e18; // 2 * 10^18
uint256 quotient = (d * precision) / e; // (5/2) * 10^18 = 2.5 * 10^18
return (a, product, quotient);
}
// 常见单位转换
function commonUnitConversions() public pure returns (
uint256, uint256, uint256, uint256
) {
// 以太币单位
uint256 oneEther = 1 ether; // 10^18 wei
uint256 oneGwei = 1 gwei; // 10^9 wei
uint256 tenFinney = 10 finney; // 10 * 10^15 wei = 10^16 wei
// 时间单位
uint256 oneDay = 1 days; // 86400 seconds
uint256 oneHour = 1 hours; // 3600 seconds
uint256 tenMinutes = 10 minutes; // 10 * 60 = 600 seconds
uint256 oneWeek = 1 weeks; // 7 * 86400 = 604800 seconds
return (oneEther, oneGwei, oneDay, oneHour);
}
// 分数字面量处理
function fractionLiterals() public pure returns (
uint256, uint256, uint256
) {
// 分数字面量必须用于计算,不能直接分配给变量
// uint256 a = 0.5; // 错误:不能将非整数字面量直接分配给整数
uint256 a = (1 / 2) * 10; // 结果:5(保持分数精度直到最终结果)
uint256 b = (1 / 4) * 100; // 结果:25
uint256 c = (3 / 4) * 100; // 结果:75
return (a, b, c);
}
// 混合算术与优先级
function mixedArithmetic() public pure returns (
uint256, uint256, uint256
) {
// 混合分数和科学计数法
uint256 a = (1 / 2) * 1e18; // 5 * 10^17
uint256 b = 1.5e18 / 3; // 5 * 10^17
uint256 c = (2 / 3) * 1.5e18; // 1 * 10^18
return (a, b, c);
}
// 零散常量和超大/超小数字
function miscConstants() public pure returns (
uint256, uint256, uint256
) {
// 超大数字表示
uint256 a = 1e24; // 10^24 (一亿亿亿)
// 小分数表示
uint256 b = (1 / 1000) * 1e21; // 10^18 (表示 0.001 * 10^21)
// 精确常量
uint256 c = 1_000_000 * 1_000_000; // 10^12 (一万亿)
return (a, b, c);
}
// 固定常量:永远不要这样计算小数
uint256 public constant HALF_PERCENT = 5e15; // 0.5% 表示为 5 * 10^15 wei
uint256 public constant FULL_PRECISION = 1e18; // 用作基础精度单位
}
关键要点
-
有理数字面量特性:
- 分数形式:
1/3
,5/2
等 - 小数形式:
0.5
,1.25
等 - 只能用于计算,不能直接赋值给变量
- 保持完全精度直到表达式最终求值
- 分数形式:
-
科学计数法表示:
- 基本形式:
1e18
(表示 10¹⁸) - 带小数点:
1.5e3
(表示 1.5×10³) - 常用于表示很大或很小的数字
- 基本形式:
-
单位字面量:
- 货币单位:
wei
,gwei
,ether
等 - 时间单位:
seconds
,minutes
,hours
,days
,weeks
- 是数值乘数,本质上是预定义常量
- 货币单位:
-
转换规则:
- 有理数最终必须转换为整数
- 计算保持分数精度,直到最终结果
- 整数除法会截断小数部分
-
模拟固定点:
- 通常使用 10¹⁸ 作为精度因子
- 乘法:
(a * b) / precision
- 除法:
(a * precision) / b
8. 特殊字面量常量
Solidity 提供了一些特殊的字面量和常量,用于访问区块链环境信息和执行特殊操作。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract SpecialLiteralsConstants {
// 块和交易特殊变量
function blockAndTxProperties() public view returns (
uint256, uint256, address, uint256, bytes memory
) {
// 特殊变量(不是严格意义上的字面量,但类似全局常量)
uint256 a = block.number; // 当前块号
uint256 b = block.timestamp; // 当前块时间戳(秒)
address c = block.coinbase; // 当前块的矿工/验证者地址
uint256 d = gasleft(); // 剩余 gas
bytes memory e = msg.data; // 调用数据
return (a, b, c, d, e);
}
// 全局常量
function globalConstants() public pure returns (
bytes32, bytes32, address, bytes4
) {
// 零值
bytes32 zeroBytes32 = bytes32(0);
// 最大值
bytes32 maxBytes32 = bytes32(type(uint256).max);
// 零地址
address zeroAddress = address(0);
// 函数选择器常量
bytes4 selector = this.globalConstants.selector;
return (zeroBytes32, maxBytes32, zeroAddress, selector);
}
// 内置函数选择器
function internalSelectors() public pure returns (
bytes4, bytes4, bytes4, bytes4
) {
// 常见 ERC20 函数选择器
bytes4 transferSelector = bytes4(keccak256("transfer(address,uint256)"));
bytes4 approveSelector = bytes4(keccak256("approve(address,uint256)"));
bytes4 transferFromSelector = bytes4(keccak256("transferFrom(address,address,uint256)"));
bytes4 balanceOfSelector = bytes4(keccak256("balanceOf(address)"));
return (transferSelector, approveSelector, transferFromSelector, balanceOfSelector);
}
// 类型系统常量
function typeSystemConstants() public pure returns (
uint256, int256, uint8, int8
) {
// 类型最大值常量
uint256 maxUint256 = type(uint256).max; // 2^256 - 1
int256 maxInt256 = type(int256).max; // 2^255 - 1
// 类型最小值常量
uint8 minUint8 = type(uint8).min; // 0
int8 minInt8 = type(int8).min; // -128
return (maxUint256, maxInt256, minUint8, minInt8);
}
// 特殊地址常量
function specialAddresses() public view returns (
address, address, address
) {
// 当前合约地址
address self = address(this);
// 零地址
address zero = address(0);
// 调用者地址
address sender = msg.sender;
return (self, zero, sender);
}
// 类型信息和接口 ID
function typeInfoConstants() public pure returns (
string memory, bytes4
) {
// 合约名称
string memory name = type(SpecialLiteralsConstants).name;
// 接口 ID(例如,计算 ERC165 接口 ID)
bytes4 erc165InterfaceId = type(IERC165).interfaceId;
return (name, erc165InterfaceId);
}
// 特殊字节码常量
function bytecodeConstants() public pure returns (
bytes memory, bytes memory
) {
// 创建字节码
bytes memory creationCode = type(SpecialLiteralsConstants).creationCode;
// 运行时字节码
bytes memory runtimeCode = type(SpecialLiteralsConstants).runtimeCode;
return (creationCode, runtimeCode);
}
// 预定义单位常量
function unitConstants() public pure returns (
uint256, uint256, uint256, uint256
) {
// 以太币单位
uint256 oneWei = 1 wei; // 1
uint256 oneGwei = 1 gwei; // 10^9
uint256 oneEther = 1 ether; // 10^18
// 时间单位
uint256 oneSecond = 1 seconds; // 1
uint256 oneMinute = 1 minutes; // 60
uint256 oneHour = 1 hours; // 3600
uint256 oneDay = 1 days; // 86400
uint256 oneWeek = 1 weeks; // 604800
return (oneWei, oneGwei, oneHour, oneDay);
}
// abi 编码常量
function abiEncodingConstants() public pure returns (
bytes memory, bytes memory, bytes4
) {
// 函数签名
string memory signature = "transfer(address,uint256)";
// 函数选择器
bytes4 selector = bytes4(keccak256(bytes(signature)));
// 空数据
bytes memory empty = "";
// 特殊 ABI 编码
bytes memory encoded = abi.encode(123, "abc", address(0));
return (bytes(signature), encoded, selector);
}
// 魔法常量
function magicConstants() public view returns (
string memory, uint256, bytes32
) {
// 源文件路径(编译器提供)
string memory sourceFile = "Example.sol"; // 仅示例,实际为完整路径
// 源代码行号(编译器提供)
uint256 lineNumber = 100; // 仅示例,实际为当前行号
// 自定义错误选择器
bytes4 errorSelector = InvalidAmount.selector;
return (sourceFile, lineNumber, bytes32(errorSelector));
}
// 示例自定义错误
error InvalidAmount(uint256 amount);
}
// 示例接口
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
关键要点
-
区块和交易信息:
block.number
:当前区块号block.timestamp
:当前区块时间戳(秒)msg.sender
:当前调用者地址msg.value
:当前交易附带的以太币数量
-
类型界限常量:
type(T).min
:类型 T 的最小值type(T).max
:类型 T 的最大值- 例如:
type(uint256).max
,type(int8).min
-
特殊地址:
address(this)
:当前合约地址address(0)
:零地址(常用于表示无效地址)
-
单位常量:
- 以太币单位:
wei
,gwei
,ether
- 时间单位:
seconds
,minutes
,hours
,days
,weeks
- 以太币单位:
-
类型信息:
type(C).name
:合约名称type(I).interfaceId
:接口 IDtype(C).creationCode
:合约创建字节码type(C).runtimeCode
:合约运行时字节码
-
选择器和签名:
bytes4(keccak256("function(param1,param2)"))
:函数选择器function.selector
:函数选择器属性error.selector
:错误选择器属性
9. 复合转换和类型推断
在实际开发中,我们经常需要在多种类型之间执行复杂的转换,并依赖类型推断来简化代码。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract CompoundConversionsAndInference {
// 复杂类型转换链
function complexConversionChains() public pure returns (
uint256, address, bytes32, string memory
) {
// 从字符串到 keccak256 哈希再到 uint256
string memory text = "Hello, Solidity!";
bytes32 hash = keccak256(bytes(text));
uint256 hashAsUint = uint256(hash);
// 从 uint256 到地址再到字符串
uint256 rawValue = 1234567890;
address addr = address(uint160(rawValue));
string memory addrStr = addressToString(addr);
return (hashAsUint, addr, hash, addrStr);
}
// 辅助函数:地址转字符串
function addressToString(address addr) internal pure returns (string memory) {
bytes memory buffer = new bytes(42);
buffer[0] = "0";
buffer[1] = "x";
bytes memory HEX = "0123456789abcdef";
for (uint256 i = 0; i < 20; i++) {
buffer[2 + i * 2] = HEX[uint8(uint160(addr) / (2**(8 * (19 - i))) / 16)];
buffer[3 + i * 2] = HEX[uint8(uint160(addr) / (2**(8 * (19 - i))) % 16)];
}
return string(buffer);
}
// 类型推断示例
function typeInferenceExamples() public pure returns (
uint256, int256, bool
) {
// 自动类型推断
var1 = 100; // 推断为 uint8(最小能容纳的类型)
var2 = -100; // 推断为 int8
var3 = 0x123456; // 推断为 uint24
var4 = true; // 推断为 bool
var5 = "Hello"; // 推断为 string
var6 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; // 推断为 address
// 上下文中的类型推断
uint256 a = var1 + 1000; // var1 自动适应为 uint256
int256 b = int256(var2) * 1000;
bool c = var4 && true;
return (a, b, c);
}
// 变量声明,用于演示类型推断
uint8 var1;
int8 var2;
uint24 var3;
bool var4;
string var5;
address var6;
// 混合类型表达式
function mixedTypeExpressions() public pure returns (
uint256, uint256, uint256
) {
// 不同类型整数混合
uint8 a = 10;
uint16 b = 100;
uint256 c = a * b; // 结果提升为更大的类型
// 字面量与变量混合
uint8 d = 5;
uint256 e = d * 100; // 100 被视为 uint256,结果为 uint256
// 高精度计算
uint256 f = (10**18 / 3) * 3; // 保持完全精度
return (c, e, f);
}
// 条件类型转换
function conditionalTypeConversions(uint256 value) public pure returns (
string memory, uint8, uint16
) {
// 基于条件的不同转换
string memory category;
if (value < 10) {
category = "small";
} else if (value < 100) {
category = "medium";
} else {
category = "large";
}
// 安全类型收缩
uint8 a;
uint16 b;
if (value <= type(uint8).max) {
a = uint8(value);
} else {
a = type(uint8).max;
}
if (value <= type(uint16).max) {
b = uint16(value);
} else {
b = type(uint16).max;
}
return (category, a, b);
}
// ABI 编码与解码
function abiEncodingAndDecoding() public pure returns (
bytes memory, address, uint256, string memory
) {
// 编码多种类型
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
uint256 value = 1000;
string memory name = "Test";
bytes memory encoded = abi.encode(addr, value, name);
// 解码回原始类型
(address decodedAddr, uint256 decodedValue, string memory decodedName) =
abi.decode(encoded, (address, uint256, string));
return (encoded, decodedAddr, decodedValue, decodedName);
}
// 字节操作与转换
function byteOperationsAndConversions() public pure returns (
bytes memory, bytes32, bytes1, uint8
) {
// 创建和修改字节数组
bytes memory data = new bytes(5);
data[0] = 0x01;
data[1] = 0x02;
data[2] = 0x03;
data[3] = 0x04;
data[4] = 0x05;
// 提取 32 字节(填充零)
bytes32 data32;
assembly {
data32 := mload(add(data, 32))
}
// 提取单个字节
bytes1 firstByte = data[0];
// 字节到整型
uint8 firstByteAsUint = uint8(firstByte);
return (data, data32, firstByte, firstByteAsUint);
}
// 结构体中的类型转换
struct Data {
uint256 id;
address owner;
bytes32 hash;
string name;
}
function structTypeConversions() public pure returns (Data memory) {
// 创建不同来源的值
uint256 id = 12345;
string memory text = "Sample";
uint160 ownerRaw = 9876543210;
// 转换并填充结构体
Data memory data;
data.id = id;
data.owner = address(ownerRaw);
data.hash = keccak256(bytes(text));
data.name = text;
return data;
}
// 使用映射的动态类型转换
mapping(bytes32 => address) private addressRegistry;
mapping(bytes32 => uint256) private valueRegistry;
mapping(bytes32 => string) private nameRegistry;
// 注册各种类型的值
function registerValue(string memory key, address addr, uint256 value, string memory name) public {
bytes32 keyHash = keccak256(bytes(key));
addressRegistry[keyHash] = addr;
valueRegistry[keyHash] = value;
nameRegistry[keyHash] = name;
}
// 获取注册表值
function getRegistryValues(string memory key) public view returns (
address, uint256, string memory
) {
bytes32 keyHash = keccak256(bytes(key));
return (
addressRegistry[keyHash],
valueRegistry[keyHash],
nameRegistry[keyHash]
);
}
}
关键要点
-
转换链:
- 复杂转换通常需要多个步骤
- 中间类型选择很重要,需要避免数据丢失
- 常见路径:字符串→字节→哈希→整数→地址
-
类型推断:
- 字面量类型推断依赖上下文
- 整型字面量默认推断为能容纳其值的最小类型
- 表达式结果类型由其操作数的类型决定
-
混合类型表达式:
- 不同大小整型混合会提升到足够大的类型
- 有符号和无符号整型混合时要特别小心
- 字面量在计算中保持完全精度
-
条件转换:
- 基于条件执行不同的转换路径
- 安全转换应检查类型界限
- 可以使用条件转换处理边界情况
-
ABI 编码/解码:
abi.encode
/abi.decode
支持各种类型之间的转换- 编码生成字节序列,解码恢复原始类型
- 可用于复杂数据结构的序列化和反序列化
-
高级字节操作:
- 字节数组适合处理任意二进制数据
- 需要谨慎处理填充和截断
- 有时需要使用
assembly
进行高效操作
10. 类型转换安全考量
在进行字面量和类型转换时,安全性是首要考虑因素。本节讨论常见的安全问题及其解决方案。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract TypeConversionSecurity {
// 整型溢出问题
function integerOverflowIssues() public pure returns (
uint8, uint8, uint8, uint8
) {
// 直接赋值检查(编译时捕获,Solidity 0.8.0+ 会检查字面量适合性)
uint8 a = 200; // 适合 uint8
// uint8 b = 500; // 编译错误:字面量 500 不适合 uint8
// 显式转换导致的潜在溢出
uint256 c = 300;
uint8 d = uint8(c); // d = 300 % 256 = 44,发生截断
// 算术运算中的潜在溢出
uint8 e = 200;
// uint8 f = e + 100; // Solidity 0.8.0+ 运行时会检查并回滚
// 使用 unchecked 可以绕过运行时检查
uint8 g;
unchecked {
g = 200 + 100; // g = (200 + 100) % 256 = 44
}
// 算术运算比较
uint8 h = 255;
uint8 i = 1;
uint8 j;
unchecked {
j = h + i; // 0,发生溢出
}
return (a, d, g, j);
}
// 安全转换辅助函数
function safeUint8Conversion(uint256 value) public pure returns (uint8, bool) {
if (value <= type(uint8).max) {
return (uint8(value), true);
} else {
return (0, false);
}
}
// 有符号与无符号转换问题
function signedUnsignedIssues() public pure returns (
uint8, uint8, int8, int8
) {
// 正值转换,可能溢出
int16 a = 200;
// int8 b = int8(a); // 溢出:int8 范围 -128 到 127
// 负值转换,从有符号转无符号
int8 c = -10;
uint8 d = uint8(c); // d = 256 - 10 = 246,注意负数如何变为大正数
// 负值转换,从有符号转有符号,可能溢出
int16 e = -200;
int8 f = int8(e); // 如果超出范围,会溢出
// 安全转换
int8 safeNegative = -10;
uint8 g = safeNegative < 0 ? 0 : uint8(safeNegative);
// 边界值测试
int8 h = 127; // int8 最大值
int8 i; // 将被设为 int8 最小值
unchecked {
i = h + 1; // 溢出变为 -128 (int8 最小值)
}
return (d, g, f, i);
}
// 地址转换安全问题
function addressConversionIssues() public pure returns (
address, address, bool
) {
// 零地址检查
uint160 zeroValue = 0;
address zeroAddr = address(zeroValue);
// 通常应该检查零地址
function(address) pure returns (bool) isValidAddress =
(address addr) pure returns (bool) { return addr != address(0); };
// 大整数到地址转换
uint256 largeValue = type(uint256).max;
// 需要确保值不超过 uint160 范围
require(largeValue <= type(uint160).max, "Value too large for address");
address fromLarge = address(uint160(largeValue));
// 检查地址是否有效
bool isValid = isValidAddress(fromLarge);
return (zeroAddr, fromLarge, isValid);
}
// 精度损失和四舍五入问题
function precisionLossIssues() public pure returns (
uint256, uint256, uint256
) {
// 整数除法的精度损失
uint256 a = 5;
uint256 b = 2;
uint256 c = a / b; // 结果:2(丢失小数部分)
// 保持精度的方法:先乘后除
uint256 d = 5;
uint256 e = 2;
uint256 precision = 1000;
uint256 f = (d * precision) / e; // 结果:2500(表示 2.5)
// 四舍五入(向上)
uint256 g = 5;
uint256 h = 2;
uint256 i = (g + h - 1) / h; // 结果:3(向上取整)
return (c, f, i);
}
// 字符串与数字转换问题
function stringNumericIssues() public pure returns (
uint256, bool, uint256, bool
) {
// 尝试转换有效数字字符串
string memory validNumber = "12345";
(uint256 parsed1, bool success1) = stringToUint(validNumber);
// 尝试转换无效数字字符串
string memory invalidNumber = "123abc";
(uint256 parsed2, bool success2) = stringToUint(invalidNumber);
return (parsed1, success1, parsed2, success2);
}
// 辅助函数:字符串转换为整数
function stringToUint(string memory s) internal pure returns (uint256, bool) {
bytes memory b = bytes(s);
uint256 result = 0;
bool success = true;
for (uint256 i = 0; i < b.length; i++) {
uint8 c = uint8(b[i]);
// 检查是否为数字字符
if (c >= 48 && c <= 57) {
result = result * 10 + (c - 48);
} else {
success = false;
break;
}
}
return (result, success);
}
// 哈希碰撞和类型混淆
function hashCollisionIssues() public pure returns (
bytes32, bytes32, bool
) {
// 不同类型可能产生相同哈希
string memory str1 = "Sample";
bytes memory bytes1 = bytes("Sample");
// 通常对比哈希时需要确保类型相同
bytes32 hash1 = keccak256(bytes(str1));
bytes32 hash2 = keccak256(bytes1);
// 罕见情况下不同数据可能产生相同哈希(几乎不可能,但理论上存在)
bool sameHash = (hash1 == hash2);
return (hash1, hash2, sameHash);
}
// 字节截断和填充问题
function byteTruncationPaddingIssues() public pure returns (
bytes32, bytes16, bytes32
) {
// 原始数据
bytes32 original = bytes32(uint256(12345));
// 截断:较大类型转为较小类型
bytes16 truncated = bytes16(original); // 保留右对齐的前 16 字节
// 填充:较小类型转为较大类型
bytes32 padded = bytes32(truncated); // 左侧填充零
// 注意:original != padded,因为填充后的结果不等于原始值
return (original, truncated, padded);
}
// 非标准长度地址和数据
function nonStandardLengthIssues() public pure returns (
address, bytes memory, bool
) {
// 地址必须正好是 20 字节
bytes memory tooShortAddressBytes = hex"1234"; // 只有 2 字节
// 安全的方式是检查长度并适当填充
bytes20 safeAddressBytes;
bool validLength = false;
if (tooShortAddressBytes.length <= 20) {
// 复制可用字节
for (uint256 i = 0; i < tooShortAddressBytes.length; i++) {
safeAddressBytes[i + (20 - tooShortAddressBytes.length)] = tooShortAddressBytes[i];
}
validLength = true;
}
// 转换为地址
address safeAddress = address(safeAddressBytes);
return (safeAddress, tooShortAddressBytes, validLength);
}
// 使用接收函数接收以太币
receive() external payable {}
// 重入攻击问题
bool private locked;
modifier noReentrant() {
require(!locked, "No reentrant call");
locked = true;
_;
locked = false;
}
function unsafeTransfer(address payable recipient, uint256 amount) public {
// 不安全:在状态更新前转账
(bool success, ) = recipient.call{value: amount}("");
require(success, "Transfer failed");
// 状态更新太晚,易受重入攻击
}
// 安全转账
function safeTransfer(address payable recipient, uint256 amount) public noReentrant {
// 安全:先修改状态,后转账
// 并使用重入锁
(bool success, ) = recipient.call{value: amount}("");
require(success, "Transfer failed");
}
}
关键安全考量
-
整型溢出/下溢:
- Solidity 0.8.0+ 自动检查算术溢出/下溢
- 显式类型转换时不会检查边界,需手动验证
- 使用
unchecked
块时需谨慎
-
符号转换问题:
- 负整数转为无符号整数会变为大正数
- 转换前检查值是否在目标类型范围内
- 小心处理边界情况(最大值/最小值)
-
地址验证:
- 检查地址非零值 (
address(0)
) - 确保整数值在
uint160
范围内 - 验证地址是否为合约(当需要时)
- 检查地址非零值 (
-
精度问题:
- 整数除法会截断小数部分
- 使用乘法因子保留小数精度
- 实现适当的舍入机制(向上、向下或四舍五入)
-
字符串解析:
- 验证字符串内容符合预期格式
- 处理非法字符和格式错误
- 为无效输入提供明确的错误处理
-
字节处理:
- 小心处理字节数组的截断和填充
- 检查长度兼容性
- 确保正确对齐数据(左对齐/右对齐)
-
重入攻击:
- 外部调用可能导致重入
- 使用检查-效果-交互模式
- 实现重入保护机制
-
其他考量:
- 避免硬编码值,尤其是地址
- 使用安全的转换辅助函数
- 全面测试边界情况和异常输入
11. Gas 优化技巧
优化智能合约中的类型转换可以显著节省 gas 和提高执行效率。本节介绍一些实用的优化技巧。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract GasOptimizationTechniques {
// 使用正确大小的整型以优化存储
struct OptimizedStruct {
uint8 smallValue; // 0-255 范围的值
uint16 mediumValue; // 0-65535 范围的值
bool flag1; // 布尔值只需要 1 位
bool flag2; // 单个存储槽可以包含多个小字段
}
struct UnoptimizedStruct {
uint256 smallValue; // 浪费空间
uint256 mediumValue; // 浪费空间
bool flag1; // 占用整个存储槽
bool flag2; // 占用另一个存储槽
}
// 使用位操作替代多个布尔值
uint8 private flags;
// 标志位常量
uint8 private constant FLAG_ACTIVE = 1; // 00000001
uint8 private constant FLAG_PAUSED = 2; // 00000010
uint8 private constant FLAG_RESTRICTED = 4; // 00000100
uint8 private constant FLAG_SPECIAL = 8; // 00001000
// 设置标志(更高效)
function setFlag(uint8 flag, bool value) public {
if (value) {
flags |= flag; // 设置位
} else {
flags &= ~flag; // 清除位
}
}
// 检查标志(更高效)
function checkFlag(uint8 flag) public view returns (bool) {
return (flags & flag) != 0;
}
// 使用更高效的转换
function optimizedConversions() public pure returns (
uint256, uint256, uint256, address
) {
// 使用移位代替乘除
uint256 a = 5;
uint256 b = a << 3; // 5 * 8 = 40,更高效
uint256 c = a >> 1; // 5 / 2 = 2,更高效
// 直接字面量使用更高效
uint256 d = 10**18; // 字面量,编译时计算
// 适当使用类型转换
uint160 rawAddr = 123456789;
address addr = address(rawAddr); // 直接转换更高效
return (b, c, d, addr);
}
// 避免冗余转换
function redundantConversions() public pure returns (
uint256, uint256, address
) {
// 低效:多次转换
uint256 a = 123;
uint8 b = uint8(a);
uint256 c = uint256(b); // 冗余转换
// 高效:避免不必要的转换
uint256 d = a < 256 ? a : 255; // 直接使用原始值并限制范围
// 低效:地址转换链
uint160 addrVal = 123456;
address addr1 = address(addrVal);
uint160 addrValAgain = uint160(addr1); // 冗余转换
address addr2 = address(addrValAgain); // 冗余转换
// 高效:最小化转换
address addr3 = address(addrVal); // 一次转换
return (c, d, addr2);
}
// 避免昂贵的字符串操作
function stringOperations() public pure returns (
bytes32, bytes32, bytes32
) {
// 低效:字符串连接和转换
string memory a = "Hello, ";
string memory b = "World!";
string memory combined = string(abi.encodePacked(a, b));
bytes32 hash1 = keccak256(bytes(combined));
// 高效:直接使用字节
bytes32 hash2 = keccak256(abi.encodePacked("Hello, ", "World!"));
// 高效:避免中间字符串
string memory name = "Token";
uint256 value = 1000;
bytes32 hash3 = keccak256(abi.encodePacked(name, value));
return (hash1, hash2, hash3);
}
// 使用 unchecked 优化计数器
function countLoop(uint256[] memory data) public pure returns (uint256) {
uint256 sum = 0;
// 低效循环
// for (uint256 i = 0; i < data.length; i++) {
// sum += data[i];
// }
// 高效循环:使用 unchecked 和缓存长度
uint256 length = data.length;
for (uint256 i = 0; i < length;) {
sum += data[i];
unchecked { i++; } // 计数器不会溢出,可安全使用 unchecked
}
return sum;
}
// 高效编码整数
function encodeIntegers(uint256 a, uint256 b) public pure returns (
bytes memory, bytes memory
) {
// 低效:使用 abi.encode
bytes memory encoded1 = abi.encode(a, b);
// 高效:使用 abi.encodePacked
bytes memory encoded2 = abi.encodePacked(a, b);
return (encoded1, encoded2);
}
// 优化存储读写
uint256 public storedValue;
// 低效:总是写入存储
function updateValueInefficient(uint256 newValue) public {
storedValue = newValue; // 无论值是否变化都写入
}
// 高效:仅在值变化时写入
function updateValueEfficient(uint256 newValue) public {
if (storedValue != newValue) {
storedValue = newValue;
}
}
// 优化动态数组操作
uint256[] public values;
// 低效:单独推送
function addValuesInefficient(uint256[] memory newValues) public {
for (uint256 i = 0; i < newValues.length; i++) {
values.push(newValues[i]); // 每次循环都修改存储
}
}
// 高效:批量操作
function addValuesEfficient(uint256[] memory newValues) public {
uint256 originalLength = values.length;
// 预先调整数组大小
// 注意:这种方法在某些情况下可能更省 gas,但取决于实际情况
for (uint256 i = 0; i < newValues.length; i++) {
values.push(0); // 先扩展数组
}
// 然后设置值(避免多次调整长度)
for (uint256 i = 0; i < newValues.length; i++) {
values[originalLength + i] = newValues[i];
}
}
// 打包多个小整数到单个 uint256
function packValues(uint8 a, uint8 b, uint8 c, uint8 d) public pure returns (uint256) {
// 将 4 个 uint8 打包为 1 个 uint256
return (uint256(a) << 24) | (uint256(b) << 16) | (uint256(c) << 8) | uint256(d);
}
function unpackValues(uint256 packed) public pure returns (uint8, uint8, uint8, uint8) {
// 从单个 uint256 解包 4 个 uint8
uint8 a = uint8(packed >> 24);
uint8 b = uint8(packed >> 16);
uint8 c = uint8(packed >> 8);
uint8 d = uint8(packed);
return (a, b, c, d);
}
// 使用短路评估优化 gas
function shortCircuitLogic(uint256 a, uint256 b) public pure returns (bool) {
// 将计算成本较低的条件放在前面
// 如果第一个条件为 false,第二个条件不会被评估
return (a < 100) && expensiveCheck(b);
}
// 假设这是一个 gas 消耗大的检查
function expensiveCheck(uint256 value) internal pure returns (bool) {
bytes32 hash = bytes32(0);
// 模拟复杂计算
for (uint256 i = 0; i < 10; i++) {
hash = keccak256(abi.encodePacked(hash, value, i));
}
return uint256(hash) % 2 == 0;
}
// 合约类型信息的低 gas 替代
// 低效:每次调用都计算类型界限
function getTypeLimitsInefficient() public pure returns (uint256, uint256) {
return (type(uint128).min, type(uint128).max);
}
// 高效:使用常量
uint256 public constant UINT128_MIN = 0;
uint256 public constant UINT128_MAX = 2**128 - 1;
function getTypeLimitsEfficient() public pure returns (uint256, uint256) {
return (UINT128_MIN, UINT128_MAX);
}
}
关键优化技巧
-
正确大小的类型:
- 对小值使用较小的整型(
uint8
,uint16
等) - 结构体中的字段排序应考虑打包优化
- 对小值使用较小的整型(
-
位运算优化:
- 使用位操作存储多个布尔标志
- 使用左移(
<<
)代替乘以 2 的幂 - 使用右移(
>>
)代替除以 2 的幂
-
避免冗余转换:
- 减少类型转换链
- 缓存转换结果而不是重复转换
-
字符串优化:
- 避免不必要的字符串操作
- 尽可能使用字节和直接编码
- 使用
abi.encodePacked
而非abi.encode
处理简单类型
-
循环优化:
- 使用
unchecked
优化循环计数器 - 缓存数组长度
- 批量操作而非单个修改
- 使用
-
存储优化:
- 只在值变化时写入存储
- 使用打包和位运算减少存储槽使用
-
编码/解码优化:
- 选择合适的编码方法
- 打包多个小值到单个大值
-
短路评估:
- 将计算量小的条件放在前面
- 利用
&&
和||
的短路特性
-
常量优化:
- 使用常量替代重复计算
- 预计算复杂表达式
-
内存管理:
- 尽量减少临时变量
- 重用内存空间
- 预分配足够的内存空间
12. 实际应用示例
本节提供一些结合字面量和基本类型转换的实际应用案例。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// 应用案例1: ERC20 代币合约
contract SimpleERC20 {
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _initialSupply) {
name = _name;
symbol = _symbol;
decimals = _decimals;
// 类型转换应用:计算实际供应量包括小数位
totalSupply = _initialSupply * 10**uint256(_decimals);
balances[msg.sender] = totalSupply;
}
// 发送代币
function transfer(address _to, uint256 _value) public returns (bool success) {
// 地址检查:确保不是零地址
require(_to != address(0), "Invalid recipient address");
// 余额检查:整数比较
require(balances[msg.sender] >= _value, "Insufficient balance");
// 更新余额
balances[msg.sender] -= _value;
balances[_to] += _value;
// 事件参数:直接使用参数无需转换
emit Transfer(msg.sender, _to, _value);
return true;
}
// 授权其他地址使用代币
function approve(address _spender, uint256 _value) public returns (bool success) {
// 更新授权映射
allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
// 从授权地址转账代币
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
// 地址检查
require(_to != address(0), "Invalid recipient address");
// 多重条件检查
require(balances[_from] >= _value, "Insufficient balance");
require(allowances[_from][msg.sender] >= _value, "Insufficient allowance");
// 更新余额
balances[_from] -= _value;
balances[_to] += _value;
// 更新授权
allowances[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
// 格式化余额显示
function formatBalance(uint256 _balance) public view returns (string memory) {
// 将原始余额分割为整数部分和小数部分
uint256 factor = 10**uint256(decimals);
uint256 integerPart = _balance / factor;
uint256 fractionalPart = _balance % factor;
// 转换整数部分
string memory integerStr = uint2str(integerPart);
// 转换小数部分,加前导零
string memory fractionalStr = uint2str(fractionalPart);
string memory paddedFractionalStr = padZeros(fractionalStr, decimals);
// 组合结果
return string(abi.encodePacked(integerStr, ".", paddedFractionalStr));
}
// 辅助函数:整数转字符串
function uint2str(uint256 _i) internal pure returns (string memory) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 length;
while (j != 0) {
length++;
j /= 10;
}
bytes memory bstr = new bytes(length);
uint256 k = length;
j = _i;
while (j != 0) {
bstr[--k] = bytes1(uint8(48 + j % 10));
j /= 10;
}
return string(bstr);
}
// 辅助函数:添加前导零
function padZeros(string memory _str, uint8 _length) internal pure returns (string memory) {
bytes memory strBytes = bytes(_str);
if (strBytes.length >= _length) {
return _str;
}
bytes memory result = new bytes(_length);
// 填充前导零
uint256 i = 0;
for (i = 0; i < _length - strBytes.length; i++) {
result[i] = "0";
}
// 复制原始字符串
for (uint256 j = 0; j < strBytes.length; j++) {
result[i++] = strBytes[j];
}
return string(result);
}
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// 应用案例2: 订单簿智能合约
contract OrderBook {
// 使用枚举表示订单类型
enum OrderType { Buy, Sell }
// 使用枚举表示订单状态
enum OrderStatus { Open, Filled, Cancelled }
// 订单结构
struct Order {
uint256 id;
address trader;
OrderType orderType;
uint256 amount;
uint256 price; // 以 wei 为单位的价格
uint256 timestamp;
OrderStatus status;
}
// 订单存储
mapping(uint256 => Order) public orders;
uint256 public nextOrderId = 1;
// 地址到订单数组的映射
mapping(address => uint256[]) private userOrders;
// 创建订单
function createOrder(OrderType _type, uint256 _amount, uint256 _price) public returns (uint256) {
// 创建订单结构
uint256 orderId = nextOrderId++;
orders[orderId] = Order({
id: orderId,
trader: msg.sender,
orderType: _type,
amount: _amount,
price: _price,
timestamp: block.timestamp,
status: OrderStatus.Open
});
// 添加到用户订单列表
userOrders[msg.sender].push(orderId);
// 发出订单创建事件
emit OrderCreated(
orderId,
msg.sender,
uint8(_type), // 枚举转换为 uint8
_amount,
_price,
block.timestamp
);
return orderId;
}
// 填充订单
function fillOrder(uint256 _orderId) public payable {
Order storage order = orders[_orderId];
// 验证订单存在且为开放状态
require(order.id == _orderId, "Order does not exist");
require(order.status == OrderStatus.Open, "Order is not open");
// 验证交易者不是自己
require(order.trader != msg.sender, "Cannot fill your own order");
// 验证买单的付款金额
if (order.orderType == OrderType.Sell) {
// 买家必须发送足够的以太币
uint256 totalCost = order.price * order.amount;
require(msg.value >= totalCost, "Insufficient funds sent");
// 计算找零
uint256 change = msg.value - totalCost;
// 将款项发送给卖家
(bool success, ) = order.trader.call{value: totalCost}("");
require(success, "Failed to send funds to seller");
// 将找零返还给买家
if (change > 0) {
(bool refundSuccess, ) = msg.sender.call{value: change}("");
require(refundSuccess, "Failed to return change");
}
} else {
// 对于买单,我们需要处理代币转移(此示例简化,不实现完整代币逻辑)
// 这里应该实现代币从卖家转移到买家的逻辑
}
// 更新订单状态
order.status = OrderStatus.Filled;
// 发出订单填充事件
emit OrderFilled(_orderId, msg.sender, block.timestamp);
}
// 取消订单
function cancelOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// 验证订单存在且为开放状态
require(order.id == _orderId, "Order does not exist");
require(order.status == OrderStatus.Open, "Order is not open");
// 验证调用者是订单创建者
require(order.trader == msg.sender, "Not your order");
// 更新订单状态
order.status = OrderStatus.Cancelled;
// 发出订单取消事件
emit OrderCancelled(_orderId, block.timestamp);
}
// 获取用户的所有订单
function getUserOrders(address _user) public view returns (uint256[] memory) {
return userOrders[_user];
}
// 获取订单详情
function getOrder(uint256 _orderId) public view returns (
uint256 id,
address trader,
uint8 orderType,
uint256 amount,
uint256 price,
uint256 timestamp,
uint8 status
) {
Order storage order = orders[_orderId];
require(order.id == _orderId, "Order does not exist");
return (
order.id,
order.trader,
uint8(order.orderType), // 枚举转换为 uint8
order.amount,
order.price,
order.timestamp,
uint8(order.status) // 枚举转换为 uint8
);
}
// 将价格转换为易读格式
function formatPrice(uint256 _price) public pure returns (string memory) {
// 将 wei 转换为 ether (1 ether = 10^18 wei)
uint256 etherValue = _price / 1e18;
uint256 remainder = _price % 1e18;
// 格式化字符串
string memory etherStr = uint2str(etherValue);
string memory remainderStr = uint2str(remainder);
string memory paddedRemainderStr = padZeros(remainderStr, 18);
return string(abi.encodePacked(etherStr, ".", paddedRemainderStr, " ETH"));
}
// 辅助函数:整数转字符串
function uint2str(uint256 _i) internal pure returns (string memory) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 length;
while (j != 0) {
length++;
j /= 10;
}
bytes memory bstr = new bytes(length);
uint256 k = length;
j = _i;
while (j != 0) {
bstr[--k] = bytes1(uint8(48 + j % 10));
j /= 10;
}
return string(bstr);
}
// 辅助函数:添加前导零
function padZeros(string memory _str, uint8 _length) internal pure returns (string memory) {
bytes memory strBytes = bytes(_str);
if (strBytes.length >= _length) {
return _str;
}
bytes memory result = new bytes(_length);
// 填充前导零
uint256 i = 0;
for (i = 0; i < _length - strBytes.length; i++) {
result[i] = "0";
}
// 复制原始字符串
for (uint256 j = 0; j < strBytes.length; j++) {
result[i++] = strBytes[j];
}
return string(result);
}
event OrderCreated(
uint256 indexed orderId,
address indexed trader,
uint8 orderType,
uint256 amount,
uint256 price,
uint256 timestamp
);
event OrderFilled(
uint256 indexed orderId,
address indexed filler,
uint256 timestamp
);
event OrderCancelled(
uint256 indexed orderId,
uint256 timestamp
);
// 接收以太币功能
receive() external payable {}
}
// 应用案例3: 身份验证合约
contract Authentication {
// 权限标志
uint8 private constant ROLE_ADMIN = 1; // 00000001
uint8 private constant ROLE_MODERATOR = 2; // 00000010
uint8 private constant ROLE_USER = 4; // 00000100
// 用户结构
struct User {
string username;
bytes32 passwordHash;
uint8 roles; // 使用位图存储多个角色
bool active;
}
// 用户存储
mapping(address => User) private users;
mapping(string => address) private usernameToAddress;
// 注册新用户
function register(string memory _username, string memory _password) public returns (bool) {
// 检查用户名是否已存在
require(usernameToAddress[_username] == address(0), "Username already exists");
// 检查此地址是否已注册
require(bytes(users[msg.sender].username).length == 0, "Address already registered");
// 创建密码哈希
bytes32 passwordHash = keccak256(abi.encodePacked(_password, msg.sender));
// 创建用户
users[msg.sender] = User({
username: _username,
passwordHash: passwordHash,
roles: ROLE_USER, // 默认为普通用户角色
active: true
});
// 设置用户名到地址的映射
usernameToAddress[_username] = msg.sender;
return true;
}
// 验证登录
function login(string memory _password) public view returns (bool) {
User storage user = users[msg.sender];
// 检查用户是否存在且处于活动状态
require(bytes(user.username).length > 0, "User not registered");
require(user.active, "User is not active");
// 验证密码
bytes32 passwordHash = keccak256(abi.encodePacked(_password, msg.sender));
return user.passwordHash == passwordHash;
}
// 授予角色
function grantRole(address _user, uint8 _role) public {
// 检查调用者是否为管理员
require(hasRole(msg.sender, ROLE_ADMIN), "Not an admin");
// 检查用户是否存在
require(bytes(users[_user].username).length > 0, "User not registered");
// 添加角色
users[_user].roles |= _role;
}
// 撤销角色
function revokeRole(address _user, uint8 _role) public {
// 检查调用者是否为管理员
require(hasRole(msg.sender, ROLE_ADMIN), "Not an admin");
// 检查用户是否存在
require(bytes(users[_user].username).length > 0, "User not registered");
// 移除角色
users[_user].roles &= ~_role;
}
// 检查用户是否拥有特定角色
function hasRole(address _user, uint8 _role) public view returns (bool) {
return (users[_user].roles & _role) != 0;
}
// 设置管理员
function setAdmin(address _user) public {
// 检查是否首个管理员(简化的逻辑,实际应用需要更复杂的初始化)
require(
msg.sender == address(this) ||
(hasRole(msg.sender, ROLE_ADMIN) && bytes(users[msg.sender].username).length > 0),
"Not authorized"
);
// 如果用户不存在,创建管理员用户
if (bytes(users[_user].username).length == 0) {
string memory adminUsername = string(abi.encodePacked("admin_", addressToString(_user)));
users[_user] = User({
username: adminUsername,
passwordHash: bytes32(0), // 需要重置密码
roles: ROLE_ADMIN,
active: true
});
usernameToAddress[adminUsername] = _user;
} else {
// 添加管理员角
// 添加管理员角色
users[_user].roles |= ROLE_ADMIN;
}
}
// 重置密码
function resetPassword(address _user, string memory _newPassword) public {
// 只有管理员或用户自己可以重置密码
require(
msg.sender == _user || hasRole(msg.sender, ROLE_ADMIN),
"Not authorized"
);
// 检查用户是否存在
require(bytes(users[_user].username).length > 0, "User not registered");
// 创建新密码哈希
bytes32 newPasswordHash = keccak256(abi.encodePacked(_newPassword, _user));
// 更新密码
users[_user].passwordHash = newPasswordHash;
}
// 停用/启用用户
function setUserStatus(address _user, bool _active) public {
// 只有管理员可以修改用户状态
require(hasRole(msg.sender, ROLE_ADMIN), "Not an admin");
// 检查用户是否存在
require(bytes(users[_user].username).length > 0, "User not registered");
// 更新状态
users[_user].active = _active;
}
// 获取用户详情
function getUserDetails(address _user) public view returns (
string memory username,
uint8 roles,
bool active
) {
// 只有管理员或用户自己可以查看详情
require(
msg.sender == _user || hasRole(msg.sender, ROLE_ADMIN),
"Not authorized"
);
User storage user = users[_user];
return (
user.username,
user.roles,
user.active
);
}
// 辅助函数:地址转字符串
function addressToString(address _addr) internal pure returns (string memory) {
bytes memory addressBytes = abi.encodePacked(_addr);
bytes memory result = new bytes(2 + addressBytes.length * 2);
result[0] = "0";
result[1] = "x";
bytes memory characters = "0123456789abcdef";
for (uint256 i = 0; i < addressBytes.length; i++) {
result[2 + i * 2] = characters[uint8(addressBytes[i] >> 4)];
result[3 + i * 2] = characters[uint8(addressBytes[i] & 0x0f)];
}
return string(result);
}
}
// 应用案例4: 智能合约钱包
contract SmartWallet {
address public owner;
// 交易结构
struct Transaction {
address to;
uint256 value;
bytes data;
bool executed;
uint256 timestamp;
}
// 交易历史
Transaction[] public transactions;
// 用于跟踪日常支出的映射
mapping(uint256 => uint256) public dailySpending; // 日期 => 金额
// 每日支出限额
uint256 public dailyLimit;
// 白名单地址可自动通过
mapping(address => bool) public whitelist;
// 事件
event Deposit(address indexed sender, uint256 amount, uint256 balance);
event TransactionCreated(uint256 indexed txIndex, address indexed to, uint256 value);
event TransactionExecuted(uint256 indexed txIndex);
constructor(uint256 _dailyLimit) {
owner = msg.sender;
dailyLimit = _dailyLimit;
}
// 修饰符:仅所有者
modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}
// 接收以太币
receive() external payable {
emit Deposit(msg.sender, msg.value, address(this).balance);
}
// 创建交易
function submitTransaction(address _to, uint256 _value, bytes memory _data)
public
onlyOwner
returns (uint256)
{
// 检查参数
require(_to != address(0), "Invalid recipient");
uint256 txIndex = transactions.length;
transactions.push(Transaction({
to: _to,
value: _value,
data: _data,
executed: false,
timestamp: block.timestamp
}));
emit TransactionCreated(txIndex, _to, _value);
// 如果是白名单地址或在限额内,自动执行
if (whitelist[_to] || isWithinDailyLimit(_value)) {
executeTransaction(txIndex);
}
return txIndex;
}
// 执行交易
function executeTransaction(uint256 _txIndex) public onlyOwner {
// 检查交易索引
require(_txIndex < transactions.length, "Transaction does not exist");
Transaction storage transaction = transactions[_txIndex];
// 检查交易是否已执行
require(!transaction.executed, "Transaction already executed");
// 检查合约余额
require(address(this).balance >= transaction.value, "Insufficient balance");
// 标记为已执行
transaction.executed = true;
// 更新每日支出
uint256 today = block.timestamp / 86400; // 转换为天数
dailySpending[today] += transaction.value;
// 执行交易
(bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
require(success, "Transaction execution failed");
emit TransactionExecuted(_txIndex);
}
// 检查是否在每日限额内
function isWithinDailyLimit(uint256 _amount) public view returns (bool) {
uint256 today = block.timestamp / 86400;
return dailySpending[today] + _amount <= dailyLimit;
}
// 设置每日限额
function setDailyLimit(uint256 _limit) public onlyOwner {
dailyLimit = _limit;
}
// 添加地址到白名单
function addToWhitelist(address _address) public onlyOwner {
whitelist[_address] = true;
}
// 从白名单移除地址
function removeFromWhitelist(address _address) public onlyOwner {
whitelist[_address] = false;
}
// 转移所有权
function transferOwnership(address _newOwner) public onlyOwner {
require(_newOwner != address(0), "Invalid owner address");
owner = _newOwner;
}
// 获取钱包余额
function getBalance() public view returns (uint256) {
return address(this).balance;
}
// 获取交易数量
function getTransactionCount() public view returns (uint256) {
return transactions.length;
}
// 获取特定日期的支出
function getDailySpending(uint256 _timestamp) public view returns (uint256) {
uint256 day = _timestamp / 86400;
return dailySpending[day];
}
// 获取待执行交易
function getPendingTransactions() public view returns (uint256[] memory) {
// 首先计算待处理交易数量
uint256 count = 0;
for (uint256 i = 0; i < transactions.length; i++) {
if (!transactions[i].executed) {
count++;
}
}
// 创建结果数组
uint256[] memory result = new uint256[](count);
uint256 index = 0;
// 填充结果
for (uint256 i = 0; i < transactions.length; i++) {
if (!transactions[i].executed) {
result[index++] = i;
}
}
return result;
}
// 批量执行交易
function batchExecuteTransactions(uint256[] memory _txIndices) public onlyOwner {
for (uint256 i = 0; i < _txIndices.length; i++) {
executeTransaction(_txIndices[i]);
}
}
// 格式化时间戳为可读格式(简化版)
function formatTimestamp(uint256 _timestamp) public pure returns (
uint256 year,
uint256 month,
uint256 day
) {
// 这是一个简化版本,实际实现需要考虑闰年等因素
// 基准时间:1970-01-01
uint256 secondsPerDay = 24 * 60 * 60;
uint256 daysSince1970 = _timestamp / secondsPerDay;
// 简单近似
year = 1970 + (daysSince1970 / 365);
uint256 daysThisYear = daysSince1970 % 365;
month = 1 + (daysThisYear / 30);
day = 1 + (daysThisYear % 30);
return (year, month, day);
}
// 转换 Wei 到 Ether(小数表示)
function weiToEtherString(uint256 _wei) public pure returns (string memory) {
uint256 ether_value = _wei / 1e18;
uint256 remainder = _wei % 1e18;
if (remainder == 0) {
return string(abi.encodePacked(uint2str(ether_value), " ETH"));
}
return string(abi.encodePacked(
uint2str(ether_value),
".",
padZeros(uint2str(remainder), 18),
" ETH"
));
}
// 辅助函数:整数转字符串
function uint2str(uint256 _i) internal pure returns (string memory) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 length;
while (j != 0) {
length++;
j /= 10;
}
bytes memory bstr = new bytes(length);
uint256 k = length;
j = _i;
while (j != 0) {
bstr[--k] = bytes1(uint8(48 + j % 10));
j /= 10;
}
return string(bstr);
}
// 辅助函数:添加前导零
function padZeros(string memory _str, uint8 _length) internal pure returns (string memory) {
bytes memory strBytes = bytes(_str);
if (strBytes.length >= _length) {
return _str;
}
bytes memory result = new bytes(_length);
// 填充前导零
uint256 i = 0;
for (i = 0; i < _length - strBytes.length; i++) {
result[i] = "0";
}
// 复制原始字符串
for (uint256 j = 0; j < strBytes.length; j++) {
result[i++] = strBytes[j];
}
return string(result);
}
}
总结
本指南全面介绍了 Solidity 中字面量和基本类型之间的转换技术。这些转换是智能合约开发的基础,掌握它们可以帮助您编写更安全、更高效的代码。
关键要点
-
字面量类型:
- Solidity 支持多种字面量,包括整型、字符串、十六进制、地址、布尔值和有理数字面量。
- 字面量会自动推断为最合适的类型,但在赋值时会根据目标变量类型进行转换。
-
类型转换规则:
- 隐式转换仅在保证安全的情况下进行(例如小整型到大整型)。
- 显式转换使用类型名作为函数,例如
uint8(value)
。 - 需要注意溢出、截断和精度损失等问题。
-
安全考量:
- 始终验证类型转换是否在有效范围内,尤其是涉及收缩转换时。
- 对于敏感操作,实现专门的安全转换函数。
- 特别注意有符号/无符号转换和零值检查。
-
优化技巧:
- 使用正确大小的类型,特别是在存储结构体字段时。
- 利用位运算替代某些算术运算,节省 gas。
- 缓存转换结果,避免重复转换。
- 使用
unchecked
块优化确定不会溢出的操作。
-
实际应用:
- 类型转换在代币合约、交易系统、身份验证和钱包应用中都有重要作用。
- 选择适当的转换路径可以提高代码可读性和维护性。
- 在处理用户输入和外部数据时,安全的类型转换尤为重要。
通过理解和应用这些转换技术,你可以更有效地处理 Solidity 中的各种数据类型,写出更高质量的智能合约代码。记住,在智能合约开发中,正确性和安全性始终是首要考虑因素,合理的类型转换是实现这些目标的关键部分。