---
title: 'Solidity WTF 102 17 ~ 21 單元'
lang: zh-tw
---
Solidity WTF 102 17 ~ 21 單元
===
:::info
:date: 2024/10/02
:::
[TOC]
# 課程學習
## 庫合約
目的是提升程式碼的複用性和減少`gas`而存在。
有以下規則:
1. 不能存在状态变量
1. 不能够继承或被继承
1. 不能接收以太币
1. 不可以被销毁
使用法方如下:
```xml=
<!-- 這邊代表會Strings庫會自動添加uint256,意思就是可以直接把聲明為uint256的變數,接上Strings庫裡的function名稱使用。 -->
using Strings for uint256;
<!-- function中使用到uint256聲明 -->
function getString1(uint256 _number) public pure returns(string memory){
return _number.toHexString();
}
```
```xml=
<!-- 也可以直接調用庫名稱 -->
function getString2(uint256 _number) public pure returns(string memory){
return Strings.toHexString(_number);
}
```
:::info
:bulb:庫合約重點是能夠著重在重複性上,並且使用安全係數較告的庫合約,能夠有效減少自行開發時間。
:::
## Import
下列是`import`方法:
```xml=
<!-- 直接使用相對路徑,又稱全局導入 -->
import './Yeye.sol';
<!-- 使用網址import -->
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol';
<!-- 使用npm目錄導入 -->
import '@openzeppelin/contracts/access/Ownable.sol';
<!-- 這種方法叫做命名導入,他可以從引入的合約中import自己想要的合約 -->
import {Yeye} from './Yeye.sol';
<!-- 別名導入YeyeTest是我指定的名稱,那我就可以使用這名稱,來呼叫Yeye合約內部 -->
import * as YeyeTest from './Yeye.sol'
```
## 接收ETH
有兩種`特殊函數`,用來當我`接收ETH`時會`自動觸發`的函數,`receive()`和`fallback()`。
:::warning
0.6版本前,只有`fallback`
:::
### 接收方式
通常有可能使用`transfer`或是`send`來`發送ETH`,因為這兩者的`gas limit is 2300`,所以如果`receive or fallback`中函數寫得太過於複雜,則會導致噴錯,因為`gas`超過(`Out of Gas`)。
```xml=
event Received(address Sender, uint Value);
event fallbackCalled(address Sender, uint Value, bytes Data);
<!-- 下面是兩種特殊函數,接收ETH會自動觸發 -->
receive() external payable {
emit Received(msg.sender, msg.value);
}
// fallback
fallback() external payable{
emit fallbackCalled(msg.sender, msg.value, msg.data);
}
```
### receive和fallback區別
這邊有一個樹狀圖,他會依照下列方式判別應該使用`receive()`和`fallback()`
```xml=
触发fallback() 还是 receive()?
接收ETH
|
msg.data是空?
/ \
是 否
/ \
receive()存在? fallback()
/ \
是 否
/ \
receive() fallback()
```
:::warning
這邊要注意,如果當下應該使用到的特殊函數若沒有實現(被寫出),否則會噴錯。
:::
## 發送ETH
有三種發送ETH的方法,`transfer()`、`send()`、`call()`,其中`call()`是被鼓勵的方法,因為沒有`gas`限制,可以在此或是`接收ETH`特殊函數中,寫複雜邏輯也不會因為`out of gas`而噴錯。
### 實現方法
> 個人認為,課程內容提到算滿完整,也沒有特別的坑,所以直接引入課程中的文案。
#### transfer
* 用法是接收方地址`.transfer(发送ETH数额)`。
* `transfer()`的`gas`限制是`2300`,足够用于转账,但对方合约的`fallback()`或`receive()`函数不能实现太复杂的逻辑。
* `transfer()`如果转账失败,会自动`revert`(回滚交易)。
```javascript=
function transferETH(address payable _to, uint256 amount) external payable{
_to.transfer(amount);
}
```
#### send
* 用法是接收方地址`.send(发送ETH数额)`。
* `send()`的`gas`限制是`2300`,足够用于转账,但对方合约的`fallback()`或`receive()`函数不能实现太复杂的逻辑。
* `send()`如果转账失败,不会`revert`。
* `send()`的返回值是`bool`,代表着转账成功或失败,需要额外代码处理一下。
```javascript=
error SendFailed(); // 用send发送ETH失败error
// send()发送ETH
function sendETH(address payable _to, uint256 amount) external payable{
// 处理下send的返回值,如果失败,revert交易并发送error
bool success = _to.send(amount);
if(!success){
revert SendFailed();
}
}
```
#### call
* 用法是接收方地址`.call{value: 发送ETH数额}("")`。
* `call()`没有`gas`限制,可以支持对方合约`fallback()`或`receive()`函数实现复杂逻辑。
* `call()`如果转账失败,不会`revert`。
* `call()`的返回值是`(bool, bytes)`,其中`bool`代表着转账成功或失败,需要额外代码处理一下。
```javascript=
error CallFailed(); // 用call发送ETH失败error
// call()发送ETH
function callETH(address payable _to, uint256 amount) external payable{
// 处理下call的返回值,如果失败,revert交易并发送error
(bool success,) = _to.call{value: amount}("");
if(!success){
revert CallFailed();
}
}
```
>這邊要注意`transfer`發送失敗會自動`revert`交易,`send`不會
:::info
:bulb: 若要選擇使用順序,則會是`call` -> `transfer` -> `send`
:::
## 調用其他合約
### 調用已部屬合約
假設已經完成一個合約如下
```javascript=
contract OtherContract {
uint256 private _x = 0; // 状态变量_x
// 收到eth的事件,记录amount和gas
event Log(uint amount, uint gas);
// 返回合约ETH余额
function getBalance() view public returns(uint) {
return address(this).balance;
}
// 可以调整状态变量_x的函数,并且可以往合约转ETH (payable)
function setX(uint256 x) external payable{
_x = x;
// 如果转入ETH,则释放Log事件
if(msg.value > 0){
emit Log(msg.value, gasleft());
}
}
// 读取_x
function getX() external view returns(uint x){
x = _x;
}
}
```
我想要使用上面已部屬合約,那我可以這樣調用
```javascript=
// 传入合约地址
function callSetX(address _Address, uint256 x) external{
OtherContract(_Address).setX(x);
}
// 传入合约变量
function callGetX(OtherContract _Address) external view returns(uint x){
x = _Address.getX();
}
// 创建合约变量
function callGetX2(address _Address) external view returns(uint x){
OtherContract oc = OtherContract(_Address);
x = oc.getX();
}
// 调用合约并发送ETH
function setXTransferETH(address otherContract, uint256 x) payable external{
OtherContract(otherContract).setX{value: msg.value}(x);
}
```
## 重點
我認為這邊有一個重點是在調用合約的部分,我直接拿測試題目來說明,我認為當初自己有點不太明白。
```javascript=
我有一個合約與接口如下
//OtherContract 合约如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
interface IOtherContract {
function getBalance() external returns(uint);
function setX(uint256 x) external payable;
function getX() external view returns(uint x);
}
contract OtherContract is IOtherContract{
uint256 private _x = 0;
event Log(uint amount, uint gas);
function getBalance() external view override returns(uint) {
return address(this).balance;
}
function setX(uint256 x) external override payable{
_x = x;
if(msg.value > 0){
emit Log(msg.value, gasleft());
}
}
function getX() external view override returns(uint x){
x = _x;
}
}
```
並且我在另一個合約想要調用,於是我這樣聲明
```javascript=
(1) OtherContract other = OtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138)
(2) IOtherContract other = IOtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138)
```
當下如果都部屬,其實會是正確的,但只部屬IOtherContract的情況下,(2)可能會是錯誤的,因為他沒有實現結果。