深入剖析ERC20

您所在的位置:网站首页 代币是什么脏话 深入剖析ERC20

深入剖析ERC20

2024-07-09 16:04| 来源: 网络整理| 查看: 265

1.ERC20简介​ERC20是以太坊区块链创建的可替代的技术标准,可替代代币是可以与另一种代币进行交换的代币,故此ERC20代币是一种同质化代币。ERC20协议更像是一种规范,规范了在智能合约中实施代币的标准API,使得代币具有基本的转账功能,以便其他链上第三方可以使用。ERC20接口

1. ERC20简介

​ ERC20是以太坊区块链创建的可替代的技术标准,可替代代币是可以与另一种代币进行交换的代币,故此ERC20代币是一种同质化代币。ERC20协议更像是一种规范,规范了在智能合约中实施代币的标准API,使得代币具有基本的转账功能,以便其他链上第三方可以使用。

ERC20接口:

pragma solidity ^0.8.20; interface IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 value) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); }

​ 这是ERC20最基本的也是最重要的功能,凡是遵循ERC20标准的都需要实现该接口。这几个方法很简单,transferFrom()函数还是要值得注意,该函数的使用方式是,from需要提前为msg.sender授权,即需要from去亲自调用approve()函数。只有 from为msg.sender授权之后,transferFrom()函数才能够成功执行,这是在平时打CTF的时候经常容易忽视的操作,写完攻击逻辑之后,最后报错。。。才发现是在某些合约里的某些函数中的转账逻辑是transferFrom(),由于没有授权导致的。

2. USDT的坑 2.1 USDT的问题所在

​ 还有一个值得注意的点是,transfer()和transferFrom()都是有返回值的!!!!为什么主要,就是因为全球使用最广的稳定币的源码中transfer()和transferFrom()是没有返回值,这是一个坑!!!大坑!!!

可以到 浏览器看到Tether Token(USDT)的源码,可以看到这两个函数:

TetherToknen.sol:

function transfer(address _to, uint _value) whenNotPaused { if (deprecated) { return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value); } else { return super.transfer(_to, _value); } } function transferFrom(address _from, address _to, uint _value) whenNotPaused { if (deprecated) { return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value); } else { return super.transferFrom(_from, _to, _value); } }

这里两个函数是没有返回值,即使它调用的是父类的函数,在没有看到父类的具体函数实现,姑且说它的父类是有返回值的,但是子类中的函数是没有返回值,这是在Remix中编译不过去的,举例:

image.png 可以看到编译通过了。下面是子合约函数中没有返回值的:

image.png

所以不用去父类中找函数都可以知道父类的函数也是没有返回值的,不信就去验证一下:

BasicToken.sol::transfer()

function transfer(address _to, uint _value) onlyPayloadSize(2 * 32) { uint fee = (_value.mul(basisPointsRate)).div(10000); if (fee > maximumFee) { fee = maximumFee; } uint sendAmount = _value.sub(fee); balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(sendAmount); balances[owner] = balances[owner].add(fee); Transfer(msg.sender, _to, sendAmount); Transfer(msg.sender, owner, fee); }

StandardToken.sol::transferFrom()

function transferFrom(address _from, address _to, uint _value) onlyPayloadSize(3 * 32) { var _allowance = allowed[_from][msg.sender]; uint fee = (_value.mul(basisPointsRate)).div(10000); if (fee > maximumFee) { fee = maximumFee; } uint sendAmount = _value.sub(fee); balances[_to] = balances[_to].add(sendAmount); balances[owner] = balances[owner].add(fee); balances[_from] = balances[_from].sub(_value); if (_allowance ; MAX_UINT) { allowed[_from][msg.sender] = _allowance.sub(_value); } Transfer(_from, _to, sendAmount); Transfer(_from, owner, fee); } 2.2 复现DOS异常

​ 本地复现。

​ 本地部署USDT,地址为:0x5FbDB2315678afecb367f032d93F642f64180aa3。

​ 再部署一个转移代币的 TokenTransfer.sol,地址为:0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512。

