Solidity学习笔记-2

news/2024/11/30 0:59:53/文章来源:https://www.cnblogs.com/WZM1230/p/18577848

16.函数重载

16_01.重载

函数重载(overloading):即函数名字相同,但输入的参数类型不同的函数可以同时存在;(被视为是不同的函数)

Solidity不允许修饰器modifier重载;

重载的函数经过编译之后,由于不同的参数类型,都变成了不同的函数选择器(selector,29节有介绍);

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract A{// 无传入参数,输出"No parameter"function saySomething() public pure returns (string memory) {return "No parameter";}// 传入string,输出stringfunction saySomething(string memory str) public pure returns (string memory){return str;}
}
image-20241117163811702

16_02.实参匹配

调用重载函数时,会把输入的实际数据函数参数的类型进行匹配,若出现多个匹配的重载函数,会报错;

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract A{uint256 data = 0;// 传入参数是uint8function Add(uint8 num) public {data += num;}// 传入参数是uint256function Add(uint256 num) public {data += num;}function callAdd() public pure  returns (string memory){// 50即可用是uint8,也可以是uint256// 因此编译会报错Add(50);return "call Add function sucess";}
}

单独将Add两个函数编译是不会报错的:

image-20241117164944148

但是调用它们其中一个的时候,编译会报错:

image-20241117165110394

17.库合约

同其他语言里面的库函数,在Solidity中还有个重要作用就是能够减少gas

和普通合约的区别:

  1. 不能有状态变量
  2. 不能够继承或被继承
  3. 不能接收以太币
  4. 不可以被销毁

库合约中的函数若被设置为publicexternal,则在调用函数时会触发一次delegatecall

若被设置为internal,则不会触发;

若被设置为private,由于是私人的,只能库合约内部自己访问;

常用的一些库合约:

  1. Strings:将uint256转为string
  2. Address:判断某个地址是否为合约地址;
  3. Create2:更安全的使用Create2 EVM opcode
  4. Arrays:跟数组相关的库合约;

17_01.Strings库合约

此库合约是将uint256类型转换为相应的string类型的代码库,样例代码:

library Strings {bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";/*** @dev Converts a `uint256` to its ASCII `string` decimal representation.*/function toString(uint256 value) public pure returns (string memory) {// Inspired by OraclizeAPI's implementation - MIT licence// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.solif (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 + uint256(value % 10)));value /= 10;}return string(buffer);}/*** @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.*/function toHexString(uint256 value) public pure returns (string memory) {if (value == 0) {return "0x00";}uint256 temp = value;uint256 length = 0;while (temp != 0) {length++;temp >>= 8;}return toHexString(value, length);}/*** @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.*/function toHexString(uint256 value, uint256 length) public pure returns (string memory) {bytes memory buffer = new bytes(2 * length + 2);buffer[0] = "0";buffer[1] = "x";for (uint256 i = 2 * length + 1; i > 1; --i) {buffer[i] = _HEX_SYMBOLS[value & 0xf];value >>= 4;}require(value == 0, "Strings: hex length insufficient");return string(buffer);}
}

主要包含两个函数:

  1. toString():将uint256转换为string
  2. toHexString():将uint256转换为hex,再转换为string

17_02.使用库合约

有两种使用的方式;

  1. 使用using A for B

为类型B添加库合约A;添加完后,B类型变量的成员便自动添加了库A中的函数,可以直接调用;

调用时,这个变量会被当作第一个参数传递给函数;

  1. 通过库合约名称来直接调用函数;

比如:Strings.toString(xxx);

示例:

contract A{// 使用using A for Busing Strings for uint256;function getString_1(uint256 num) public pure returns (string memory){return num.toString();}// 通过库合约名来调用function getString_2(uint256 num) public pure returns (string memory){return Strings.toHexString(num);}
}
image-20241117173523142

18.Import

import可以在一个文件中引用另一个文件的内容,提高代码的可重用性和组织性;

  1. 通过文件的相对位置可以引用:import './xxx.sol';
  2. 通过源文件网址导入网上的合约全局符号;import 'https://xxxxx/xxx.sol';
  3. 通过npm的目录导入:import '@openzeppelin/contracts/access/Ownable.sol';
  4. 通过指定全局符号导入合约特定的全局符号:import {XXX} from './xxx.sol';
// ---------------------------Demo.sol----------------------------------------
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract B{function sayHello() public pure returns (string memory){return "Hello!";}
}// ---------------------------test.sol----------------------------------------
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;// 通过文件相对位置import
import './Demo.sol';
// 通过`全局符号`导入特定的合约
// 'B'是Demo.sol中合约的名称
import {B} from './Demo.sol';
// 通过网址引用
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol';
// 引用OpenZeppelin合约
import '@openzeppelin/contracts/access/Ownable.sol';contract A {// 成功导入Address库using Address for address;// 声明Demo.sol中的合约变量// 要使用合约名称B b = new B();// 调用引入的Demo.sol中合约B的函数function callImport() public view {b.sayHello();}
}
image-20241118212707032

19.接收ETH

Solidity支持两种特殊的回调函数:receive()fallback()

