Solidity快速梳理进阶要点

  • 木西
  • 发布于 11小时前
  • 阅读 42

前言本文高效梳理Solidity编程语言进阶知识点1.底层调用call、delegatecall以及Multicallcall:用于调用其他合约的函数,可以修改目标合约的状态。delegatecall:在调用者的上下文中执行目标合约的代码,可以修改调用者的状态。Multical

前言

本文高效梳理Solidity编程语言进阶知识点

1.底层调用 call、delegatecall 以及 Multicall

  • call:用于调用其他合约的函数,可以修改目标合约的状态。
  • delegatecall:在调用者的上下文中执行目标合约的代码,可以修改调用者的状态。
  • Multicall:允许在一笔交易中执行多个调用,适用于批量操作,提高效率。

    2.跨合约调用方式

    类型

  • 通过合约地址直接调用:简单直接,但需要注意返回值和错误处理。
  • 通过接口调用:安全、易维护,推荐使用。
  • 低级调用(call、delegatecall) :适用于高级用例,需要小心使用以避免安全问题。
  • Multicall:适用于批量操作,提高效率。

    方法

  • 通过合约地址直接调用

    
    # 合约B
    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.17;
    contract ContractB  {
    function add(uint a, uint b) external pure returns (uint) {
    return a + b;
    }
    }
    # 合约A 调用B合约
    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.17;
    import "./5_b.sol";//B合约

contract ContractA { function callAdd(address _contractBAddress, uint a, uint b) external pure returns (uint) { ContractB contractB = ContractB(_contractBAddress); return contractB.add(a, b); } }

* #### 通过接口调用

合约B接口

// SPDX-License-Identifier: MIT pragma solidity 0.8.17;

interface IContractB { function add(uint a, uint b) external pure returns (uint); }

合约B

// SPDX-License-Identifier: MIT pragma solidity 0.8.17; contract ContractB { function add(uint a, uint b) external pure returns (uint) { return a + b; } }

合约A 调用B合约接口

// SPDX-License-Identifier: MIT pragma solidity 0.8.17; import "./5_ib.sol";//B合约接口

contract ContractA { function callAdd(address _contractBAddress, uint a, uint b) external pure returns (uint) { IContractB contractB = IContractB(_contractBAddress); return contractB.add(a, b); } }

* #### 低级调用(call、delegatecall)
**call**

合约B

// SPDX-License-Identifier: MIT pragma solidity 0.8.17; contract ContractB { function add(uint a, uint b) external pure returns (uint) { return a + b; } }

合约A 调用B合约

// SPDX-License-Identifier: MIT pragma solidity 0.8.17;

contract ContractA { function callAdd(address _contractBAddress, uint a, uint b) external returns (uint) { (bool success, bytes memory data) = _contractBAddress.call( abi.encodeWithSignature("add(uint256,uint256)", a, b) ); require(success, "Call failed"); return abi.decode(data, (uint)); } }

**delegatecall**

   **使用场景**
   * **代理合约**
   * **EIP-2535 Diamonds(钻石)**

* #### Multicall

# 3.常见的特殊变量
* **`address(this)`**:当前合约的地址
* **`msg.sender`**:当前调用合约的地址
* **`msg.value`**:表示当前交易发送的以太数量(单位是wei)
*  **`msg.data`**:表示当前调用的数据部分,通常用于低级调用
*  **`tx.origin`**:表示当前交易的发起者,即最初发送交易的地址
*  **`block.number`**:表示当前区块的编号
*  **`block.timestamp`**:表示当前区块的时间戳
*  **`block.difficulty`**:表示当前区块的难度
*  **`block.gaslimit`**:表示当前区块的Gas限制
*  **`block.coinbase`**:表示当前区块的矿工地址
*  **`blockhash(uint blockNumber)`**:返回指定区块的哈希值,只能获取最近256个区块的哈希值
*  **`abi.encodePacked(...)`**:将多个值打包成一个字节数组
*  **`abi.encode(...)`**:将多个值编码成一个字节数组,使用更严格的编码规则
*  **`abi.encodeWithSignature(string memory signature, ...)`**:将多个值编码成一个字节数组,并包含函数签名
*  **`abi.decode(bytes memory data, (...))`**:将字节数组解码成多个值
*  **`keccak256(bytes memory data)`**:计算给定数据的Keccak-256哈希值
*  **`require(bool condition, string memory message)`**:如果条件不满足,抛出错误并回滚交易
*  **`assert(bool condition)`**:如果条件不满足,抛出错误并回滚交易,通常用于内部错误检查
*  **`revert(string memory reason)`**:显式回滚当前交易
*  **`gasleft()`**:返回当前交易剩余的Gas数量
*  **`this`**:表示当前合约的实例,可以用来调用合约的函数
*  **`super`**:在继承中,表示父合约的实例
*  **`type(C).name`**:返回合约的名称
*  **`type(C).creationCode`**:返回合约的创建代码
*  **`type(C).runtimeCode`**:返回合约的运行时代码
# 4.创建合约Create1
### 直接上案例
**合约**