// SPDX-License-Identifier: MIT pragma solidity 0.8.20; interface IERC20_USDT { function transfer(address, uint256) external; function transferFrom(address, address, uint256) external; } interface IERC20_stad { function transfer(address, uint256) external returns(bool); function transferFrom(address, address, uint256) external returns(bool); } // USDT: 0x5FbDB2315678afecb367f032d93F642f64180aa3 contract TokenTransfer { IERC20_USDT USDT; IERC20_stad TOKEN; constructor(address _usdt) { USDT = IERC20_USDT(_usdt); TOKEN = IERC20_stad(_usdt); } function test_TransferWithOutReturnValue() external { USDT.transfer(msg.sender, 10); } function test_TransferWithReturnValue() external { TOKEN.transfer(msg.sender, 10); } function test_TransferFromWithOutReturnValue() external { USDT.transferFrom(msg.sender, 0x71bE63f3384f5fb98995898A86B02Fb2426c5788, 1); } function test_TransferFromWithReturnValue() external { TOKEN.transferFrom(msg.sender, 0x71bE63f3384f5fb98995898A86B02Fb2426c5788, 1); } }

​ 该合约主要是测试transfer和transferFrom函数,定义两个接口,一个接口中有返回值,一个没有,模拟将USDT转入一个遵循ERC20标准的合约,看看是否能将转入的ERC20 Token转出。

​ 可以看到调用test_TransferWithReturnValue,交易会被revert,而调用test_TransferWithOutReturnValue,交易则正常运行。

image.png

​ 同理test_TransferFromWithReturnValue操作也是会被revert。这也就说明了,用标准的ERC20接口转换USDT,就会造成资金永久封锁的情况。当然,为了解决这个问题,可以将合约中的ERC20接口中的那两个函数的返回值移除,即:

interface IERC20_USDT { function transfer(address, uint256) external; function transferFrom(address, address, uint256) external; } 3.SafeERC20 3.1 兼容USDT

​ 当然还有其他的解决方式,也是最常用一种解决方式,那就是使用SafeERC20库。

using SafeERC20 for IERC20;

​ SafeERC20做了兼容严格遵守与不严格遵守ERC20协议标准的代币,兼容的原理如下:

function _callOptionalReturn(IERC20 token, bytes memory data) private { bytes memory returndata = address(token).functionCall(data); if (returndata.length != 0 && !abi.decode(returndata, (bool))) { revert SafeERC20FailedOperation(address(token)); } } function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); }

​ 简单来说就是,通过Address.sol的低级调用方式,可以检测调用是否成功,且检测是否有返回值。当调用USDT的transfer函数时,如果执行成功,且return data = 0x,那么函数便可以执行,即跳过if的检测。

​ 同理 transferFrom函数也是如此。

4. ERC20系列数字货币 4.1 (Tether USD)USDT

​ 不遵循标准的ERC20协议,需要操作该代币时,建议使用SafeERC20.sol。

发行公司: 由 Tether Limited 发行,成立于 2014 年的香港公司。 市值: USDT 是市值最大的稳定币之一,截至 2022 年 7 月,市值超过 650 亿美元,占稳定币市场 50% 以上的份额。 挂钩: USDT 与美元 1:1 挂钩,据称有大量抵押品储备支持,包括现金、商业票据和商品。 历史: 最早作为 RealCoin 推出,后来于 2014 年 11 月更名为 Tether。然而,Tether 并不是没有争议的,曾因多次争议而备受关注,包括被指控误导投资者和缺乏对储备的透明度。 4.2 (USD Coin)USDC

​ 遵循标准的ERC20协议。源码链接:link。

发行公司: 由 Circle、Coinbase 和其他金融科技公司共同创立的财团 Center 是 USDC 的发行人。 市值: USDC 是按市值计算的第二大稳定币,截至 2022 年 7 月,市值超过 540 亿美元。 挂钩: 每个 USDC 与美元 1:1 挂钩,并由现金和美元等值资产支持。 安全性: USDC 被认为是一种更安全的价值储存手段,因为它有现金和现金等价物支持,而且受到美国监管。

这两种稳定币的比较:

