普通视图

发现新文章,点击刷新页面。
昨天 — 2025年7月2日首页

基于 ethers.js 的区块链事件处理与钱包管理实践指南

作者 木西
2025年7月2日 18:40

前言

本文将围绕 事件检索与监听HD 钱包批量生成与加密存储静态调用与 callData 构造ERC 标准合约识别 等关键场景,结合代码示例与最佳实践,展示如何利用 ethers.js 完成从基础交互到高级功能的完整流程。无论是初学者还是有经验的开发者,都能通过本指南快速掌握 ethers.js 的核心用法,并将其应用于实际项目中

Event事件

检索事件

const { ethers } = require("hardhat");
async function SearchEvent() {
    try {
        const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
        const signer = await provider.getSigner();
        const TokenAddress = "0xxxxx";//合约地址
        const TokenABI =[]//合约的abi;
        const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//创建合约
        //读取合约
        const name = await TokenContract.name();
        console.log("Contract Name:", name);
        const symbol = await TokenContract.symbol();
        console.log("Contract Symbol:", symbol);
        const totalSupply = await TokenContract.totalSupply();
        console.log("Total Supply:", totalSupply.toString());
        //合约转eth
        const arr1="0xxxxxxxx"
        await TokenContract.transfer(arr1,10);//给arr1转10;
        
        const block = await provider.getBlockNumber()//得到当前block
         const transferEvents = await TokenContract.queryFilter('Transfer', block - x, block);//检索合约Transfer,从block - x,到block之间的解析事件
          console.log(`Transfer事件数量: ${transferEvents.length}`);
         //transferEvents是个数组,我们可以解析他的参数
         console.log(...transferEvents[0].args);//返回form,to ,value
       }catch (error) {
        console.error("Error:", error);
    }
    }

监听事件

//以上同上
TokenContract.on("Transfer", (from, to, value, event) => {
            console.log(`Transfer事件触发:`);
            console.log(`From: ${from}`);
            console.log(`To: ${to}`);
            console.log(`Value: ${value.toString()}`);
            console.log(` 从 ${from}=> 到 ${to} = ${value.toString()}`); 
            console.log(`Event Details:`, event);   
        });

过滤事件

设置过滤规则:contract.filters.EVENT_NAME( ...args )说明:EVENT_NAME:过滤事件,...args:过滤规则

基础规则汇总

规则 含义 示例
null 该位置不限制,匹配任意值 contract.filters.Transfer(null, addr)
单个值 必须完全匹配 contract.filters.Transfer(addr)
数组 至少匹配数组中任意一个值 contract.filters.Transfer(null, [addr1, addr2])
以上代码如上
//设置规则
# 规则1
let addr1="0xf39Fd6e51aad88F6F4ce6axxxxxxx"
let addr2="0x70997970C51812dc3A010C7xxxxxx"
let addr3="0xb0997970C51812dcxxxxxxxxxxxxx"
let rule1 = TokenContract.filters.Transfer(addr1);//过滤来自`addr1`地址的`Transfer`事件
let rule2 = TokenContract.filters.Transfer(null,addr2);//过滤所有发给 addr2`地址的`Transfer`事件
let rule3 = TokenContract.filters.Transfer(addr1,addr2);//过滤所有从 `addr1`发给`addr2`的`Transfer`事件
let rule3 = TokenContract.filters.Transfer(addr1,addr2);//过滤所有从 `addr1`发给`addr2`的`Transfer`事件
let rule4 = TokenContract.filters.Transfer(null,[addr2,addr3]);//过滤所有发给 addr2`地址的或者addr3`的Transfer`事件
# 其他就是各种组合使用了
# 过滤使用
TokenContract.on(rule1, (res) => {
  console.log('---------监听开始过滤--------');
  console.log(
    `${res.args[0]} -> ${res.args[1]} ${res.args[2]}`
  )
})
# 其他同上 把过滤规则给监听事件即可

批量生成HD钱包