contract Pair{ address public factory; // 工厂合约地址 address public token0; // 代币1 address public token1; // 代币2

constructor() payable {
    factory = msg.sender;
}

// called once by the factory at time of deployment
function initialize(address _token0, address _token1) external {
    require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
    token0 = _token0;
    token1 = _token1;
}

}

**工厂合约**

// SPDX-License-Identifier: MIT pragma solidity 0.8.0; import "./Pair.sol"; contract PairFactory{ mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址 address[] public allPairs; // 保存所有Pair地址

function createPair(address tokenA, address tokenB) external returns (address pairAddr) {
    // 创建新合约
    Pair pair = new Pair(); 
    // 调用新合约的initialize方法
    pair.initialize(tokenA, tokenB);
    // 更新地址map
    pairAddr = address(pair);
    allPairs.push(pairAddr);
    getPair[tokenA][tokenB] = pairAddr;
    getPair[tokenB][tokenA] = pairAddr;
}

}

#### 案例说明
* 使用工厂合约中的createPair生成合约
* 通过allPairs获取合约地址
* 通过 at address输入获取的合约地址可以查看对应的实例
# 5.创建合约Create2 
# 直接上案例
**合约**

contract Pair{ address public factory; // 工厂合约地址 address public token0; // 代币1 address public token1; // 代币2

constructor() payable {
    factory = msg.sender;
}

// called once by the factory at time of deployment
function initialize(address _token0, address _token1) external {
    require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
    token0 = _token0;
    token1 = _token1;
}

}

**工厂合约**

// SPDX-License-Identifier: MIT pragma solidity 0.8.0; import "./Pair.sol"; contract PairFactory2{ mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址 address[] public allPairs; // 保存所有Pair地址

function createPair2(address tokenA, address tokenB) external returns (address pairAddr) {
    require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突
    // 用tokenA和tokenB地址计算salt
    (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序
    bytes32 salt = keccak256(abi.encodePacked(token0, token1));
    // 用create2部署新合约
    Pair pair = new Pair{salt: salt}(); 
    // 调用新合约的initialize方法
    pair.initialize(tokenA, tokenB);
    // 更新地址map
    pairAddr = address(pair);
    allPairs.push(pairAddr);
    getPair[tokenA][tokenB] = pairAddr;
    getPair[tokenB][tokenA] = pairAddr;
}

}

# 总结Create1 和Create2 差异
| 特性         | CREATE                                             | CREATE2                                     |
| ---------- | -------------------------------------------------- | ------------------------------------------- |
| **地址生成方式** | 基于创建者地址和创建者账户中的nonce值,通过哈希计算生成                     | 基于创建者地址、salt值和初始化代码的keccak256哈希,通过哈希计算生成    |
| **地址可预测性** | 合约地址是可预测的,但需要等待上一个创建者账户中的nonce增加                   | 合约地址在创建时就能够预测,不受nonce的影响                    |
| **用途**     | 适用于在合约之间直接通信,无需事先知道合约地址                            | 适用于在创建合约时预测合约地址,并通过地址存储信息,以便其他合约能够可靠地找到它    |
| **重复部署**   | 如果两个不同的创建者同时尝试使用相同的nonce创建合约,可能会发生nonce竞争,导致一个创建失败 | 使用不同的salt,两个创建者可以同时创建具有相同初始化代码的合约,而不会发生地址冲突 |
| **灵活性**    | 地址生成方式较为固定,依赖于创建者的nonce                            | 提供了更多的地址生成灵活性,可以通过选择不同的salt值来创建不同的地址        |

# 6.销毁合约
### 实例
**注释:说明**:在部署合约的时候给合约转一定量的eth代币
**测试验证步骤**:
* **编译、部署合约**:在部署
* 调用getBalance获取余额返回代币的余额:例如1eth
* 调用deleteContract后,在调用getBalance返回的余额为:0eth

