区块链web3 基础知识,包括ABI、EIP、ERC等
- EIP和ERC
-
- ERC-20 (代币标准)
- ERC-721 (非同质化代币标准)
- ERC165
- ERC-4626 (资产托管合约标准)
- ERC-777 (高级代币标准)
-
-
- [关键代码,token转移 send函数](#关键代码,token转移 send函数)
-
- EIP-2612 (代币许可)
- ERC-1155 (多代币标准)
- SBT (Soulbound Tokens,灵魂绑定代币)
ABI
一个是 solidity 提供了ABI的相关API函数:
abi.encode(...) returns (bytes)
:计算参数的ABI编码abi.encodePacked(...) returns (bytes)
:计算参数的紧密打包编码 `abi. encodeWithSelector(bytes4 selector, ...) returns (bytes)
: 计算函数选择器和参数的ABI编码abi.encodeWithSignature(string signature, ...) returns (bytes)
:
等价于*abi.encodeWithSelector(bytes4(keccak256(signature), ...)
用 "1" 作为参数调用set,如下图:
然后我们打开etherscan查看交易详情数据, 可以看到其附加数据如下图:
这个数据就是ABI的编码数据:
javascript
0x60fe47b10000000000000000000000000000000000000000000000000000000000000001
javascript
function abiEncode() public constant returns (bytes) {
abi.encode(1); // 计算1的ABI编码
return abi.encodeWithSignature("set(uint256)", 1); //计算函数set(uint256) 及参数1 的ABI 编码
}
EIP和ERC
EIP 是为以太坊生态提出的改进建议,涵盖协议升级、核心规则和智能合约标准等;
ERC 是一种特殊类型的 EIP,用于定义智能合约的标准,通常是应用层的开发者所使用。
简单来说,ERC 是以 EIP 提案形式提出并被广泛采用的智能合约标准。ERC 的编号是对应的 EIP 编号。例如:ERC-20 实际是 EIP-20。
ERC-20 (代币标准)
- 功能:ERC-20 是一种广泛使用的代币标准,定义了代币的交互接口,包括转账、余额查询、供应量查询等。它允许智能合约表现得像标准化的、可预测的货币。
- 内容 :ERC-20 标准包括基本的代币功能,如
transfer()
,balanceOf()
,approve()
, 和transferFrom()
,这些函数确保了代币能够在整个以太坊网络中被统一和可靠地处理。
功能代码:
solidity
function transfer(address to, uint256 value)
function approve(address owner,address spender,uint256 value)
function transferFrom(address from,address to,uint256 value)//由被授权者调用此方法,from为授权者
function mint(address account, uint256 value)
function burn(address account, uint256 value)
tips:
transfer和safeTransfer函数的区别?
_safeTransfer
函数在发送代币之前会检查接收方地址是否是一个合约。如果是,它会调用该合约的onERC20Received
函数(这是一个接口,由接收代币的合约实现)来确认合约能够接收代币。- 确保了接收代币的合约能够正确处理接收到的代币,从而避免了代币永久锁定在无法接收的合约地址中。
ERC20Permit.sol
该合约提供了一个新的授权操作,permit()函数的作用便是完成 owner对spender的授权,个人理解是,因为原ERC20中的approve函数必须是owner亲自去调用才能完成授权,这比较麻烦owner,而permit则是可以通过owner提供的签名来验证,并执行owner对spender的授权操作。
ERC20Burnable.sol
该合约提供了销币功能。
- burn(uint256 value):销毁msg.sender的value个代币。
- burnFrom(address account, uint256 value) :msg.sender销毁account的value个代币,前提是account给予msg.sender权限。
ERC20Capped.sol
给ERC20代币的的totalsupply设置上限,设置某个ERC20代币的发行量不能超过cap。限制的逻辑在这里(from==0,则被检测为铸币操作):
solidityif (from == address(0)) { uint256 maxSupply = cap(); uint256 supply = totalSupply(); if (supply > maxSupply) { revert ERC20ExceededCap(supply, maxSupply); } }
ERC20FlashMint.sol
该合约提供了一个借贷功能,只能借该合约生成的代币,且最大接待额为:
token == address(this) ? type(uint256).max - totalSupply() : 0
;还需要支付fee,这个借贷函数不需要主动还款,因为ta采用的是burn操作,直接将你手中借来的token全部销毁。
ERC20Pausable.sol
该合约提供了一个紧急停止功能,在_update()函数加上whenNotPaused修饰符
ERC20Snapshot.sol
通过快照机制记录当时的余额和总供应量,以备以后查阅。
solidity
function _snapshot() internal virtual returns (uint256)
function _getCurrentSnapshotId() internal view virtual returns (uint256)
function balanceOfAt(address account, uint256 snapshotId) public view virtual returns (uint256)
function totalSupplyAt(uint256 snapshotId) public view virtual returns (uint256)
function _beforeTokenTransfer(address from,address to,uint256 amount)
function _valueAt(uint256 snapshotId, Snapshots storage snapshots) private view returns (bool, uint256)
function _updateAccountSnapshot(address account)
function _updateTotalSupplySnapshot()
function _updateSnapshot(Snapshots storage snapshots, uint256 currentValue)
function _lastSnapshotId(uint256[] storage ids) private view returns (uint256)
ERC20Votes.sol
该合约支持DAO的投票和授权,正常投票前需要先授权代币
ERC20Wrapper.sol
该合约支持代币的包装,用户可以存入和取出
_underlying
代币,存入多少_underlying代币,就可以铸造多少ERC20Wrapper代币,同理取出多少 _underlying代币,便会销毁多少ERC20Wrapper代币代币。
ERC-721 (非同质化代币标准)
- 功能:ERC-721 定义了非同质化代币(NFT)的标准,每个代币都是独一无二的,可以代表不同的资产,如艺术品、收藏品、房产等。
- 内容 :ERC-721 包括
transferFrom
,approve
, 和ownerOf
等方法,每个方法都处理具有唯一标识符的独立代币。
代码功能:
solidity
function transfer(address to, uint256 tokenId)
function setApprove(address to,uint256 tokenId)
function transferFrom(address to,uint256 tokenId)//由被授权者调用此方法,from为nft的owner
function mint(address account,uint256 tokenId,string memory tokenURI)
function burn(uint256 tokenId)
ERC165
ERC165 的核心方法
supportsInterface(bytes4 interfaceID) external view returns (bool)
用于检测合约是否支持某个接口。
interfaceID 是接口函数签名的 XOR(异或)结果。
参考链接:理解ERC165标准
ERC165的使用
目录结构
javascript
contracts/
├─ MyContract.sol
├─ IMyInterface.sol
test/
├─ MyContract.t.sol
合约代码
javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IMyInterface {
function myFunction() external view returns (uint256);
}
javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "./IMyInterface.sol";
contract MyContract is ERC165, IMyInterface {
// 实现接口方法
function myFunction() external pure override returns (uint256) {
return 42;
}
// 声明支持的接口
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IMyInterface).interfaceId || super.supportsInterface(interfaceId);
}
}
测试脚本
javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
import "../contracts/MyContract.sol";
import "../contracts/IMyInterface.sol";
contract MyContractTest is Test {
MyContract myContract; // 实例化待测试的合约
function setUp() public {
// 部署 MyContract 合约
myContract = new MyContract();
}
function testSupportsInterface() public {
// 计算接口 ID
bytes4 interfaceId = type(IMyInterface).interfaceId;
console.logBytes32(interfaceId);
// 测试是否支持接口
bool isSupport = myContract.supportsInterface(interfaceId);
console.log(" isSupport: ", isSupport);
assertTrue(isSupport, "MyContract should support IMyInterface");
}
function testDoesNotSupportOtherInterfaces() public {
// 一个不存在的接口 ID
bytes4 fakeInterfaceId = 0x12345678;
// 测试不支持的接口
bool isSupport = myContract.supportsInterface(fakeInterfaceId);
console.log("interfaceId= 0x12345678 isSupport: ", isSupport);
assertFalse(isSupport, "MyContract should not support fake interface");
}
function testMyFunction() public {
// 测试接口函数是否正常工作
uint256 result = myContract.myFunction();
console.log("myFunction() result: ", result);
assertEq(result, 42, "myFunction should return 42");
}
}
ERC-4626 (资产托管合约标准)
- 功能:ERC4626 协议是一种用于代币化保险库的标准,它可以优化和统一收益保险库的技术参数。收益保险库是指使用不同策略来为用户提供最佳收益的合约,例如借贷市场、聚合器或本身具有利息的代币。
- 内容:ERC4626 协议提供了一个标准的 API,用于表示单个底层 ERC-20 代币的收益保险库份额。,包括存入、取出资产的方法,并计算每个代币代表的底层资产份额。
份额计算公式 (shares = assets * totalSupply / totalAssets),计算结果是有小数点的,一般是向下取整。
代码功能:
preview
函数提供了一种方式,用于在不实际执行交易的情况下预估交易结果。这可以帮助用户了解如果他们进行特定的金库操作(如存款、提款、铸币或赎回),将会得到多少份额或资产。
solidity
asset() //返回保险库使用的底层代币的地址,必须是一个 ERC-20 合约。
totalAssets() //返回保险库持有的底层资产的总量,应该包括任何由收益产生的复利。
convertToShares() //返回保险库为给定数量的底层资产兑换的份额数量。
convertToAssets() //返回保险库为给定数量的份额兑换的底层资产数量。
maxDeposit() //返回接收者在单次存款调用中可以存入的底层资产的最大数量。
previewDeposit() //允许用户在当前区块模拟他们的存款效果。
deposit() //将底层资产存入保险库,并将份额授予接收者。
maxMint() //返回接收者在单次铸造调用中可以铸造的份额的最大数量。
previewMint() //允许用户在当前区块模拟他们的铸造效果。
mint() //将份额铸造给接收者,并从保险库中取出相应数量的底层资产。
maxWithdraw() //返回接收者在单次取款调用中可以取出的底层资产的最大数量。
previewWithdraw() //允许用户在当前区块模拟他们的取款效果。
withdraw() //将份额从接收者处销毁,并将相应数量的底层资产从保险库中取出。
maxRedeem() //返回接收者在单次赎回调用中可以赎回的份额的最大数量。
previewRedeem() //允许用户在当前区块模拟他们的赎回效果。
redeem() //将份额从接收者处销毁,并将相应数量的底层资产从保险库中取出。
redeem
函数在 ERC-4626 标准中用于按份额赎回资产,允许用户根据持有的份额数量直接提取相应的资产。而 withdraw
函数则按用户指定的资产数量直接提取,计算并赎回所需的份额以匹配这一资产请求。
ERC-777 (高级代币标准)
-
功能:ERC-777 介绍了一个新的代币标准,提供了比 ERC-20 更多的功能,如发送者和接收者挂钩功能,更灵活的交易处理。
-
内容 :该标凈支持ERC-20的基本功能,并增加了操作员机制,允许用户授权第三方在其代币账户上执行操作,以及
send
和receive
钩子,这些钩子可以在代币被发送和接收时触发函数。 -
原因:由于ERC20 标准没有一个转账通知机制 ,很多ERC20代币误转到合约之后,再也没有办法把币转移出来,已经有大量的ERC20 因为这个原因被锁死,如锁死的QTUM,锁死的EOS 。
另外一个问题是ERC20 转账时,无法携带额外的信息,例如:我们有一些客户希望让用户使用 ERC20 代币购买商品,因为转账没法携带额外的信息, 用户的代币转移过来,不知道用户具体要购买哪件商品,从而展加了线下额外的沟通成本。
ERC777 在 ERC20的基础上定义了
send(dest, value, data)
来转移代币, send函数额外的参数用来携带其他的信息,send函数会检查持有者和接收者是否实现了相应的钩子函数,如果有实现(不管是普通用户地址还是合约地址都可以实现钩子函数),则调用相应的钩子函数。 -
**详细解释:**ERC777与ERC20兼容(兼容的意思就是ERC777的功能包括了ERC20的所有功能,实现兼容的方式就是,让ERC777直接继承IERC20接口。),同时引入了operator操作员的概念,操作员可以代表另一个地址(合约或者普通账户)发送代币,这个操作员的身份类似始于ERC20中被 某地址执行 approve操作后的身份,可以托管授权这的资产。同时还引进了sender和receiver的钩子函数(hooks)让代币持有者和代币接收者能有更多的处理。而且ERC777还采用了ERC1820标准的优点,可以判断某合约是否实现ERC777协议的相关接口,更重要的是还可以将sender/receiver的钩子函数放到 地址的 implement去处理,这样一来,使得整个代币体系更丰富,拓展性也大大增强。
ERC-777是在ERC20的基础上增加了操作员机制
- 普通操作员:即是代币的 holder亲自给 operator授权的,该 operator只能操作 该holder的代币。
- 默认操作员:即合约初始化设置的操作员,ta的权限是最大的,默认情况下它可以操作所有人的代币,类似于管理员。但是用户可以自行将其移除权限,这样一来ta就不能操作自己的tokens了。
solidity// Immutable, but accounts may revoke them (tracked in __revokedDefaultOperators). mapping(address => bool) private _defaultOperators; // For each account, a mapping of its operators and revoked default operators. mapping(address => mapping(address => bool)) private _operators; mapping(address => mapping(address => bool)) private _revokedDefaultOperators;
关键代码,token转移 send函数
solidity
function _send(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData,
bool requireReceptionAck
)
private
{
require(from != address(0), "ERC777: send from the zero address");
require(to != address(0), "ERC777: send to the zero address");
_callTokensToSend(operator, from, to, amount, userData, operatorData);
_move(operator, from, to, amount, userData, operatorData);
_callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);
}
-
_callTokensToSend
函数 ,先通过注册表getInterfaceImplementer(from, TOKENS_SENDER_INTERFACE_HASH)
查看 tokenHolder 用来实现IERC777TokensSender
接口的合约地址ADDRESS,如果有则调用ADDRESS中的tokensToSend
函数。solidityaddress implementer = _erc1820.getInterfaceImplementer(from, TOKENS_SENDER_INTERFACE_HASH); if (implementer != address(0)) { IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData); }
-
_move
函数则是负责更新余额,修改Holder和receiver的代币余额solidity_balances[from] = _balances[from].sub(amount); _balances[to] = _balances[to].add(amount);
-
_callTokensReceived
函数 ,先通过注册表getInterfaceImplementer(from, TOKENS_RECIPIENT_INTERFACE_HASH)
查看 tokenHolder 用来实现IERC777Recipient
接口的合约地址ADDRESS,如果有则调用ADDRESS中的tokensReceived
函数,如果没有还需要判断传入的参数requireReceptionAck
,如果参数为true
,那么则需要检测接收者to
是否为合约地址,如果是合约地址则revert
(其目的即使为了保证contract receiver必须实现ERC777TokensRecipient
)。solidityaddress implementer = _erc1820.getInterfaceImplementer(to, TOKENS_RECIPIENT_INTERFACE_HASH); if (implementer != address(0)) { IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); } else if (requireReceptionAck) { require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient"); }
EIP-2612 (代币许可)
- 功能:EIP-2612 允许代币持有者通过签名方式授权他人在他们的账户上进行交易,而无需实际发送交易,从而节约了Gas成本。
- 内容 :它引入了
permit
方法,使得代币的批准(通常通过approve
完成)可以通过签名方式离线完成,这种方式与ERC-20的approve/transferFrom
模式相兼容。
ERC-1155 (多代币标准)
- 功能:ERC-1155 允许在单一合约中定义和管理多种代币类型,包括可替代和非可替代代币。
- 内容:该标准提供了一种高效的代币管理方式,通过一个合约调用来批量处理多种代币类型的转移,减少了交易成本。
solidity
balanceOf() //单币种余额查询,返回account拥有的id种类的代币的持仓量。
balanceOfBatch() //多币种余额查询,查询的地址accounts数组和代币种类ids数组的长度要相等。
setApprovalForAll() //批量授权,将调用者的代币授权给operator地址。。
isApprovedForAll() //查询批量授权信息,如果授权地址operator被account授权,则返回true。
safeTransferFrom() //安全单币转账,将amount单位id种类的代币从from地址转账给to地址。如果to地址是合约,则会验证是否实现了onERC1155Received()接收函数。
safeBatchTransferFrom() //安全多币转账,与单币转账类似,只不过转账数量amounts和代币种类ids变为数组,且长度相等。如果to地址是合约,则会验证是否实现了onERC1155BatchReceived()接收函数。
SBT (Soulbound Tokens,灵魂绑定代币)
- 功能:SBT是一种不能转让的代币,用于代表用户身份、资质、经历或信誉等个人属性或成就。
- 内容 :实际上是一个特殊类型的
NFT
,它被设计为不可转让或交易。
solidity
1) 铸造 NFT
mint 函数允许合约所有者为指定的地址创建一个新的 SBT。
2)禁止转移
通过重写 transferFrom 方法,禁止 SBT 的转移。任何尝试转移 SBT 的操作都会失败。
3)禁止授权
通过重写 approve 和 setApprovalForAll 方法来禁止授权操作。