流动性: USDT 的交易量更大,更广泛可用,但USDC 的交易量较低。 透明度: USDC 在透明度和监管方面表现较好,而 USDT 面临一些争议。 用途: USDT 在期货交易中很受欢迎,提供了更高的收益,而 USDC 是去中心化金融 (DeFi) 领域的首选,因为它被认为更安全。 4.3 其他 (Shiba Inu)SHIB:SHIB是一种基于以太坊的山寨币,算是狗狗币的一种替代品,遵循标准的ERC20协议。 Binance USD(BUSD):遵循标准的ERC20协议。 DAI Stablecoin (DAI):遵循标准的ERC20协议。 HEX (HEX):遵循标准的ERC20协议。 5. ERC20 extensions

来自 OpenZeppelin,链接。ERC4626单独xue'xi

5.1 ERC1363.sol

​ 这个拓展协议实现的功能是,用户在执行transfer、transferFrom和approve操作的时候,可以传入calldata,完成一些函数调用,或者是参数的传递,实现逻辑类似ERC721的 checkOnERC721Received()。

它的_checkOnTransferReceived()和_checkOnApprovalReceived()函数会分别去调用IERC1363Receiver(to).onTransferReceived,IERC1363Spender(spender).onApprovalReceived,这里会埋下被重入的安全隐患,在实用这个协议的时候需要注意这点。

5.2 ERC20Burnable.sol

​ 该合约提供了销币功能。

burn(uint256 value):销毁msg.sender的value个代币。 burnFrom(address account, uint256 value) :msg.sender销毁account的value个代币,前提是account给予msg.sender权限。 5.3 ERC20Capped.sol

我的理解是,给ERC20代币的的totalsupply盖帽子,也就是设置上限,设置某个ERC20代币的发行量不能超过cap。限制的逻辑在这里(from==0,则被检测为铸币操作):

if (from == address(0)) { uint256 maxSupply = cap(); uint256 supply = totalSupply(); if (supply > maxSupply) { revert ERC20ExceededCap(supply, maxSupply); } } 5.4 ERC20FlashMint.sol

​ 该合约提供了一个借贷功能,只能借该合约生成的代币,且最大接待额为:token == address(this) ? type(uint256).max - totalSupply() : 0;还需要支付fee,这个借贷函数不需要主动还款,因为ta采用的是burn操作,直接将你手中借来的token全部销毁。但是这不影响执行某些重入攻击,比如可以借钱去执行套利操作这类的,这是借贷函数的“通病”吧。

5.5 ERC20Pausable.sol

​ 该合约提供了一个紧急停止功能,在_update()函数加上whenNotPaused修饰符,当所有者暂停合约时,该合约生成的代币将不能执行一系列操作,如transfer、mint、transferFrom等。

5.6 ERC20Permit.sol

​ 该合约提供了一个新的授权操作,permit()函数的作用便是完成 owner对spender的授权,个人理解是,因为原ERC20中的approve函数必须是owner亲自去调用才能完成授权,这比较麻烦owner,而permit则是可以通过owner提供的签名来验证,并执行owner对spender的授权操作。

举个例子来对比,以开门为例:

原ERC20的授权方式:owner想让spender进屋拿东西,而owner需要亲自开门让他进去 ERC20Permit的授权方式:同样的示例,owner可以直接把家门口的钥匙给spender,让spender直接去开门拿东西就好了。 5.7 ERC20Votes.sol

​ 该合约支持类似Compound的投票和授权。

5.8 ERC20Wrapper.sol

​ 该合约支持代币的包装,用户可以存入和取出_underlying代币,存入多少_underlying代币,就可以铸造多少ERC20Wrapper代币,同理取出多少 _underlying代币,便会销毁多少ERC20Wrapper代币代币。

还提供了一个 _recover函数,该函数的作用是将错误转入该合约的 _underlying代币数量全部铸造为ERC20Wrapper代币,我对“错误转入”的理解是,没有通过depositFor()函数转入 _underlying代币。

最后,码字不易,点个赞呗~🤪

原创 学分: 115 分类: EIP/ERC 标签: ERC20  本文已由作者铸造成 NFT 网络: Polygon 合约地址: 0x6f772e254Ef50e9b462915b66404009c73766350 IPFS hash: bafkreia6p6mc23qoisbwqvurfglxe22teuks6ome6ssjjjwsnf53h4623a 查看TA的链上存证


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3