diff --git a/FFFFourwood.md b/FFFFourwood.md index 91bb496f..dc3c288b 100644 --- a/FFFFourwood.md +++ b/FFFFourwood.md @@ -393,6 +393,31 @@ IERC721函数 - `setApprovalForAll`:将自己持有的NFT批量授权给某个地址。 - `isApprovedForAll`:查询某地址的NFT是否已批量授权给另一个地址。 - `safeTransferFrom`:带有额外`data`参数的安全转账函数。 + +### 2024.09.30 + +Error +`error` 是 Solidity 0.8.4 版本新加的内容,方便且高效(省 gas)地向用户解释操作失败的原因,同时还可以在抛出异常的同时携带参数 + +Require +`require` 命令是 Solidity 0.8 版本之前抛出异常的常用方法,目前很多主流合约仍然还在使用它。它很好用,唯一的缺点就是 gas 随着描述异常的字符串长度增加,比 `error` 命令要高。使用方法:`require(检查条件, "异常的描述")`,当检查条件不成立的时候,就会抛出异常。 + +Assert +`assert` 命令一般用于程序员写程序 debug,因为它不能解释抛出异常的原因(比 `require` 少个字符串)。它的用法很简单,`assert(检查条件)`,当检查条件不成立的时候,就会抛出异常。 + + +三种方法的 gas 比较 +我们比较一下三种抛出异常的 gas 消耗,通过 remix 控制台的 Debug 按钮,能查到每次函数调用的 gas 消耗分别如下:(使用 0.8.17 版本编译) + +- `error` 方法 gas 消耗:24457 (加入参数后 gas 消耗:24660) +- `require` 方法 gas 消耗:24755 +- `assert` 方法 gas 消耗:24473 + +我们可以看到,`error` 方法 gas 最少,其次是 `assert`,`require` 方法消耗 gas 最多!因此,`error` 既可以告知用户抛出异常的原因,又能省 gas + +备注: Solidity 0.8.0 之前的版本,`assert` 抛出的是一个 panic exception,会把剩余的 gas 全部消耗,不会返还。更多细节见官方文档。 + + diff --git a/Yuchen.md b/Yuchen.md index 11821263..1c9815be 100644 --- a/Yuchen.md +++ b/Yuchen.md @@ -667,6 +667,123 @@ abstract contract InsertionSort{ function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); } ``` +### 2024.09.30 + +#### 接口(interface) +類似抽象合約,但不實現任何功能。 +* 規則: + 1. 不能包含狀態變量 + 2. 不能包含構造函數 + 3. 不能繼承接口以外的其他合約 + 4. 所有函數都必須是external且不能有函數體 + 5. 繼承 interface 的非抽象合約必須實現 interface 定義的所有功能 + +interface 是智能合約的骨架,定義了合約的功能及如何觸發,若合約實現了某種接口,ex.`ERC20`、`ERC721`,其他`Dapps`和合約就知道該如何與此智能合約交互。 +* 作用: + 1. 標準化交互: + 如果某個智能合約實現了一個通用的接口(如 ERC20 或 ERC721),那麼其他合約或應用程序就能根據這個接口來與其互動,有助於跨平台、跨合約之間的兼容性。 + 2. 定義合約中的功能: + 接口會定義每個函數的名稱和參數類型,以及這些函數如何被調用,這相當於描述了合約的公共 API。 + 3. 定義每個函數哈希(`bytes4`選擇器)、函數簽名(`函數名(每個參數類型)`),以通過對函數的簽名進行哈希後得到獨特的4字節哈希。 + ```Solidity + keccak256("transfer(address,uint256)").slice(0, 4) + ``` + 4. 接口 ID: + 接口 ID 是一個唯一的標識符,用來表示某個合約是否實現了某個接口。 + + +```Solidity +interface IERC721 is IERC165 { + 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); + + function balanceOf(address owner) external view returns (uint256 balance); + + function ownerOf(uint256 tokenId) external view returns (address owner); + + 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 operator); + + function setApprovalForAll(address operator, bool _approved) external; + + function isApprovedForAll(address owner, address operator) external view returns (bool); + + function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data) external; +} +``` + +#### 什麼時候使用interface? +若知道一個合約實現了`IERC721`,不需要知道他的具體程式實現就可以與之交互。 +ex. +```Solidity +contract interactBAYC { + // 利用BAYC地址创建接口合约变量(ETH主网) + IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D); + + // 通过接口调用BAYC的balanceOf()查询持仓量 + function balanceOfBAYC(address owner) external view returns (uint256 balance){ + return BAYC.balanceOf(owner); + } + + // 通过接口调用BAYC的safeTransferFrom()安全转账 + function safeTransferFromBAYC(address from, address to, uint256 tokenId) external{ + BAYC.safeTransferFrom(from, to, tokenId); + } +} +``` + +#### 異常 +異常命令可以幫助尋找錯誤。 +* `error`:可以在`contract`之外拋出異常,高效且方便的向用戶解釋操作失敗的原因,且在拋出異常時可攜帶參數。 +ex.定義`TransferNotOwner`異常,當用戶不是貨幣`owner`時轉帳會拋出錯誤。 + ```Solidity + error TransferNotOwner(); // 自定义error + + // 攜帶參數的異常,以提示轉帳的帳戶地址 + error TransferNotOwner(address sender); // 自定义的带参数的error + ``` + `error`需搭配`revert`命令使用: + 當用戶不是貨幣`owner`時轉帳會拋出錯誤,否則成功轉帳。 + ```Solidity + function transferOwner1(uint256 tokenId, address newOwner) public { + if(_owners[tokenId] != msg.sender){ + revert TransferNotOwner(); + // revert TransferNotOwner(msg.sender); + } + _owners[tokenId] = newOwner; + } + ``` +* `require`:消耗的gas比`error`高,且會隨著描述異常的字符串長度增加,gas也隨之增加。 + * 使用方法:`require(檢查條件,"異常的描述")`,檢查條件不成立時,拋出異常。 + ```Solidity + function transferOwner2(uint256 tokenId, address newOwner) public { + require(_owners[tokenId] == msg.sender, "Transfer Not Owner"); + _owners[tokenId] = newOwner; + } + ``` +* `assert`:不能解釋拋出異常的原因(相較`require`少了字符串),但仍會在檢查條件不成立時拋出異常。 + ```Solidity + function transferOwner3(uint256 tokenId, address newOwner) public { + assert(_owners[tokenId] == msg.sender); + _owners[tokenId] = newOwner; + } + ``` +#### 驗證 +輸入任意數字、非0地址,呼叫以各種語法寫的異常訊息。 + + + + +* `error`方法`gas`消耗:24457(加入參數後`gas`消耗:24660) +* `require`方法`gas`消耗:24755 +* `assert`方法`gas`消耗:24473 +`error`方法`gas`消耗最少,`require`方法消耗最多,因此在求最小`gas`消耗下可以多加使用`error`。 diff --git a/jaxrunboo.md b/jaxrunboo.md index 1570bc9e..9ecb393e 100644 --- a/jaxrunboo.md +++ b/jaxrunboo.md @@ -398,4 +398,34 @@ function getString2(uint256 _number) public pure returns(string memory){ ### +### 2024.09.29 + +#### 导入 + +关键词 import + +导入方式: + +1. 通过文件的相对位置导入 +2. 通过全局符号,导入合约特定的全局符号 +3. 通过网址引入 +4. 通过npm的目录导入 + +```solidity + +//1 +import './3_Ballot.sol'; +//2 +import {Ballot} from './3_Ballot.sol'; +//3 +import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; +//4 +import '@openzeppelin/contracts/access/Ownable.sol'; + +``` + +接下来就可以很简单的像使用库一样,通过using for语句或者直接静态函数引用的方式来使用。 + +### +