主要在两种情况使用:

  1. 接收ETH
  2. 处理合约中不存在的函数调用(代理合约proxy contract

在0.6.x版本之前,语法上只有fallback()函数,用来接收用户发送的ETH以及在被调用函数签名没有匹配到时调用;

0.6版本之后,Solidity才将其拆分为receive()fallback()

19_01.接收ETH函数-receive

receive函数是在合约收到ETH转账时会被调用的函数,一个合约最多只能有一个;

声明的方式和一般函数不一样,不需要function关键字,且不能有任何参数不能返回任何值,必须包含externalpayable

receive函数最好不要执行太多逻辑,因为对方调用sendtransfer方法发送ETH的话,gas会被限制在2300,receive太复杂可能会触发Out of gas报错;

call就可以自定义gas执行更复杂的逻辑。

示例(在receive中发送一个事件):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract A {event Received(address Sender, uint Value);receive() external payable { emit Received(msg.sender, msg.value);}
}

在老版本中,有些恶意合约,会在receive函数中嵌入恶意消耗gas的内容或者使得执行故意失败的代码,导致一些包含退款和转账逻辑的合约不能正常工作;

19_02.回退函数-fallback

fallback函数会在调用合约中不存在的函数时被触发;

可用于接收ETH,也可用于代理合约(proxy contract);

receive函数一样,不需要function关键字,但必须包含external,一般也会使用payable来修饰;

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract A {event fallbackCalled(address Sender, uint Value, bytes Data);fallback() external payable { emit fallbackCalled(msg.sender, msg.value, msg.data);}
}

19_03.两者区别

首先,它们俩都能够接收ETH;,它们触发的规则如下:

​ / 是 --> receive()

​ / 是 --> receive()是否存在

接收ETH --> msg.data是否为空 \ 否 --> fallback()

​ \ 否 --> fallback()

只有msg.data为空且receive()存在时,才会使用receive()

两者都不存在时,向合约发送ETH会报错;(但仍然可以通过带有payable的函数向合约发送ETH)

receive函数时,转账时data为空:

image-20241118222729536

转账时data不为空:

image-20241118223045330

20.发送ETH

Solidity有三种方式向其他合约发送ETH:transfer()send()call(),其中call推荐使用;

首先先部署一个接收ETH的合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract ReceiveETH {// 收到ETH的事件// 记录amount和gasevent Log(uint amount, uint gas);// 接收ETH时触发的方法receive() external payable { emit Log(msg.value, gasleft());}// 返回ETH余额function getBalance() public view returns (uint){return address(this).balance;}
}

部署后运行getBalance(),发现此时的余额为0:

image-20241119225817417

20_01.transfer

用法:接收方地址.transfer(发送的ETH数额)

  • transfer的gas限制是2300,足够用于转账,前提是接收方的fallbackreceive不能太复杂;
  • transfer如果转账失败,会自动revert交易(回滚交易);

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract Transfer_test{function transferETH(address payable to, uint256 amount) external payable {// to是接收方to.transfer(amount);}
}

转账失败时:

image-20241119231200736

转账成功时(多余的转账会被返回到发送方合约,并非附带ETH的钱包):

image-20241119232141984

20_02.send

用法:接收方地址.send(发送的ETH数额)

  • send的gas限制同样是2300;
  • send如果转账失败,不会revert
  • send的返回值是bool,代表的是转账成功或者失败,需要额外的代码来处理;

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract Send_test{// 发送ETH失败的错误error SendFailed();// 发送ETHfunction sendETH(address payable to, uint256 amount) external payable {bool success = to.send(amount);if (!success){// 失败就revert错误revert SendFailed();}}
}

转账失败:

image-20241119233240303

转账成功(同样多余的ETH退回到发送方合约):

image-20241119233450913

20_03.call

用法:接收方地址.call{value:发送到ETH数额}("")

  • call没有gas限制,可以支持对方合约fallbackreceive实现复杂逻辑;
  • call如果转账失败,不会revert
  • call对返回值是bool,和send一样需要额外代码处理;

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract Call_test{// 发送ETH失败的错误error CallFailed();// 发送ETHfunction callETH(address payable to, uint256 amount) external payable {bool success = to.call{value:amount}("");if (!success){// 失败就revert错误revert CallFailed();}}
}

转账失败时:

image-20241119234226065

转账成功时(同样多余的ETH退回到发送方合约):

image-20241119234359832

21.调用其他合约

TestContract合约,目的是被其他合约所调用:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract TestContract {// 设置私有变量uint256 private x = 0;// 交易的事件,记录amount和gasevent Log(uint amount, uint gas);// 得到合约账户余额function getBalance() public view returns (uint){return address(this).balance;}// 设置合约中私有变量值// 同时可以向其中转账function setX(uint256 num) external payable {x = num;if(msg.value > 0){emit Log(msg.value, gasleft());}}// 获得私有变量的值function getX() external view returns (uint256){return x;}
}

部署,并得到合约地址:

image-20241120002354682

调用合约的代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// 由于在不同的文件,所以先导入
import './test.sol';contract CallContract{// 方式一// 合约名(合约地址).func()function callSetX(address contract_address, uint256 x) external{TestContract(contract_address).setX(x);}// 方式二// 合约地址.func()function callGetX(TestContract contract_address) external view returns(uint x){x = contract_address.getX();}// 方式三// 创建合约对象的方式,然后调用function callGetX_2(address contract_address) external view returns(uint x){TestContract tc = TestContract(contract_address);x = tc.getX();}// 调用并转账function setXTransferETH(address contract_address, uint256 x) payable external{TestContract(contract_address).setX{value: msg.value}(x);}
}

21_01.调用方式一

可以在函数中传入合约地址,生成目标合约的引用,然后再调用函数;

  • 用法:合约名(合约地址).func(参数)

  • 合约名和接口都必须保持一致(TestContractsetX());

// 方式一
// 合约名(合约地址).func()
function callSetX(address contract_address, uint256 x) external{TestContract(contract_address).setX(x);
}
image-20241120003213411

21_02.调用方式二

参考方式一中,将address类型换为目标合约名即可;

注意:TestContract contract_address的底层还是address类型,生成的ABI中,调用callGetX时传入的参数都是address类型的;

  • 用法:
    • 参数->合约名 合约地址
    • 函数内部->合约地址.func(参数)
// 方式二
// 合约地址.func()
function callGetX(TestContract contract_address) external view returns(uint x){x = contract_address.getX();
}
image-20241120003946647

21_03.调用方式三

通过创建合约(对象)的方式;

用法:合约名 变量名 = 合约名(地址);

// 方式三
// 创建合约对象的方式,然后调用
function callGetX_2(address contract_address) external view returns(uint x){TestContract tc = TestContract(contract_address);x = tc.getX();
}
image-20241120004228115

21_04.调用并转账

如果目标函数是payable的,那么便可以向其转账;

用法:合约名(合约地址).func{value:xxx}(参数);

// 调用并转账
function setXTransferETH(address contract_address, uint256 x) payable external{TestContract(contract_address).setX{value: msg.value}(x);
}
image-20241120004826076

22.Call

20_03call可以用来发送ETH,同时它还可以调用合约;

calladdress类型的低级成员函数,它用来与其他合约交互;

  • 返回值:(bool, bytes memory),分别对应call是否成功以及目标函数的返回值;
  • call是官方推荐的通过触发fallbackreceive函数发送ETH的方法;
  • 不推荐用call来调用另一个合约(因为当你调用一个不安全的合约时,主动权便不在你的手上;推荐声明合约变量后调用函数21_03);
  • 当我们不知道对方合约的源代码或者ABI,就没法生成合约变量;此时,仍然可以通过call调用对方合约的函数;

22_01.使用规则

用法:目标合约地址.call(字节码),可以在不知道源代码或ABI的情况下调用;

  • 字节码:利用结构化编码函数来获得 --> abi.encodeWithSignature("函数签名", 具体参数, 具体参数, ...)
    • 函数签名:是函数名(参数类型,参数类型,...)

示例:abi.encodeWithSignature("f(uint256,address)",x,addr)

在调用合约的同时,call还能知道交易发送的ETH和gas:

使用方法:目标合约地址.call{value:ETH数额, gas:gas数额}(字节码),就是在参数前加了大括号,里面填上发送的数额;

22_02.通过call调用目标合约

目标合约(还是和之前一样):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract TestContract {// 设置私有变量uint256 private x = 0;// 交易的事件,记录amount和gasevent Log(uint amount, uint gas);// 得到合约账户余额function getBalance() public view returns (uint){return address(this).balance;}// 设置合约中私有变量值// 同时可以向其中转账function setX(uint256 num) external payable {x = num;if(msg.value > 0){emit Log(msg.value, gasleft());}}// 获得私有变量的值function getX() external view returns (uint256){return x;}
}

调用setX(uint256 num)函数,有参数,但无返回值(data无内容),附带ETH发送过去:

image-20241121220329742

调用getX()函数,无参数,但有返回值(data有内容),不带ETH:

image-20241121221156903

调用一个不存在的函数:

  • 当没有fallback函数的情况下(会返回false):
image-20241121222026282
  • 当给目标合约添加一个fallback函数时,再调用它(会返回true):
fallback() external payable { }
image-20241121222709235

完整示例代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;// 没有导入test.sol
contract CallContract{// 定义的Response事件// 输出call返回的结果和dataevent Response(bool success, bytes data);function callSetX(address payable addr, uint256 x) public payable {// 调用setX函数// 同时可以发送ETH// {}中是发送的ETH数额// ()中是利用结构化编码函数获得的字节码(bool success, bytes memory data) = addr.call{value:msg.value}(abi.encodeWithSignature("setX(uint256)", x));emit Response(success, data);}function callGetX(address addr) external returns (uint256){// 调用getX函数// ()中是利用结构化编码函数获得的字节码(bool success, bytes memory data) = addr.call(abi.encodeWithSignature("getX()"));emit Response(success, data);// 返回data中的值(转为uint)return abi.decode(data, (uint256));}function callNonExist(address addr) external {// 调用一个不存在的函数(bool success, bytes memory data) = addr.call(abi.encodeWithSignature("xxx(address)"));emit Response(success, data);}
}

23.DelegateCall

delegatecall委托,和call差不多,同样是地址类型的低级成员函数;

23_01.什么是委托

当用户A通过合约Bcall合约C时:

  • 此时执行的是合约C上的函数;

  • 上下文(Context,可以理解为包含变量和状态的环境)也是合约C的:

    • msg.sender是合约B的地址

    • 若函数改变了一些状态变量,产生的效果会用在合约C的变量上;

image-20241121223728699

而当用户A通过合约Bdelegatecall合约C时:

  • 执行的是合约C上的函数;
  • 上下文仍然是合约B的:
    • msg.sender是合约A的地址;
    • 若函数改变了一些状态变量,产生的效果会用在合约B的变量上;
image-20241121223807749

也可以这么理解:

  1. 合约B的视角

我合约B"借用"了合约C的某一个函数的功能,来改变我自己这边的一些状态;

  1. 现实世界

用户A:投资者

合约B中的状态变量:资产

合约C中执行的函数:风险投资机构

投资者将他的资产交给一个风险投资机构来打理,此时执行的是风险投资机构,但改变的是投资者资产

23_02.使用规则

call类似:目标合约地址.delegatecall(字节码)

其中字节码仍是通过abi.encodeWithSignature()来获得的;

call不一样的是:delegatecall()在调用时,不能指定发送的ETH数额,但能指定gas数额;

注意:delegatecall()有安全隐患,使用时要保证当前合约和目标合约的状态变量存储结构相同,并且目标合约安全,不然会造成财产损失。

23_03.什么情况下用到委托

主要有两个应用场景:

  1. 代理合约(Proxy Contract

将智能合约的存储合约逻辑合约分开;

存储合约(代理合约(Proxy Contract))存储所有相关的变量,并且保存逻辑合约的地址;

逻辑合约(Logic Contract)中存储所有的函数,通过delegatecall执行;

当升级的时候,只需要将代理合约指向新的逻辑合约即可(以太坊官方开发文档中有提到)。

  1. EIP-2535 Diamonds(钻石)

钻石是一个支持构建可在生产中扩展的模块化智能合约系统的标准。钻石具有多个实施合约的代理合约。详细信息:钻石标准简介。

23_04.示例

用户A通过合约B委托调用合约C

被调用的合约C

两个状态变量和一个可以修改状态变量的函数:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;// 被调用的合约C
contract C {// 状态变量numuint public num;// 状态变量senderaddress public sender;// 设置状态变量num和sender的值function setVars(uint x) public payable {num = x;sender = msg.sender;}
}

发起调用的合约B

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;// 没有导入test.sol
contract B{// 必须与合约C的变量存储布局相同// 两个变量,顺序也必须一致uint public num;address public sender;// 通过call来调用SetVars函数// 预计只会改变合约C的变量值function callSetVars(address addr, uint x) external payable {(bool success, bytes memory data) = addr.call(abi.encodeWithSignature("setVars(uint256)", x));}// 通过delegatecall来调用SetVars函数// 预计只改变本合约(合约B)的变量值function delegatecallSetVars(address addr, uint x) external payable {(bool success, bytes memory data) = addr.delegatecall(abi.encodeWithSignature("setVars(uint256)", x));}
}

验证

状态变量的初始值:

image-20241121235657671

合约B中调用callSetVars函数,预计只会改变合约C中的变量值(num为更改后的值,sender为合约B的地址):

image-20241122000241525

在合约B中调用delegatecallSetVars函数,预计会改变合约B中的变量(num变为更改后的值,sender为钱包地址),合约C中的不变:

image-20241122000953665

24.在合约中创建新合约

以太坊上,外部账户EOA(钱包)可以创建智能合约;此外,智能合约也可以创建新的智能合约。

去中心化交易所Uniswap就是利用工厂合约(PairFactory)创建了无数个币对合约(Pair)

Uniswap V2核心合约中包含两个合约:

  1. UniswapV2Pair:币对合约,用于管理币对地址,流动性,买卖;
  2. UniswapV2Factory:工厂合约,用于创建新的币对合约,并管理币对地址;

24_01.Create

Create用法:ContractXXX xxx = new ContarctXXX{value:_value}(构造函数参数)

就和new对象一样,新new一个合约,并传入新合约构造函数所需要的参数,并且可以附带ETH(前提构造函数得是payable的);

极简Uniswap

Create来实现一个极简版的Uniswap(真正的Uniswap不是用这种方式实现的,是24_02中的方法):

币对合约(Pair)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;// 币对合约
// 包含3个状态变量
// 部署时将factory赋值
// 调用initToken时更新币对中两个代币的地址
contract Pair{// 工厂地址address public factory;// 代币0address public token0;// 代币1address public token1;// 构造函数,带有payable// 将消息的发送者赋值为factoryconstructor() payable {factory = msg.sender;}// 初始化代币0和代币1的地址function initToken(address _token0, address _token1) external {// 检测是否是factory调用的require(factory == msg.sender, "Not real factory use function");// 代币地址赋值token0 = _token0;token1 = _token1;}
}

工厂合约(PairFactory)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import './Pair.sol';// 工厂合约
// 一个映射将 代币 和 币对合约地址 建立联系
// 一个数组 保存 币对合约地址
// 利用Create方法创建新的合约
contract PairFactory {// 映射,address -> address -> addressmapping (address => mapping ( address => address )) public getPair;// 保存所有的Pair地址(币对合约地址)address[] public allPairs;// 创建新的币对合约地址function createPair(address token0, address token1) external returns (address pairAddr){// 利用Create方法创建新合约Pair pair = new Pair();// 调用新合约的initToken方法,并初始化里面的token0,token1pair.initToken(token0, token1);// 获得当前币对合约的地址pairAddr = address(pair);// 保存在数组中allPairs.push(pairAddr);// 建立映射// token0 -> token1 -> 币对合约地址getPair[token0][token1] = pairAddr;// token1 -> token0 -> 币对合约地址getPair[token1][token0] = pairAddr;}
}

利用下面两个地址作为参数调用createPair函数:

WBNB地址: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78
BSC链上的PEOPLE地址: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
image-20241126002026701 image-20241126002224011

24_02.Create2

上面可以看到Create方法创建的合约地址是完全不可预测的;

Create2方法使我们在部署智能合约之前就能预测合约的地址(Uniswap创建 Pair合约(币对合约)的方法就是这个)。

Create2方法的目的是为了让合约地址独立于未来事件,不管未来区块链上发生什么,都可以将合约部署在事先计算好的地址上。

Create原理

新地址 = hash(创建者地址, nonce)

无论是EOA创建还是智能合约创建,都是这个方法;

创建者地址部署的钱包地址或者合约地址

nonce,对于EOA是该地址发送的交易总数,对于合约账户是创建的合约总数,创建时的noncenonce+1

创建者的地址不会变,但是nonce会随着时间而改变,所以不好预测;

Create2原理

新地址 = hash("0xFF", 创建者地址, salt, initcode)

0xFF:一个常数,避免和Create冲突;

创建者地址:调用Create2的当前合约地址;

salt:一个由创建者指定的bytes32类型的值,主要目的是用来影响新创建的合约地址;

initcode:新合约的初始字节码(合约的Creation Code和构造函数参数);

Create2用法

ContractXXX xxx = new COntractXXX{salt:_salt, value:_value}(构造函数参数)

同样也是new,只不过多加入了个salt

极简Uniswap2

使用Create2来实现一个极简的Uniswap

币对合约(Pair)(和之前一样):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;// 币对合约
// 包含3个状态变量
// 部署时将factory赋值
// 调用initToken时更新币对中两个代币的地址
contract Pair{// 工厂地址address public factory;// 代币0address public token0;// 代币1address public token1;// 构造函数,带有payable// 将消息的发送者赋值为factoryconstructor() payable {factory = msg.sender;}// 初始化代币0和代币1的地址function initToken(address _token0, address _token1) external {// 检测是否是factory调用的require(factory == msg.sender, "Not real factory use function");// 代币地址赋值token0 = _token0;token1 = _token1;}
}

工厂合约(PairFactory)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import './Pair.sol';// 工厂合约
// 一个映射将 代币 和 币对合约地址 建立联系
// 一个数组 保存 币对合约地址
// 利用Create方法创建新的合约
contract PairFactoryV2 {// 映射,address -> address -> addressmapping (address => mapping ( address => address )) public getPair;// 保存所有的Pair地址(币对合约地址)address[] public allPairs;// 创建新的币对合约地址function createPairV2(address token0, address token1) external returns (address pairAddr){// 检测两个地址不同require(token0 != token1, "Identcial Address");// 将地址按照从小到大排序(address token_0, address token_1) = token0 < token1 ? (token0, token1) : (token1, token0);// 计算一个saltbytes32 salt = keccak256(abi.encodePacked(token_0, token_1));// 利用Create2方法创建新合约Pair pair = new Pair{salt: salt}();// 调用新合约的initToken方法,并初始化里面的token0,token1pair.initToken(token_0, token_1);// 获得当前币对合约的地址pairAddr = address(pair);// 保存在数组中allPairs.push(pairAddr);// 建立映射// token0 -> token1 -> 币对合约地址getPair[token0][token1] = pairAddr;// token1 -> token0 -> 币对合约地址getPair[token1][token0] = pairAddr;}// 预测地址function calcAddr(address token0, address token1) public view returns (address predictedAddr){// 检测两个地址不同require(token0 != token1, "Identcial Address");// 将地址按照从小到大排序(address token_0, address token_1) = token0 < token1 ? (token0, token1) : (token1, token0);// 计算一个saltbytes32 salt = keccak256(abi.encodePacked(token_0, token_1));// 计算地址predictedAddr = address(uint160(uint(// hashkeccak256(abi.encodePacked(// 四个参数bytes1(0xff),address(this),salt,keccak256(type(Pair).creationCode))))));}
}

若部署的合约的构造函数中需要有参数:

比如Pair pair new Pair{salt:salt}(address(this));

predictedAddr = address(uint160(uint(// hashkeccak256(abi.encodePacked(// 四个参数bytes1(0xff),address(this),salt,// 一起打包,并计算哈希keccak256(abi.encodePacked(type(Pair).creationCode, abi.encode(address(this)))))))
));

还是利用这两个地址:

WBNB地址: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78
BSC链上的PEOPLE地址: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c

事先计算:

image-20241126012744690

验证:

image-20241126012839407 image-20241126013109845

24_03.应用场景

  1. 交易所为新用户预留创建钱包合约的地址;
  2. 减少不必要的调用(知道新合约的地址后,无需再执行getPair的跨合约调用);

25.删除合约

25_01.selfdestruct

selfdestruct命令可被用来删除合约,并将该合约剩余的ETH转到指定地址;

它为了应对合约出错的极端情况而设计的,最早被命名为suicide,后面改为selfdestruct

v0.8.18版本中,它被标记为"不再建议使用",因为在一些情况下它会导致预期之外的合约语意,但由于目前还没有替代方案,只对开发者做了编译阶段的警告,相关内容:EIP-6049。

然而,在以太坊坎昆(Cancun)升级中,EIP-6780被纳入升级以实现对Verkle Tree更好的支持。该更新减少了SELFDESTRUCT操作码的功能。

根据提案描述,当前SELFDESTURCT仅会被用来将合约中的ETH转移到指定地址,而原先的删除功能只有在合约创建-自毁这两个操作处在同一笔交易时才能生效。

所以,目前来说:

  1. 现在的seldestrict仅会被用来将合约中的ETH转移到指定地址;
  2. 已经部署的合约无法被SELFDESTRUCT
  3. 如果要使用原先的SELFDESTRUCT功能,必须在同一笔交易中创建并自毁;

25_02.如何使用selfdeftruct

用法:selfdestruct(addr)

其中,addr是接收合约中剩余ETH的地址,并且addr地址不需要有receive()fallback()也能接收ETH。

25_03.升级前后功能对比

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;contract SelfDestructDemo{uint public value = 10;constructor() payable {}receive() external payable { }  // 升级之前应该能自毁// 升级之后只能转移ETHfunction SelfDestruct() external {selfdestruct(payable(msg.sender));}// 获取余额function getBalance() external view returns (uint balance){balance = address(this).balance;}
}

升级前:

合约中函数报错,并且合约中的ETH被转入指定地址;

image-20241127002142455 image-20241127002254054

升级后:

合约中的ETH被转入指定地址,但合约中的函数仍能使用;

image-20241127002604531

25_04.同笔交易实现创建-自毁

// SPDX-License-Identifier: MIT
// pragma solidity ^0.8.26;
pragma solidity ^0.8.4;// DeployDestructDemo合约(还是上一个)
import './Factory.sol';contract DeployDestructDemo{struct DemoResult{address addr;uint balance;uint value;}constructor() payable {}function getBalance() external view returns (uint balance){balance = address(this).balance;}// 演示创建-自毁function demo() public payable returns (DemoResult memory){// 创建一个新合约SelfDestructDemo sd = new SelfDestructDemo{value:msg.value}();// 给返回值赋值DemoResult memory res = DemoResult({addr:address(sd),balance:sd.getBalance(),value:sd.value()});// 新合约调用自销毁sd.SelfDestruct();return res;}
}
image-20241127004222773 image-20241127004548737 image-20241127004836011

26.ABI编码解码

ABI-(Application Binary Interface,应用二进制接口),是与以太坊智能合约交互的标准。

数据基于他们的类型编码,并且由于编码后不包含类型信息,解码时需要注明它们的类型;

编码abi.encodeabi.encodePackedabi.encodeWithSignatureabi.encodeWithSelector

解码abi.decode

26_01.abi编码

下面将这4个变量一起打包编码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract ABIEncode{uint256 x = 10;address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;string name = "0xAA";uint[2] array = [3, 4];function encode() public view returns (bytes memory res){res = abi.encodeXXX(x, addr, name, array);}
}

abi.encode(能和合约交互)

将给定参数利用ABI规则编码;

将每个参数填充为32字节的倍数的数据,并拼接在一起;

如果要和智能合约交互,需要使用它;

uint256 x = 10;
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
string name = "0xAA";
uint[2] array = [3, 4];
// 结果
//0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
/*
0x
000000000000000000000000000000000000000000000000000000000000000a(x)
0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4(addr)
00000000000000000000000000000000000000000000000000000000000000a0(array)
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000004
0000000000000000000000000000000000000000000000000000000000000004(string)
3078414100000000000000000000000000000000000000000000000000000000
*/

若将string变成很长:

uint256 x = 10;
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
string name = "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
uint[2] array = [3, 4];
// 结果
//0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000047307841414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414100000000000000000000000000000000000000000000000000
/*
0x
000000000000000000000000000000000000000000000000000000000000000a(x)
0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4(addr)
00000000000000000000000000000000000000000000000000000000000000a0(array)
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000004
0000000000000000000000000000000000000000000000000000000000000047(string)
3078414141414141414141414141414141414141414141414141414141414141
4141414141414141414141414141414141414141414141414141414141414141
4141414141414100000000000000000000000000000000000000000000000000
*/

abi.encodePacked(不能和合约交互)

将给定参数根据其所需要的最低空间编码,与abi.encode类似,但会省略很多0;

但不能与合约交互;

uint256 x = 10;
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
string name = "0xAA";
uint[2] array = [3, 4];
// 结果
//0x000000000000000000000000000000000000000000000000000000000000000a5b38da6a701c568545dcfcb03fcb875f56beddc43078414100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004
/*
0x
000000000000000000000000000000000000000000000000000000000000000a(x,因为是uint256)
5b38da6a701c568545dcfcb03fcb875f56beddc4(addr)
30784141(string)
00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004(array)
*/

abi.encodeWithSignature(调用其他合约时使用)

abi.encode类似,但是第一个参数是函数签名keccak哈希,编码时为4字节,等同于在前面加了个函数选择器

当调用其他函数的时候可以使用;

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract ABIEncode{uint256 x = 10;address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;string name = "0xAA";uint[2] array = [3, 4];function encode() public view returns (bytes memory res){res = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array);}
}// 结果
//0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
/*
0x
e87082f1(函数签名)
000000000000000000000000000000000000000000000000000000000000000a(x)
0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4(addr)
00000000000000000000000000000000000000000000000000000000000000a0(array)
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000004
0000000000000000000000000000000000000000000000000000000000000004(string)
3078414100000000000000000000000000000000000000000000000000000000
*/

abi.encodeWithSelector

abi.encodeWithSignature类似,只不过第一个参数时函数选择器,为函数签名Keccak哈希的前4个字节;

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract ABIEncode{uint256 x = 10;address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;string name = "0xAA";uint[2] array = [3, 4];function encode() public view returns (bytes memory res){res = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array);}
}// 结果
//0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
/*
0x
e87082f1(函数签名)
000000000000000000000000000000000000000000000000000000000000000a(x)
0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4(addr)
00000000000000000000000000000000000000000000000000000000000000a0(array)
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000004
0000000000000000000000000000000000000000000000000000000000000004(string)
3078414100000000000000000000000000000000000000000000000000000000
*/

26_02.abi解码

abi.decode

用于解码abi.encode生成的二进制编码,将它还原成原本的参数;

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract ABIEncode{function dncode(bytes memory data) public pure returns (uint x, address addr, string memory name, uint[2] memory array){(x, addr, name, array) = abi.decode(data, (uint, address, string, uint[2]));}
}
// 输入
// 0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
image-20241129224205133

27.选择器

27_01.calldata

当我们调用智能合约时,本质上是向目标合约发送了一段calldata,发送交易后,可以在详细信息的input中看到此次交易的calldata

image-20241129224706383

发送的calldata中前4个字节是函数选择器(selector)

// 上图中的calldata
// 0x012b48bf000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
/*
0x
012b48bf(函数选择器)
(因为bytes是动态的,所以会有下面这俩,静态的不会有,比如address,uint)
0000000000000000000000000000000000000000000000000000000000000020(偏移量,0x20 = 32,从这开始偏移32个字节)
00000000000000000000000000000000000000000000000000000000000000e0(参数长度,0xe0 = 7 * 32,正好对应上面)输入的参数
000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
*/

其实,calldata就是告诉智能合约,为要调用哪个函数,参数都是什么;

27_02.selector的生成

基础类型参数

基础类型的参数有:uint(uint8, ..., uint256)booladdress等;

bytes(keccak256("func_name(uint256,bool,...)"));

固定长度类型参数

固定长度类型的参数,比如:uint256[3]

bytes(keccak256("func_name(uint256[3])"));

可变长度类型参数

可变长度类型的参数,比如:address[]uint[]stringbytes等;

bytes(keccak256("func_name(bytes,string)"));

映射类型参数

映射类型的参数有:contractenumstruct等;

contract Demo{}	// 需要转化为address
struct User{	// 需要转化为tuple类型:(uint256,bytes)uint256 uid;bytes name;
}
enum School {SCHOOL1, SCHOOL2}	// 需要转化为uint8
mapping(address => uint) public balance;	// 直接转化为address(第一个类型),因为mapping类型不能直接作为参数
bytes(keccak256("func_name(address,(uint256,bytes),uint256[],uint8),address"))

27_03.使用selector

address(this).call(abi.encodeWithSelector(0x12345678函数签名, 参数, 参数, ...));

28.Try Catch

28_01.用法

基础用法

try func_name(){// call成功的情况下
} catch{// call失败的情况下
}

调用的函数有返回值

必须这么使用(需要加上returns),同时可以使用返回的变量:

try func_name() returns (address addr, uint x){// call成功的情况下// 可以使用返回的变量
} catch{// call失败的情况下
}

捕捉特殊的异常原因

try func_name() returns (address addr, uint x){// call成功的情况下// 可以使用返回的变量
} catch Error(string memory reason){// 捕捉revert("xxxx")// 捕捉require(false, "xxxx")
} catch Panic(uint errorCode){// 捕捉Panic导致的错误// 例如assert失败、溢出、除零、数组访问越界等
} catch (bytes memory lowLevelData){// 如果发生了revert且上面2个异常匹配失败,会进入这个分支// 例如revert()、require(false)、revert(自定义的error)
}

28_02.示例

调用合约(合约创建成功,但函数调用错误)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract OnlyEven{constructor(uint a){// 当a = 0时,require会抛出异常require(a != 0, "invalid number");// 当a = 1时,assert会抛出异常assert(a != 1);}function onlyEven(uint b) external pure returns(bool success){// 当b为奇数时,require抛出异常require(b % 2 == 0, "Odd number");success = true;}
}contract TryCatch{// 成功事件event SuccessEvent();// 抛出异常时的两个事件// 对应require和revertevent CatchEvent(string message);// 对应assertevent CatchByte(bytes data);// 合约状态变量OnlyEven oe;// 构造函数constructor(){// 赋值为2,应该不会抛出异常oe = new OnlyEven(2);}function exec(uint amount) external returns (bool success){try oe.onlyEven(amount) returns (bool _success){// 成功,返回Trueemit SuccessEvent();return _success;} catch Error(string memory reason){// 失败,捕捉require(false, error_string)// 比如此处输入的是奇数,应该返回"Odd number"emit CatchEvent(reason);}}
}

成功:

image-20241130003918089

失败:

image-20241130003955781

调用合约(合约创建失败)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract OnlyEven{constructor(uint a){// 当a = 0时,require会抛出异常require(a != 0, "invalid number");// 当a = 1时,assert会抛出异常assert(a != 1);}function onlyEven(uint b) external pure returns(bool success){// 当b为奇数时,require抛出异常require(b % 2 == 0, "Odd number");success = true;}
}contract TryCatch{// 成功事件event SuccessEvent();// 抛出异常时的两个事件// 对应require和revertevent CatchEvent(string message);// 对应assertevent CatchByte(bytes data);// exec(0) --> 失败,释放CatchEvent// exec(1) --> 失败,释放CatchByte// exec(2) --> 成功,释放SuccessEventfunction exec(uint num) external returns (bool success){try new OnlyEven(num) returns (OnlyEven oe){emit SuccessEvent();success = oe.onlyEven(num);} catch Error(string memory reason){// 捕捉失败的revert()和require()emit CatchEvent(reason);} catch (bytes memory reason){// 捕捉失败的assert()emit CatchByte(reason);}}
}

exec(0) --> 失败,释放CatchEvent:

image-20241130004935488

exec(1) --> 失败,释放CatchByte:

image-20241130005020918

exec(2) --> 成功,释放SuccessEvent:

image-20241130005053655

参考:https://github.com/AmazingAng/WTF-Solidity

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/843750.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gitlab分支保护

作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。 目录1.开发者无法推送代码到master分支案例1.1 查看jasonyin2020角色1.2 查看jasonyin2020有权限的相关项目1.3 使用jasonyin2020用户拉取meta-data项目所有分支到本地1.4 使用jasonyin2020用户推送数据到dev…

Python绘制南北极地图

import matplotlib.pyplot as plt import numpy as np import cartopy.crs as ccrs import cartopy.feature as cfeature import matplotlib.path as mpathdef plot_polar_map(dmeridian: float = 30.0, # 经度网格线间隔dparallel: float = 15.0): # 纬度网格线间隔"&q…

开源 - Ideal库 - Excel帮助类,TableHelper实现(二)

本文实现对象集合与DataTable的转换,包括按列名数组、列名-类型键值对、类创建表格的方法,并对类进行类型校验和反射获取属性信息,单元测试代码已上传至代码库。书接上回,我们今天开始实现对象集合与DataTable的相互转换。01、接口设计 上文中已经详细讲解了整体设计思路以…

大语言模型中的MoE

1.概述 MoE代表“混合专家模型”(Mixture of Experts),这是一种架构设计,通过将不同的子模型(即专家)结合起来进行任务处理。与传统的模型相比,MoE结构能够动态地选择并激活其中一部分专家,从而显著提升模型的效率和性能。尤其在计算和参数规模上,MoE架构能够在保持较…

ssh控制远程设备连接wifi

查看wifisudo nmcli device wifi连接wifisudo nmcli device wifi connect MERCURY_21B8 password 密码提示连接成功Device wlan0 successfully activated with 5c7d67b4-145d-4848-83cf-3b51795503fb

Kubeapps可视化管理Helm Chart包

目录一.基于helm部署kubeapps1.kubeapps介绍2.添加kubeapps的repo3.搜索kubeapps4.下载指定的kubeapps版本5.基于helm部署kubeapps6.访问kubeapps的WebUI二.通过kubeapps部署应用案例1 通过kubeapps查看已经部署的Chart2.通过kubeapps搜索Chart信息 一.基于helm部署kubeapps 1.…

剪映设置封面时候如何防止文字遮挡人物 All In One

剪映设置封面时候如何防止文字遮挡人物 All In One PS 图层叠加 / 前景后景 / 透明度 照片 人物抠图 + 画中画剪映设置封面时候如何防止文字遮挡人物 All In One 原理分析 PS 图层叠加 / 前景后景 / 透明度 照片 人物抠图 + 画中画 solutionsFilmora ???人物抠图 图层叠加 图…

聊一下怎么10分钟速水中危CVE

怎么在10分钟里速水一个中危CVE题目是真的。今天晚上刚接触关于CVE的审计和提交。只能说:牛逼的是真牛逼,水的是真水。 我接下来教你,怎么在10分钟里找到一个CVE的中危漏洞并且提交。然后你就可以去咸鱼接单了,一个一铂快 打开https://code-projects.org/ 随机找一个水项目…

2024-2025-1 20241403 《计算机基础与程序设计》第十周学习总结

学期2024-2025-1 学号20241403 《计算机基础与程序设计》第十周学习总结 作业信息这个作业属于哪个课程 <班级的链接>2024-2025-1-计算机基础与程序设计](https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP)这个作业要求在哪里 <作业要求的链接>2024-2025-1计…

《Django 5 By Example》阅读笔记:p388-p454

《Django 5 By Example》学习第 15 天,p388-p454 总结,总计 66 页。 一、技术总结 1.celery 我觉得书中这种用法太简单了。 2.flower 用于监控 celery。 # 安装 pip install flower # 启动 celery -A myshop flower --basic-auth=root:root # 访问 http://127.0.0.1:555…

java学习11.29

去年22级报销管理系统基本完成,出差报销申请撤销,审批功能基本完成

Kustomize 设计理念与使用说明

Kustomize 设计理念与使用说明 一、设计理念 Kustomize 的设计理念是基于"基础配置 + 补丁"的模式,这里解释一下为什么需要在 base 目录下创建基础配置:基础配置的重要性:base 目录下的配置是所有环境共享的基础配置 包含了服务最基本的定义和配置 确保了不同环境…