解答這些 Solidity 開發(fā)中的重要問題
我來詳細(xì)解答這些 Solidity 開發(fā)中的重要問題:
1. Calldata 的含義和使用場(chǎng)景
Calldata 定義
calldata 是函數(shù)參數(shù)的存儲(chǔ)位置,專門用于存儲(chǔ)外部函數(shù)調(diào)用的參數(shù)數(shù)據(jù)。
使用場(chǎng)景
contract CalldataExample {
// 1. 外部函數(shù)參數(shù)默認(rèn)使用 calldata
function externalFunction(uint256[] calldata data) external pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < data.length; i++) {
sum += data[i];
}
return sum;
}
// 2. 內(nèi)部函數(shù)也可以使用 calldata
function internalFunction(uint256[] calldata data) internal pure returns (uint256) {
return data.length;
}
// 3. 字符串和字節(jié)數(shù)組
function processString(string calldata message) external pure returns (uint256) {
return bytes(message).length;
}
}
Calldata vs Memory vs Storage
contract DataLocationComparison {
uint256[] public storageArray;
function calldataExample(uint256[] calldata data) external pure returns (uint256) {
// calldata: 只讀,最省 Gas
return data.length;
}
function memoryExample(uint256[] calldata data) external pure returns (uint256) {
// memory: 可讀寫,需要復(fù)制數(shù)據(jù)
uint256[] memory localArray = data;
localArray[0] = 999; // 可以修改
return localArray.length;
}
function storageExample() external {
// storage: 可讀寫,直接操作狀態(tài)變量
storageArray.push(1);
storageArray[0] = 999;
}
}
2. 數(shù)組與 Map 存儲(chǔ)的區(qū)別
存儲(chǔ)布局
contract StorageLayout {
// 數(shù)組存儲(chǔ)
uint256[] public array; // 連續(xù)存儲(chǔ)
mapping(uint256 => uint256) public map; // 分散存儲(chǔ)
function demonstrateStorage() public {
// 數(shù)組:連續(xù)存儲(chǔ)槽
array.push(1);
array.push(2);
array.push(3);
// 存儲(chǔ)槽:keccak256(0) + 0, keccak256(0) + 1, keccak256(0) + 2
// 映射:分散存儲(chǔ)
map[1] = 100;
map[2] = 200;
// 存儲(chǔ)槽:keccak256(1, 1), keccak256(2, 1)
}
}
存儲(chǔ)槽計(jì)算
contract StorageSlotCalculation {
uint256[] public array;
mapping(uint256 => uint256) public map;
function getArraySlot(uint256 index) public view returns (bytes32) {
// 數(shù)組元素存儲(chǔ)槽 = keccak256(array_slot) + index
return keccak256(abi.encodePacked(uint256(0))); // 假設(shè)數(shù)組在槽0
}
function getMapSlot(uint256 key) public view returns (bytes32) {
// 映射存儲(chǔ)槽 = keccak256(key, map_slot)
return keccak256(abi.encodePacked(key, uint256(1))); // 假設(shè)映射在槽1
}
}
3. 合約安全保障
常見安全措施
contract SecurityExample {
address public owner;
bool public paused;
uint256 public constant MAX_SUPPLY = 1000000;
// 1. 權(quán)限控制
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// 2. 重入攻擊防護(hù)
bool private locked;
modifier noReentrancy() {
require(!locked, "Reentrancy detected");
locked = true;
_;
locked = false;
}
// 3. 整數(shù)溢出防護(hù)
function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
require(a + b >= a, "Overflow");
return a + b;
}
// 4. 外部調(diào)用安全
function safeTransfer(address to, uint256 amount) external {
require(to != address(0), "Invalid address");
require(amount > 0, "Invalid amount");
(bool success, ) = to.call{value: amount}("");
require(success, "Transfer failed");
}
// 5. 事件記錄
event SecurityEvent(string action, address user, uint256 amount);
}
開發(fā)安全考慮
- 輸入驗(yàn)證:檢查所有外部輸入
- 權(quán)限控制:使用修飾符控制訪問
- 重入防護(hù):防止重入攻擊
- 整數(shù)安全:使用 SafeMath 或 Solidity 0.8+
- 外部調(diào)用:謹(jǐn)慎處理外部調(diào)用
- 事件記錄:記錄重要操作
4. Gas 優(yōu)化措施
優(yōu)化技巧
contract GasOptimization {
// 1. 使用 uint256 而不是 uint8(在某些情況下)
uint256 public value1; // 更省 Gas
uint8 public value2; // 可能更費(fèi) Gas(需要轉(zhuǎn)換)
// 2. 打包變量
struct PackedData {
uint128 a; // 16字節(jié)
uint128 b; // 16字節(jié)
uint32 c; // 4字節(jié)
uint32 d; // 4字節(jié)
// 總共32字節(jié),一個(gè)存儲(chǔ)槽
}
// 3. 使用 events 而不是 storage
event DataStored(uint256 indexed id, string data);
function storeData(uint256 id, string calldata data) external {
emit DataStored(id, data); // 比存儲(chǔ)到 mapping 便宜
}
// 4. 批量操作
function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) external {
require(recipients.length == amounts.length, "Length mismatch");
for (uint256 i = 0; i < recipients.length; i++) {
// 批量處理,減少交易數(shù)量
}
}
// 5. 使用 assembly 優(yōu)化
function optimizedAdd(uint256 a, uint256 b) public pure returns (uint256) {
assembly {
let result := add(a, b)
if lt(result, a) {
revert(0, 0)
}
mstore(0x0, result)
return(0x0, 0x20)
}
}
}
備注 :
1 使用 uint256 而不是 uint8 原理理解:
-
EVM 的存儲(chǔ)單元是 256 位:
- EVM 的基本存儲(chǔ)單元是 256 位(32 字節(jié)),所有的存儲(chǔ)操作都會(huì)以 256 位為單位進(jìn)行。
- 如果使用
uint8,雖然數(shù)據(jù)本身只占 8 位,但在存儲(chǔ)時(shí)仍然需要填充到 256 位,這可能導(dǎo)致額外的操作成本。
-
類型轉(zhuǎn)換的額外開銷:
- 如果在操作中需要將
uint8轉(zhuǎn)換為uint256(例如在數(shù)學(xué)運(yùn)算或與其他uint256類型變量交互時(shí)),會(huì)引入額外的類型轉(zhuǎn)換操作,從而增加 Gas 消耗。
- 如果在操作中需要將
-
存儲(chǔ)槽的對(duì)齊問題:
- 如果多個(gè)小數(shù)據(jù)類型(如
uint8)可以打包到同一個(gè)存儲(chǔ)槽中(節(jié)省存儲(chǔ)空間),那么使用uint8是有優(yōu)勢(shì)的。 - 但如果單獨(dú)存儲(chǔ)一個(gè)
uint8,它仍然會(huì)占用一個(gè)完整的 256 位存儲(chǔ)槽,因此沒有任何存儲(chǔ)空間上的優(yōu)勢(shì)。
- 如果多個(gè)小數(shù)據(jù)類型(如
-
操作成本的差異:
- 對(duì)于 EVM,操作
uint256是原生的,成本最低。 - 操作
uint8可能需要額外的指令來處理數(shù)據(jù)的截?cái)嗷驍U(kuò)展。
- 對(duì)于 EVM,操作
總結(jié):
- 使用
uint256更省 Gas:當(dāng)變量是單獨(dú)存儲(chǔ)或頻繁參與運(yùn)算時(shí),uint256是更高效的選擇。 - 使用
uint8更省空間:當(dāng)多個(gè)小變量可以打包到同一個(gè)存儲(chǔ)槽中時(shí),uint8可以節(jié)省存儲(chǔ)空間,但需要權(quán)衡操作成本。
2 使用 events 而不是 storage原理理解:
-
Gas 成本差異:
- Storage:在 Solidity 中,寫入狀態(tài)變量(
storage)是最昂貴的操作之一,因?yàn)閿?shù)據(jù)需要永久存儲(chǔ)在鏈上。 - Events:寫入事件日志(
event)的成本相對(duì)較低,因?yàn)槭录?shù)據(jù)存儲(chǔ)在交易日志中,而不是合約的狀態(tài)存儲(chǔ)中。
- Storage:在 Solidity 中,寫入狀態(tài)變量(
-
數(shù)據(jù)存儲(chǔ)位置:
- Storage:數(shù)據(jù)存儲(chǔ)在合約的狀態(tài)存儲(chǔ)中,所有節(jié)點(diǎn)都需要保存這些數(shù)據(jù)。
- Events:事件數(shù)據(jù)存儲(chǔ)在交易日志中,主要用于鏈下(off-chain)監(jiān)聽和處理,不會(huì)占用合約的狀態(tài)存儲(chǔ)。
-
使用場(chǎng)景:
- 如果數(shù)據(jù)僅用于鏈下消費(fèi)(如前端應(yīng)用或數(shù)據(jù)分析),使用
events是更高效的選擇。 - 如果數(shù)據(jù)需要在鏈上長(zhǎng)期保存并供其他合約訪問,則必須使用
storage。
- 如果數(shù)據(jù)僅用于鏈下消費(fèi)(如前端應(yīng)用或數(shù)據(jù)分析),使用
-
可訪問性:
- Storage:數(shù)據(jù)可以直接在鏈上讀取,適合需要頻繁訪問的場(chǎng)景。
- Events:事件數(shù)據(jù)無法直接在鏈上讀取,只能通過鏈下工具(如 Web3.js 或 The Graph)查詢。
優(yōu)化總結(jié):
- 使用
events:適合記錄日志或僅供鏈下消費(fèi)的數(shù)據(jù),減少存儲(chǔ)成本。 - 使用
storage:適合需要鏈上長(zhǎng)期保存和訪問的數(shù)據(jù),但成本較高。
通過合理選擇 events 和 storage,可以在降低 Gas 消耗的同時(shí)滿足不同的業(yè)務(wù)需求。
3 批量操作原理解釋:
原理:
-
減少交易數(shù)量:
- 如果每次轉(zhuǎn)賬都單獨(dú)調(diào)用一個(gè)函數(shù)(即每個(gè)接收者和金額對(duì)應(yīng)一筆交易),會(huì)產(chǎn)生多次交易,每次交易都需要支付基礎(chǔ) Gas 費(fèi)用(如
21000Gas)。 - 使用批量操作時(shí),所有轉(zhuǎn)賬操作合并到一個(gè)交易中,只需支付一次基礎(chǔ) Gas 費(fèi)用,大幅降低總成本。
- 如果每次轉(zhuǎn)賬都單獨(dú)調(diào)用一個(gè)函數(shù)(即每個(gè)接收者和金額對(duì)應(yīng)一筆交易),會(huì)產(chǎn)生多次交易,每次交易都需要支付基礎(chǔ) Gas 費(fèi)用(如
-
共享計(jì)算成本:
- 在批量操作中,循環(huán)體內(nèi)的邏輯(如驗(yàn)證、計(jì)算)可以共享,避免重復(fù)執(zhí)行相同的代碼邏輯。
- 例如,
require和其他檢查邏輯只需執(zhí)行一次,而不是每筆交易都單獨(dú)執(zhí)行。
-
減少合約調(diào)用開銷:
- 每次調(diào)用合約函數(shù)都會(huì)產(chǎn)生額外的開銷(如
CALL操作的 Gas 消耗)。 - 批量操作將多個(gè)調(diào)用合并為一次,減少了合約調(diào)用的開銷。
- 每次調(diào)用合約函數(shù)都會(huì)產(chǎn)生額外的開銷(如
-
優(yōu)化存儲(chǔ)和內(nèi)存操作:
- 在批量操作中,數(shù)據(jù)可以一次性加載到內(nèi)存中進(jìn)行處理,避免多次存儲(chǔ)和加載操作,從而進(jìn)一步節(jié)省 Gas。
總結(jié):
批量操作通過減少交易數(shù)量、共享計(jì)算成本、減少合約調(diào)用開銷等方式優(yōu)化了 Gas 消耗,適合需要對(duì)多個(gè)數(shù)據(jù)進(jìn)行相同邏輯處理的場(chǎng)景(如批量轉(zhuǎn)賬、批量更新)。
在 Solidity 中,使用 assembly 是一種高級(jí)的 Gas 優(yōu)化方式。它允許開發(fā)者直接編寫低級(jí)的 EVM 字節(jié)碼操作,從而繞過 Solidity 的一些高層抽象,減少不必要的開銷。以下是詳細(xì)的解釋:
1. 為什么使用 assembly 可以優(yōu)化 Gas?
-
繞過 Solidity 的高層抽象:
Solidity 編譯器會(huì)將高層代碼轉(zhuǎn)換為 EVM 字節(jié)碼,這個(gè)過程中可能會(huì)引入一些額外的操作(如邊界檢查、類型轉(zhuǎn)換等)。使用assembly可以直接操作底層字節(jié)碼,避免這些額外的開銷。 -
更精確的控制:
在某些情況下,開發(fā)者可以通過assembly精確控制內(nèi)存和存儲(chǔ)的使用,減少冗余操作。 -
減少操作碼數(shù)量:
Solidity 的某些操作可能會(huì)生成多個(gè)操作碼,而使用assembly可以直接使用更少的操作碼完成相同的任務(wù),從而降低 Gas 消耗。
2. 使用 assembly 的場(chǎng)景
以下是一些常見的使用場(chǎng)景:
(1) 數(shù)學(xué)運(yùn)算
Solidity 的數(shù)學(xué)運(yùn)算會(huì)進(jìn)行溢出檢查(在 0.8.x 版本中默認(rèn)開啟),這會(huì)增加 Gas 消耗。如果開發(fā)者能夠確保安全性,可以使用 assembly 繞過這些檢查。
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 result;
assembly {
result := add(a, b)
}
return result;
}
(2) 內(nèi)存操作
直接操作內(nèi)存可以避免 Solidity 的高層抽象帶來的額外開銷。
function copyMemory(uint256 src, uint256 dest, uint256 len) internal pure {
assembly {
for { let i := 0 } lt(i, len) { i := add(i, 32) } {
mstore(add(dest, i), mload(add(src, i)))
}
}
}
(3) 讀取存儲(chǔ)槽
直接讀取或?qū)懭氪鎯?chǔ)槽可以減少存儲(chǔ)操作的開銷。
function readStorage(bytes32 slot) internal view returns (bytes32 value) {
assembly {
value := sload(slot)
}
}
(4) 自定義錯(cuò)誤處理
Solidity 的 require 和 revert 會(huì)生成額外的字符串處理邏輯,使用 assembly 可以更高效地實(shí)現(xiàn)錯(cuò)誤處理。
function revertWithMessage(string memory message) internal pure {
assembly {
let ptr := mload(0x40)
let len := mload(message)
mstore(ptr, len)
mstore(add(ptr, 0x20), mload(add(message, 0x20)))
revert(ptr, add(len, 0x20))
}
}
3. 使用 assembly 的注意事項(xiàng)
-
安全性:
assembly繞過了 Solidity 的安全檢查(如溢出檢查、邊界檢查等),因此開發(fā)者需要手動(dòng)確保代碼的安全性。
-
可讀性:
assembly代碼的可讀性較差,維護(hù)成本較高,建議僅在性能關(guān)鍵的地方使用。
-
調(diào)試難度:
- 由于直接操作字節(jié)碼,調(diào)試
assembly代碼比調(diào)試 Solidity 高層代碼更困難。
- 由于直接操作字節(jié)碼,調(diào)試
-
兼容性:
assembly是直接操作 EVM 的字節(jié)碼,可能會(huì)受到未來 EVM 升級(jí)的影響。
4. 總結(jié)
使用 assembly 是一種強(qiáng)大的 Gas 優(yōu)化方式,但需要權(quán)衡性能與安全性、可讀性之間的關(guān)系。它適合在性能關(guān)鍵的場(chǎng)景下使用,例如:
- 高頻調(diào)用的數(shù)學(xué)運(yùn)算
- 大量?jī)?nèi)存操作
- 自定義存儲(chǔ)和錯(cuò)誤處理
開發(fā)者在使用 assembly 時(shí),應(yīng)確保代碼經(jīng)過嚴(yán)格的測(cè)試和審計(jì),以避免潛在的漏洞。
5. ABI 編碼
ABI 編碼示例
contract ABIExample {
function encodeData(uint256 a, string memory b) public pure returns (bytes memory) {
// 編碼函數(shù)調(diào)用
return abi.encodeWithSignature("transfer(address,uint256)", address(0x123), 1000);
}
function decodeData(bytes calldata data) public pure returns (uint256, string memory) {
// 解碼數(shù)據(jù)
return abi.decode(data, (uint256, string));
}
}
通過 ABI 調(diào)用合約
// Go 語言示例
package main
import (
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func callContract() {
// 1. 編碼函數(shù)調(diào)用
method := "transfer(address,uint256)"
data := abi.Encode([]interface{}{common.HexToAddress("0x123"), big.NewInt(1000)})
// 2. 構(gòu)建交易
tx := &types.Transaction{
To: &contractAddress,
Data: data,
Value: big.NewInt(0),
}
}
6. Call vs Delegatecall
Call 示例
contract CallExample {
function callExternal(address target, bytes calldata data) external returns (bool, bytes memory) {
// call: 在目標(biāo)合約的上下文中執(zhí)行
return target.call(data);
}
function callWithValue(address target, bytes calldata data, uint256 value) external returns (bool, bytes memory) {
// 帶 ETH 的調(diào)用
return target.call{value: value}(data);
}
}
Delegatecall 示例
contract DelegatecallExample {
address public implementation;
function delegatecallExternal(bytes calldata data) external returns (bool, bytes memory) {
// delegatecall: 在當(dāng)前合約的上下文中執(zhí)行目標(biāo)合約的代碼
return implementation.delegatecall(data);
}
// 代理模式示例
fallback() external payable {
implementation.delegatecall(msg.data);
}
}
區(qū)別總結(jié)
| 特性 | Call | Delegatecall |
|---|---|---|
| 執(zhí)行上下文 | 目標(biāo)合約 | 當(dāng)前合約 |
| 狀態(tài)變量 | 訪問目標(biāo)合約 | 訪問當(dāng)前合約 |
| msg.sender | 當(dāng)前合約 | 原始調(diào)用者 |
| 用途 | 普通調(diào)用 | 代理模式 |
7. Topic 和 Indexed
Event 中的 Topic
contract EventExample {
// 最多3個(gè) indexed 參數(shù)
event Transfer(
address indexed from, // topic 1
address indexed to, // topic 2
uint256 indexed tokenId, // topic 3
uint256 value // 非 indexed,存儲(chǔ)在 data 中
);
// 發(fā)出事件
function transfer(address to, uint256 tokenId, uint256 value) external {
emit Transfer(msg.sender, to, tokenId, value);
}
}
Topic 結(jié)構(gòu)
Event Log:
├── topics[0]: 事件簽名哈希
├── topics[1]: indexed 參數(shù)1
├── topics[2]: indexed 參數(shù)2
├── topics[3]: indexed 參數(shù)3
└── data: 非 indexed 參數(shù)
8. ERC20 vs ERC721
ERC20 標(biāo)準(zhǔn)
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
ERC721 標(biāo)準(zhǔn)
interface IERC721 {
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}
主要區(qū)別
| 特性 | ERC20 | ERC721 |
|---|---|---|
| 代幣類型 | 同質(zhì)化 | 非同質(zhì)化 |
| 數(shù)量 | 可分割 | 不可分割 |
| 標(biāo)識(shí) | 數(shù)量 | 唯一ID |
| 用途 | 貨幣、股票 | 藝術(shù)品、游戲道具 |
9. Bool 類型優(yōu)化
Bool 存儲(chǔ)優(yōu)化
contract BoolOptimization {
// 原始方式:每個(gè) bool 占用一個(gè)存儲(chǔ)槽
bool public flag1;
bool public flag2;
bool public flag3;
bool public flag4;
// 優(yōu)化方式:打包到同一個(gè)存儲(chǔ)槽
struct PackedBools {
bool flag1; // 1位
bool flag2; // 1位
bool flag3; // 1位
bool flag4; // 1位
// 剩余28位可以存儲(chǔ)其他數(shù)據(jù)
uint28 otherData; // 28位
}
PackedBools public packedFlags;
// 位操作優(yōu)化
uint256 public flags; // 使用位操作
function setFlag(uint256 index, bool value) external {
if (value) {
flags |= (1 << index);
} else {
flags &= ~(1 << index);
}
}
function getFlag(uint256 index) external view returns (bool) {
return (flags & (1 << index)) != 0;
}
}
10. 發(fā)行圖文并茂的 ERC721
完整的 NFT 合約
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ArtNFT {
struct NFTData {
string name;
string description;
string imageURI;
string animationURI;
string externalURI;
uint256 timestamp;
address creator;
}
mapping(uint256 => NFTData) public nftData;
mapping(address => uint256[]) public ownedTokens;
uint256 public totalSupply;
string public baseURI;
event NFTMinted(uint256 indexed tokenId, address indexed creator, string name);
function mintNFT(
string memory name,
string memory description,
string memory imageURI,
string memory animationURI,
string memory externalURI
) external returns (uint256) {
uint256 tokenId = totalSupply + 1;
totalSupply = tokenId;
nftData[tokenId] = NFTData({
name: name,
description: description,
imageURI: imageURI,
animationURI: animationURI,
externalURI: externalURI,
timestamp: block.timestamp,
creator: msg.sender
});
ownedTokens[msg.sender].push(tokenId);
emit NFTMinted(tokenId, msg.sender, name);
return tokenId;
}
function getNFTData(uint256 tokenId) external view returns (NFTData memory) {
return nftData[tokenId];
}
function tokenURI(uint256 tokenId) external view returns (string memory) {
NFTData memory data = nftData[tokenId];
return string(abi.encodePacked(
'data:application/json;base64,',
base64Encode(abi.encodePacked(
'{"name":"', data.name, '",',
'"description":"', data.description, '",',
'"image":"', data.imageURI, '",',
'"animation_url":"', data.animationURI, '",',
'"external_url":"', data.externalURI, '",',
'"attributes":[',
'{"trait_type":"Creator","value":"', toAsciiString(data.creator), '"}',
']}'
))
));
}
function toAsciiString(address x) internal pure returns (string memory) {
bytes memory s = new bytes(40);
for (uint i = 0; i < 20; i++) {
bytes1 b = bytes1(uint8(uint(uint160(x)) / (2**(8*(19 - i)))));
bytes1 hi = bytes1(uint8(b) / 16);
bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
s[2*i] = char(hi);
s[2*i+1] = char(lo);
}
return string(s);
}
function char(bytes1 b) internal pure returns (bytes1) {
if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
else return bytes1(uint8(b) + 0x57);
}
function base64Encode(bytes memory data) internal pure returns (string memory) {
// Base64 編碼實(shí)現(xiàn)
// 這里簡(jiǎn)化處理,實(shí)際項(xiàng)目中應(yīng)使用完整的 Base64 編碼
return "base64encodeddata";
}
}
這些概念涵蓋了 Solidity 開發(fā)的核心知識(shí)點(diǎn),理解它們對(duì)于編寫高效、安全的智能合約至關(guān)重要。

浙公網(wǎng)安備 33010602011771號(hào)