尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

WTF-Solidity 第 33 讲深度解析:智能合约 ERC20 空投合约(Airdrop Contract)源码级实战

WTF-Solidity 第 33 讲深度解析:智能合约 ERC20 空投合约(Airdrop Contract)源码级实战 WTF-Solidity 第 33 讲深度解析智能合约 ERC20 空投合约Airdrop Contract源码级实战【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity本篇文章基于 WTF-Solidity 极简入门教程第 33 讲English 版文档展开。空投Airdrop是加密行业中项目方将代币免费发放给特定用户群体的营销策略而利用智能合约一次交易批量向大量地址发送ERC20代币是显著提升空投效率的关键做法。读完本文你将掌握Airdrop空投合约的完整设计思路、getSum/multiTransferToken/multiTransferETH三个核心函数的实现细节、approvetransferFrom授权转账机制的底层原理并能在 Remix 中完成一次完整的 ERC20 代币空投实战同时了解空投场景中常见的 DoS 安全风险与防御手段。一、空投Airdrop与智能合约批量转账空投是币圈一种常见的营销策略项目方将代币免费发放给特定用户群体。为了获得空投资格用户通常需要完成一些简单任务如测试产品、分享新闻、推荐朋友等。项目方通过空投获得种子用户用户则获得一笔财富是双赢的局面。由于一次空投往往涉及成百上千个接收地址项目方不可能一笔一笔地手动转账。借助智能合约在一笔交易内通过循环将ERC20代币批量发送给多个地址可以显著提高空投效率——这正是本讲Airdrop合约的核心价值。对应源码位于 33_Airdrop/Airdrop.sol英文版见 Languages/en/33_Airdrop_en/Airdrop.sol。二、空投合约核心函数设计Airdrop合约的逻辑非常简单利用for循环一笔交易把ERC20代币发送给多个地址。合约核心包含三个函数数组求和的getSum()、发送 ERC20 空投的multiTransferToken()以及发送 ETH 空投的multiTransferETH()。2.1 getSum()计算空投总量getSum()是纯函数pure对传入的uint256数组求和用于计算一次空投的代币总量供后续授权额度校验使用// sum function for arrays function getSum(uint256[] calldata _arr) public pure returns(uint sum) { for(uint i 0; i _arr.length; i) sum sum _arr[i]; }实现细节要点使用calldata修饰数组参数避免将数据从 calldata 复制到 memory 的开销降低 gas 消耗声明为public pure不读取也不修改状态变量可被外部直接调用也可被合约内部函数复用源码位置。2.2 multiTransferToken()发送 ERC20 代币空投该函数负责向多个地址批量转账ERC20代币共 3 个参数参数类型含义_tokenaddress要空投的 ERC20 代币合约地址_addressesaddress[]接收空投的用户地址数组_amountsuint256[]空投数量数组与_addresses中每个地址一一对应/// notice Transfer ERC20 tokens to multiple addresses, authorization is required before use /// /// param _token The address of ERC20 token for transfer /// param _addresses The array of airdrop addresses /// param _amounts The array of amount of tokens (airdrop amount for each address) function multiTransferToken( address _token, address[] calldata _addresses, uint256[] calldata _amounts ) external { // Check: The length of _addresses array should be equal to the length of _amounts array require(_addresses.length _amounts.length, Lengths of Addresses and Amounts NOT EQUAL); IERC20 token IERC20(_token); // Declare IERC contract variable uint _amountSum getSum(_amounts); // Calculate the total amount of airdropped tokens // Check: The authorized amount of tokens should be greater than or equal to the total amount of airdropped tokens require(token.allowance(msg.sender, address(this)) _amountSum, Need Approve ERC20 token); // for loop, use transferFrom function to send airdrops for (uint8 i; i _addresses.length; i) { token.transferFrom(msg.sender, _addresses[i], _amounts[i]); } }函数体包含两道关键检查require数组长度一致性检查_addresses.length _amounts.length。保证每个空投地址都有对应的数量避免数组错位导致的逻辑错误授权额度检查token.allowance(msg.sender, address(this)) _amountSum。_amountSum由getSum(_amounts)计算得出。因为合约最终调用的是transferFrom代币从msg.sender账户扣除所以空投发起人必须先调用代币合约的approve()授权给Airdrop合约且授权额度必须不小于空投总量。检查通过后进入for循环逐一调用token.transferFrom(msg.sender, _addresses[i], _amounts[i])完成批量转账源码位置。2.3 multiTransferETH()发送 ETH 空投除了 ERC20 代币合约也支持批量发送原生 ETH共 2 个参数参数类型含义_addressesaddress payable[]接收空投的用户地址数组payable表示可接收 ETH_amountsuint256[]空投数量数组与地址一一对应/// Transfer ETH to multiple addresses function multiTransferETH( address payable[] calldata _addresses, uint256[] calldata _amounts ) public payable { // Check: _addresses and _amounts arrays should have the same length require(_addresses.length _amounts.length, Lengths of Addresses and Amounts NOT EQUAL); // Calculate total amount of ETH to be airdropped uint _amountSum getSum(_amounts); // Check: transferred ETH should equal total amount require(msg.value _amountSum, Transfer amount error); // Use a for loop to transfer ETH using transfer function for (uint256 i 0; i _addresses.length; i) { _addresses[i].transfer(_amounts[i]); } }与代币空投不同ETH 空投是payable函数空投的 ETH 由调用者通过msg.value随交易一起转入合约因此校验逻辑变为msg.value _amountSum英文版源码。三、源码级原理剖析授权转账机制与安全增强3.1 IERC20 接口与 allowance 授权机制multiTransferToken中使用IERC20 token IERC20(_token)将地址转换为接口引用再调用接口方法。IERC20接口定义了 ERC20 标准的全部必要函数与事件见 33_Airdrop/IERC20.solfunction allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool);Airdrop合约同文件下还附带了一个简化版ERC20实现33_Airdrop/Airdrop.sol#L81-L144其底层逻辑可以清晰印证整个授权-转账闭环approve()设置allowance[msg.sender][spender] amount即“所有者”授权“调用者”可代为花费的代币额度并释放Approval事件transferFrom()先从allowance[sender][msg.sender]中扣减授权额度再从balanceOf[sender]中扣减余额、给recipient增加余额并释放Transfer事件。因此整个空投流程的资金走向是空投发起人msg.sender→ approve 授权 → Airdrop 合约调用 transferFrom → 各接收地址。Airdrop合约自身并不持有代币只是“代扣代发”的中转者这正是空投合约不需要预先锁仓资金的原因。若读者想深入了解 ERC20 标准可继续学习本仓库 31_ERC20 一讲。3.2 从源码结构看中文版合约的安全增强需要特别说明本仓库的 中文版 Airdrop.sol 相比英文版额外做了 DoS 安全加固英文版文档对应的 Languages/en/33_Airdrop_en/Airdrop.sol 仍使用transfer循环转账。从源码结构看中文版合约引入了三个增强点mapping(address uint) failTransferList; // 记录转账失败的地址与金额 for (uint256 i 0; i _addresses.length; i) { // 注释代码有Dos攻击风险, 并且transfer 也是不推荐写法 // _addresses[i].transfer(_amounts[i]); (bool success, ) _addresses[i].call{value: _amounts[i]}(); if (!success) { failTransferList[_addresses[i]] _amounts[i]; } } // 给空投失败提供主动操作机会 function withdrawFromFailList(address _to) public { uint failAmount failTransferList[msg.sender]; require(failAmount 0, You are not in failed list); failTransferList[msg.sender] 0; (bool success, ) _to.call{value: failAmount}(); require(success, Fail withdraw); }改用底层call代替transfertransfer固定只转发 2300 gas且目标地址的回调一旦失败会整体回滚而call允许返回成功布尔值便于逐笔处理失败源码位置失败清单failTransferList单笔转账失败时记录地址与金额不让整个循环回滚避免“一颗老鼠屎坏了一锅粥”withdrawFromFailList()主动领取失败的接收方可以自行调用该函数领回属于自己的金额符合“pull 优于 push”的安全实践源码位置。3.3 为什么循环转账存在 DoS 风险智能合约的循环批量转账是典型的 DoS拒绝服务漏洞温床。若用transfer/强制require(success)的方式逐笔推送任何一个恶意合约地址在fallback中revert都会让整笔空投交易失败资金被卡在合约中。这与本仓库 S09_DoS 合约安全专题 中 Akutar 项目因退款循环中被恶意合约卡死、上万个 ETH 被永久锁仓的案例同源。因此空投合约的推荐做法是单笔失败不阻断整体并让用户自行领取pull而非批量推送push。四、Remix 实操完成一次 ERC20 代币空投下面在 Remix IDE 中完整走一遍空投流程5 个步骤使用的合约即 Airdrop.sol内含Airdrop与简化版ERC20两个合约。步骤 1部署 ERC20 代币合约并给自己 mint 10000 单位代币。在编译环境中分别选择ERC20合约进行部署构造函数填入名称WTF与符号WTF部署成功后调用mint函数amount填入10000为自己铸造 1 万枚代币步骤 2部署 Airdrop 空投合约。在合约下拉列表中选择Airdrop合约并部署得到空投合约地址步骤 3调用 ERC20 合约的approve()给 Airdrop 合约授权 10000 单位代币。spender填入空投合约地址amount填入10000。这一步是第 2.2 节中allowance检查能够通过的前提步骤 4调用 Airdrop 合约的multiTransferToken()执行空投。参数按如下填写以 Remix 自带测试账号为例_token填入 ERC20 代币合约地址_addresses接收空投的用户地址数组[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4]_amounts对应的空投数量数组[100, 200]步骤 5调用 ERC20 合约的balanceOf()验证空投结果。查询上述用户地址的代币余额若分别变为100和200说明空投成功五、空投合约实践要点与注意事项授权先于空投multiTransferToken依赖approvetransferFrom的授权转账机制发起空投前必须先授权且额度充足否则第二个require会以Need Approve ERC20 token报错回滚数组长度必须一致_addresses与_amounts长度不一致会直接回滚Lengths of Addresses and Amounts NOT EQUAL这是防止空投金额错位的第一道防线ETH 空投需精确配平multiTransferETH要求msg.value与_amountSum完全相等多余或不足都会回滚警惕循环转账 DoS接收地址中若混入恶意合约fallback中revert强制推送式转账会让整笔空投失败应像中文版源码那样使用call 失败清单并支持失败方主动领取gas 与成本控制空投地址越多、单笔交易 gas 越高需要权衡每批空投的地址数量必要时分批执行。六、总结本讲介绍了如何使用 Solidity 编写ERC20代币空投合约通过getSum()计算空投总量借助multiTransferToken()与multiTransferETH()两个循环转账函数一次交易即可向大量地址批量分发代币或 ETH极大提升空投效率同时深入剖析了IERC20接口下的allowance/approve/transferFrom授权转账原理以及循环转账场景下 DoS 漏洞的成因与失败清单、主动领取等防御方案。从部署代币合约、铸造、授权到执行空投、验证余额完整闭环均可在 Remix 中复现可作为后续接入 Merkle 白名单空投、NFT 空投等更复杂方案参考 36_MerkleTree的基础。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表