BIP汇总
BIP编号 主要用途 典型格式示例
BIP-32 HD 钱包路径 m/44'/0'/0'/0/0
BIP-39 助记词生成种子 12/24 个单词
BIP-44 多币种路径 m/44'/60'/0'/0/0
BIP-49 隔离见证兼容地址 m/49'/0'/0'/0/0
BIP-84 原生隔离见证地址 m/84'/0'/0'/0/0
BIP-173 Bech32 地址编码 bc1q...
BIP-350 Taproot 地址编码 bc1p...
以BIP-44为例代码实践
  • 助记词生成
 const mnemonic = ethers.Mnemonic.entropyToPhrase(ethers.randomBytes(32))
  • 创建HD基钱包
    BIP-44
    基路格式:"m / purpose' / coin_type' / account' / change" 参数说明
    • m:主密钥(Master Key)
    • purpose':固定为 44'(表示遵循 BIP-44 多账户标准)
    • coin_type':币种标识(如 0' = BTC,60' = ETH,501' = SOL)详细可查看SLIP-44
    • account':账户编号(从 0' 开始)
    • change:比特币专用(0 = 外部地址,1 = 找零地址);其他链通常为 0
    • address_index:地址索引(从 0 开始)
 # BIP-44
 // 基路径:
 const basePath = "44'/60'/0'/0"
 # 生成第一对外的链接
 const baseWallet = ethers.HDNodeWallet.fromPhrase(mnemonic, basePath)
  • 批量生成
const WalletNumber = 10;//钱包数
 for (let i = 0; i < WalletNumber; i++) {
     let NewBaseWallet = baseWallet.derivePath(i.toString());
     console.log(`第${i+1}个钱包地址: ${baseWalletNew.address}`)
     wallets.push(baseWalletNew);//生成10个钱包
 }
console.log("钱包地址列表:", wallets.map(wallet => wallet.address));
  • 加密JSON保存
async function saveWalletJson() {
 const wallet = ethers.Wallet.fromPhrase(mnemonic);//助记词
 console.log("通过助记词创建钱包:")
 console.log(wallet)
 // 加密json用的密码,可以更改成别的
 const pwd = "XXXX";
 const json = await wallet.encrypt(pwd)
 console.log("钱包的加密json:")
 console.log(json)
 require("fs").writeFileSync("keystoreBatch.json", json);//在当前文件夹下生成一个 keystoreBatch.json文件
 }
 saveWalletJson();
  • 通过加密json读取钱包信息
async function ReadWalletJson() {
console.log("开始读取json文件");
const json=require("fs").readFileSync("keystoreBatch.json", "utf8");
const walletJson =await ethers.Wallet.fromEncryptedJson(json, "xxx");//生成json时设置的密码
console.log("Wallet from JSON:",walletJson);
console.log("Address:", walletJson.address);
console.log("Private Key:", walletJson.privateKey);
console.log("Mnemonic:", walletJson.mnemonic.phrase);
}
ReadWalletJson();

staticCall和callStatic:

名称 所属模块 作用 返回值 适用场景
staticCall ethers.Contract 实例方法 只读方式 调用合约函数,不修改状态 函数返回值 任何函数(读/写)
callStatic ethers.Contract 实例方法(v6 新增) 只读方式 调用合约函数,不修改状态 函数返回值 任何函数(读/写)
# 代码实例
# staticCall
const from="0xf39xxx"
const to="0x70xxx"
const result = await TokenContract.transfer.staticCall(to,10,{  
                     // 可选 overrides
                    from: from, // 指定调用者(模拟不同账户)
                });
                console.log('模拟结果:', result);
# callStatic
const result = await TokenContract.transfer.staticCall(to,10,{                 
                    // 可选 overrides
                    from: from, // 指定调用者(模拟不同账户)
                });
                console.log('模拟结果:', result);

callData

  • 接口abi:infce=new ethers.Interface(abi);//两者是一样的功能

  • callData:infce=TokenContract.interface;//两者是一样的功能