合约

// SPDX-License-Identifier: MIT contract DeleteContract {

uint public value = 10;

constructor() payable {}

receive() external payable {}

function deleteContract() external {
    // 调用selfdestruct销毁合约,并把剩余的ETH转给msg.sender
    selfdestruct(payable(msg.sender));
}

function getBalance() external view returns(uint balance){
    balance = address(this).balance;
}

}

# 7.Solidity 内联汇编
**Solidity 内联汇编是什么**:Solidity 提供的一种底层语言,允许开发者直接在 Solidity 代码中编写 EVM 指令,使用 Yul 语言
### 基本语法

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract InlineAssemblyExample { function add(uint256 a, uint256 b) public pure returns (uint256 result) { assembly { result := add(a, b) // 直接使用 EVM 的 add 指令 } } }

### 常用操作码
#### 算术运算

-   `add(value1, value2)`:加法
-   `sub(value1, value2)`:减法
-   `mul(value1, value2)`:乘法
-   `div(value1, value2)`:除法
-   `mod(value1, value2)`:取模
-   `exp(value1, value2)`:幂运算

#### 位运算

-   `not(x)`:按位取反
-   `and(x, y)`:按位与
-   `or(x, y)`:按位或
-   `xor(x, y)`:按位异或
-   `shl(x, y)`:逻辑左移
-   `shr(x, y)`:逻辑右移
-   `sar(x, y)`:算术右移

#### 比较运算符

-   `lt(x, y)`:小于
-   `gt(x, y)`:大于
-   `eq(x, y)`:等于
-   `iszero(x)`:检查是否为零

#### 存储操作

-   `sload(p)`:从存储位置 `p` 读取值
-   `sstore(p, v)`:将值 `v` 存储到位置 `p`

#### 内存操作

-   `mload(p)`:从内存位置 `p` 读取值
-   `mstore(p, v)`:将值 `v` 存储到内存位置 `p`
-   `msize()`:返回当前合约的内存大小

#### 其他操作

-   `keccak256(p, n)`:计算内存位置 `p` 开始的 `n` 字节的 Keccak-256 哈希值
-   `gas()`:返回当前合约中可用的燃料数量
-   `address()`:返回当前合约的地址
-   `caller()`:返回当前函数的调用者地址
-   `balance(a)`:返回地址 `a` 的余额
-   `extcodesize(a)`:返回地址 `a` 的代码大小
-   `create(v, p, n)`:创建新合约
-   `create2(v, p, n, s)`:使用盐值 `s` 创建新合约
-   `call(g, a, v, in, insize, out, outsize)`:调用地址 `a` 上的合约

# 8.合约的升级方式
* **可升级合约框架Openzeppelin**:透明代理和UUPS代理,可以查看我的另一篇[《智能合约可升级方式之通用可升级代理合约》](https://learnblockchain.cn/article/12008)
# 9.Solidity 内存布局
### 概念 :智能合约中变量和数据在内存中的存储方式,分为三部分存储(Storage)、内存(Memory)和栈(Stack)
* **存储(Storage)**:存储是智能合约的永久数据存储区域,它在区块链上。每个智能合约都有自己的存储空间,其中包含所有状态变量。存储中的数据是持久的,即使在交易执行完成后仍然存在

* **内存(Memory)**:内存是智能合约在执行交易时使用的临时数据存储区域。内存中的数据在交易执行完成后会被丢弃,不会保存在区块链上
* **栈(Stack)**:栈是智能合约在执行交易时使用的临时数据存储区域,用于存储函数调用的参数和返回值
### 应用

pragma solidity ^0.8.0;

contract StorageExample { // 存储变量 uint256 public storedData;

// 内存变量
function set(uint256 x) public {
    uint256 temp = x; // 内存变量
    storedData = temp;
}

// 栈变量
function get() public view returns (uint256) {
    return storedData;
}

}

# 10. Library库合约
### 特点
1.  **可重用性**:库合约中的函数可以被多个合约调用,从而避免代码重复。
1.  **状态变量**:库合约不能包含状态变量,因为它们不能拥有自己的存储。
1.  **部署方式**:库合约在部署时会生成一个独立的地址,其他合约通过这个地址调用库合约中的函数。
1.  **调用方式**:库合约中的函数可以通过`libraryName.functionName()`的方式调用,也可以通过`this.functionName()`的方式调用。
### 使用案例

库合约

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0;

library MathLibrary { function add(uint a, uint b) internal pure returns (uint) { return a + b; }

function sub(uint a, uint b) internal pure returns (uint) {
    require(b <= a, "Subtraction overflow");
    return a - b;
}

}

调用库合约

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0;

import "./MathLibrary.sol";

contract MyContract { using MathLibrary for uint;

function addNumbers(uint a, uint b) public pure returns (uint) {
    return a.add(b); // 使用库合约中的add函数
}

function subNumbers(uint a, uint b) public pure returns (uint) {
    return a.sub(b); // 使用库合约中的sub函数
}

}

# 11.Openzeppelin 代码库
1.  **提供安全的合约模板**:OpenZeppelin 提供了一组经过审计和验证的安全合约库,涵盖了 ERC20、ERC721 等常用标准的实现。这些模板帮助开发者快速、安全地构建智能合约,减少开发工作量,避免常见的漏洞和错误。

1.  **增强合约安全性**:

    -   其合约经过多次安全审计,减少了智能合约开发中的常见安全漏洞。
    -   提供多种安全相关的功能,如访问控制模块、所有权控制合约、防止重入攻击的工具、数学计算时的溢出保护等。
    -   使用 OpenZeppelin 的合约库,可以显著提高智能合约的安全性。

1.  **促进合约的模块化和可扩展性**:

    -   采用模块化的设计,允许开发者根据需求组合使用不同的功能模块。
    -   提供了灵活的基于角色的权限控制方案和可重用的 Solidity 组件。
1.  **推动合约的标准化**:其提供的 ERC20、ERC721 等标准的实现,被广泛应用于 DeFi 和 NFT 项目中,促进了智能合约的标准化发展。

1.  **助力合约的可升级性**:OpenZeppelin 提供了可升级合约模式,帮助开发者在不重新部署合约的情况下,对合约逻辑进行升级。

1.  **拥有庞大的社区支持**:作为一个开源项目,OpenZeppelin 拥有一个庞大且活跃的开发社区,贡献着各种工具、合约和改进。

1.  **对行业的影响**:OpenZeppelin 的 ERC-20/721 库被 90% 的 DeFi 和 NFT 项目采用,其在智能合约开发中的广泛使用,对整个区块链行业的发展产生了重要影响
# 12.ABI 编解码
### 编码、解码
**编、解码方法**
* `abi.encode`
* `abi.encodePacked`
* `abi.encodeWithSignature` 
* `abi.encodeWithSelector`
* `abi.decode`

**完整例子**

// SPDX-License-Identifier: MIT pragma solidity 0.8.14; contract ABIFn { uint x ; address addr; string name ; uint[2] array; function encode(uint x, address addr,string memory str, uint[] memory array) public view returns(bytes memory result) { result = abi.encode(x, addr, name, array); } function encodePacked(uint x, address addr,string memory str, uint[] memory array) public view returns(bytes memory result) { result = abi.encodePacked(x, addr, array);
} function encodeWithSignature(uint x, address addr,string memory str, uint[] memory array) public view returns(bytes memory result) { result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array); } function encodeWithSelector(uint x, address addr,string memory str, uint[] memory array) public view returns(bytes memory result) { result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array); } function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) { (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2])); }

}

#### 总结
-   **`abi.encode`**:遵循ABI规范,对数据进行填充和对齐,适用于编码函数参数、事件日志等。
-   **`abi.encodePacked`**:不遵循ABI规范,不进行填充和对齐,编码后的数据更紧凑,适用于哈希计算、存储等场景。
-   **`abi.encodeWithSignature`**:包含函数的4字节选择器和参数,适用于构造函数调用数据。
-   **`abi.encodeWithSelector`**:与`abi.encodeWithSignature`类似,但直接接受4字节选择器。
-   **`abi.decode`**:解码ABI编码的数据,需要指定解码的类型,适用于解码函数调用数据、事件日志等。

# 13.Hash应用
### 特性
-   **生成数据唯一标识**
-   **加密签名**
-   **安全加密**
### 应用

// SPDX-License-Identifier: MIT pragma solidity 0.8.14; contract HashFn { function hash( uint _num, string memory _string, address _addr ) public pure returns (bytes32) { return keccak256(abi.encodePacked(_num, _string, _addr)); } }


# 总结
以上内容对Solidity的基础要点进行了梳理。如需深入了解,可查阅Solidity官方文档。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
江湖只有他的大名,没有他的介绍。