const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const signer = await provider.getSigner();
const TokenAddress = "0xxxx";//合约地址
const TokenABI =[];//abi
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);
const param = TokenContract.interface.encodeFunctionData(
    "balanceOf",
    ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]
  );
  console.log("param:", param);
  const tx = {
    to: TokenAddress,
    data: param
}
// 发起交易,可读操作(view/pure)可以用 provider.call(tx)
const balanceWETH = await provider.call(tx)
console.log(`存款前WETH持仓: ${ethers.formatEther(balanceWETH)}\n`)

encodeFunctionData

const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const signer = await provider.getSigner();
const TokenAddress = "0xxxxxxx";//合约地址
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//构造合约
# 使用合约的transfer 向0x70997970C51812dc3A010C7d01b50e0d17dc79C8 转10n
const calldata = TokenContract.interface.encodeFunctionData('transfer', [
  '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // 收款地址
  10n                                           // 转账数量 (BigInt)
]);
console.log(calldata)//生成callData
const wallet = new ethers.Wallet("钱包的私钥", provider);
const tx = await wallet.sendTransaction({
  to: "0x5Fxxxxxxx",//合约地址
  data: calldata,
});
await tx.wait();
console.log("交易成功生成的txHash:", tx.hash);
//通过交易hash 
//交易的详细信息
const hash = await provider.getTransaction(tx.hash);
//交易收据
const receipt = await provider.getTransactionReceipt(tx.hash);

识别ERC20、ERC721、ERC115标准合约

识别关键说明:所有现代标准(ERC721、ERC1155)都实现了 ERC165,通过 supportsInterface(bytes4 interfaceId) 函数声明支持的接口,ERC20 不支持 ERC165

  • ERC20

    说明:识别关键ERC20不是基于ERC165,但是ERC20包含totalSupply,识别关键通过totalSupply
    const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
    const signer = await provider.getSigner();//
    const TokenAddress = "0x5Fbxxxxx";//合约地址
    const TokenABI = []//abi
    const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//创建合约
    const totalSupplyValue=await TokenContract.totalSupply(); 
    console.log(totalSupplyValue)//说明是ERC20
    
  • ERC721

    说明:识别关键是ERC721基于ERC165,ERC165标准包含supportsInterface(bytes4 interfaceId)
     创建合约如上
     const isERC721 = await contract.supportsInterface("0x80ac58cd");
     console.log(isERC721); // true 或 false
    
  • ERC1155

    说明:识别关键是ERC721基于ERC165,ERC165标准包含supportsInterface(bytes4 interfaceId)
     创建合约如上
     const isERC721 = await contract.supportsInterface("0xd9b67a26");
     console.log(isERC721); // true 或 false
    
  • 总结

    调用函数/方法 返回值 识别结果 备注
    supportsInterface(0x80ac58cd) true ERC721 NFT 标准接口标识符
    supportsInterface(0xd9b67a26) true ERC1155 多代币标准接口标识符
    totalSupply() 等函数调用成功 成功 ERC20 同质化代币标准(无 ERC165)

总结

以上就是系统介绍了使用 ethers.js 进行区块链开发的关键技术,涵盖事件处理、钱包管理、合约交互及标准识别四大核心模块,并通过代码示例与最佳实践提供完整解决方案;

昨天以前首页

Solidity快速梳理进阶要点

作者 木西
2025年6月27日 22:07

前言

本文高效梳理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.合约的升级方式

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. 可重用性:库合约中的函数可以被多个合约调用,从而避免代码重复。
  2. 状态变量:库合约不能包含状态变量,因为它们不能拥有自己的存储。
  3. 部署方式:库合约在部署时会生成一个独立的地址,其他合约通过这个地址调用库合约中的函数。
  4. 调用方式:库合约中的函数可以通过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 等常用标准的实现。这些模板帮助开发者快速、安全地构建智能合约,减少开发工作量,避免常见的漏洞和错误。

  2. 增强合约安全性

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

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

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

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

  7. 对行业的影响: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官方文档。

❌
❌