)
WTF Solidity 极简入门第 33 讲 ERC20 空投合约实战Airdrop 合约源码与 Remix 部署全流程【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity空投Airdrop是区块链项目方将代币免费发放给特定用户群体的常见营销策略而利用智能合约在一笔交易内批量向多个地址发送ERC20代币是提升空投效率的核心手段。本文以 WTF-Solidity 仓库 33_Airdrop/Airdrop.sol 为蓝本完整讲解空投合约的multiTransferToken、multiTransferETH实现原理、approve/transferFrom授权机制并带你走通从部署代币合约、授权到执行空投、验证余额的 Remix 实操全流程同时剖析其中的 DoS 安全风险与失败回滚兜底方案。空投Airdrop是什么在币圈空投是一种常见的营销策略项目方将代币免费发放给特定用户群体。为了拿到空投资格用户通常需要完成一些简单的任务如测试产品、分享新闻、介绍朋友等。项目方通过空投可以获得种子用户而用户可以获得一笔财富双方各取所需。由于每次接收空投的用户往往成百上千项目方不可能一笔一笔地手动转账。利用智能合约批量发放ERC20代币可以显著提高空投效率——一次交易Transaction内完成对多个地址的转账既节省 Gas 费也减少操作成本。前置知识本讲基于 31_ERC20 中讲解的IERC20接口transfer、approve、transferFrom、allowance等展开建议先掌握 ERC20 代币标准的基础概念再阅读本讲。空投合约源码逐段解析本讲的空投合约逻辑非常简洁利用循环在一笔交易内将ERC20代币发送给多个地址。完整源码位于 33_Airdrop/Airdrop.sol整个合约包含三个核心函数与一个辅助函数getSum()返回uint数组的元素之和multiTransferToken()批量发送ERC20代币空投multiTransferETH()批量发送 ETH 空投withdrawFromFailList()为 ETH 空投失败的用户提供主动取回资金的兜底操作。此外合约开头声明了一个关键的状态变量mapping(address uint) failTransferList;它用于记录 ETH 空投过程中转账失败的地址及对应金额为后续的失败补偿机制提供数据支撑。getSum()数组求和辅助函数getSum()是pure函数不读取也不修改链上状态仅对传入的uint256数组做累加求和返回总和// 数组求和函数 function getSum(uint256[] calldata _arr) public pure returns(uint sum){ for(uint i 0; i _arr.length; i) sum sum _arr[i]; }它在multiTransferToken()和multiTransferETH()中都被调用用于计算空投代币/ETH 的总量是后续require校验的核心依据。multiTransferToken()批量空投 ERC20 代币该函数是代币空投的主入口包含3个参数参数类型含义_tokenaddress要空投的ERC20代币合约地址_addressesaddress[]接收空投的用户地址数组_amountsuint256[]空投数量数组与_addresses一一对应/// notice 向多个地址转账ERC20代币使用前需要先授权 /// /// param _token 转账的ERC20代币地址 /// param _addresses 空投地址数组 /// param _amounts 代币数量数组每个地址的空投数量 function multiTransferToken( address _token, address[] calldata _addresses, uint256[] calldata _amounts ) external { // 检查_addresses和_amounts数组的长度相等 require(_addresses.length _amounts.length, Lengths of Addresses and Amounts NOT EQUAL); IERC20 token IERC20(_token); // 声明IERC合约变量 uint _amountSum getSum(_amounts); // 计算空投代币总量 // 检查授权代币数量 空投代币总量 require(token.allowance(msg.sender, address(this)) _amountSum, Need Approve ERC20 token); // for循环利用transferFrom函数发送空投 for (uint8 i; i _addresses.length; i) { token.transferFrom(msg.sender, _addresses[i], _amounts[i]); } }该函数有两个关键检查require数组长度一致性检查_addresses和_amounts两个数组的长度必须相等否则无法确定每个地址对应多少代币直接revert授权额度检查空投合约从调用者msg.sender账户转出代币必须保证token.allowance(msg.sender, address(this)) _amountSum即调用者已通过approve授权给空投合约的代币额度不小于空投代币总量否则整笔交易回滚。注意external关键字限定该函数只能由外部账户或其他合约调用不能由合约内部调用calldata表示数组参数只读地存储在调用数据区节省 Gas。底层原理——为什么需要先授权空投合约本身并不持有代币它使用transferFrom从调用者的账户中划转代币。根据 33_Airdrop/IERC20.sol 中定义的接口语义transferFrom(from, to, amount)会从from账户向to账户转账并扣除from授权给msg.sender此处即空投合约的额度。因此在调用multiTransferToken()之前代币持有者必须先行调用 ERC20 的approve(spender, amount)函数把空投合约加入授权名单并设置足够额度。这正是第 2 个require检查allowance的原因。multiTransferETH()批量空投 ETH如果项目方要空投的是原生资产 ETH而非ERC20代币则使用multiTransferETH()函数包含2个参数参数类型含义_addressesaddress payable[]接收空投的用户地址数组可接收 ETH_amountsuint256[]空投数量数组与_addresses一一对应/// 向多个地址转账ETH function multiTransferETH( address payable[] calldata _addresses, uint256[] calldata _amounts ) public payable { // 检查_addresses和_amounts数组的长度相等 require(_addresses.length _amounts.length, Lengths of Addresses and Amounts NOT EQUAL); uint _amountSum getSum(_amounts); // 计算空投ETH总量 // 检查转入ETH等于空投总量 require(msg.value _amountSum, Transfer amount error); // for循环利用transfer函数发送ETH for (uint256 i 0; i _addresses.length; i) { // 注释代码有Dos攻击风险, 并且transfer 也是不推荐写法 // Dos攻击 具体参考 https://github.com/AmazingAng/WTF-Solidity/blob/main/S09_DoS/readme.md // _addresses[i].transfer(_amounts[i]); (bool success, ) _addresses[i].call{value: _amounts[i]}(); if (!success) { failTransferList[_addresses[i]] _amounts[i]; } } }与代币空投类似ETH 空投同样先检查数组长度再通过require(msg.value _amountSum, Transfer amount error)校验调用者随交易转入的 ETH 恰好等于空投总量防止多转或少转。值得关注的是源码特意注释掉了旧式的转账写法// _addresses[i].transfer(_amounts[i]);并改用底层调用(bool success, ) _addresses[i].call{value: _amounts[i]}(); if (!success) { failTransferList[_addresses[i]] _amounts[i]; }这样设计有两个原因避免 DoS 攻击transfer仅提供 2300 Gas若接收方是合约且其receive/fallback逻辑消耗更多 Gas转账会失败并导致整个循环 revert一个坏地址即可让整笔空投交易失败形成单点 DoS具体攻击原理可参考仓库 S09_DoS/readme.md。而call会把剩余全部 Gas 转发出去并在失败时仅返回false而非抛出异常不会中断循环失败不整体回滚先记录再补偿call失败后合约把失败地址与金额写入failTransferList映射空投主流程继续执行其他用户的转账不受影响。withdrawFromFailList()失败转账的兜底取回由于失败金额记录在了failTransferList中合约还提供了主动补偿的入口让转账失败的地址能够自行取回对应 ETH// 给空投失败提供主动操作机会 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); }调用者必须是之前空投失败、被记入failTransferList的地址msg.sender合约校验其失败金额大于0后清零记录并通过call把等额 ETH 转出require(success, Fail withdraw)确保取回成功。这构成了失败记录 主动取回的完整兜底闭环避免了因个别地址异常导致资金永久滞留合约的风险。空投合约依赖的 IERC20 接口33_Airdrop/Airdrop.sol 顶部通过import ./IERC20.sol;引入 33_Airdrop/IERC20.sol 中定义的IERC20接口。空投逻辑只依赖其中三个函数allowance(address owner, address spender)查询owner授权给spender的额度用于空投前的授权校验transferFrom(address from, address to, uint256 amount)从from划转代币给to并扣除调用者的授权额度是批量空投的执行原语配套的approve与Transfer/Approval事件在授权环节由代币持有者调用/触发。在同一个 Airdrop.sol 文件中还附带实现了一个简化版ERC20合约balanceOf、allowance、totalSupply、transfer、approve、transferFrom、mint、burn用于本地演示空投流程先mint给自己 10000 单位代币再approve授权给空投合约最后由空投合约transferFrom批量转出。生产环境中建议使用经过审计的 OpenZeppelin 实现。空投实践Remix 全流程演示下面以 Remix IDE 为例走通部署代币 → 铸造 → 部署空投合约 → 授权 → 执行空投 → 验证余额的完整链路合约文件为 33_Airdrop/Airdrop.sol其中同时包含演示用ERC20与Airdrop两个合约。第 1 步部署 ERC20 代币合约并铸造代币在 Remix 中编译并选择ERC20合约进行部署构造函数需传入代币名称与符号例如name_: WTF、symbol_: WTF部署完成后调用mint函数给自己铸造 10000 单位代币注意该演示合约的decimals为 1810000 对应 1e22 的最小单位数值第 2 步部署 Airdrop 空投合约选择Airdrop合约并部署该合约构造函数无参数第 3 步授权 Airdrop 合约利用ERC20代币合约中的approve()函数spender填入Airdrop合约地址amount填入10000给空投合约授权 10000 单位代币只有完成这一步multiTransferToken()内的token.allowance(msg.sender, address(this)) _amountSum校验才能通过。第 4 步执行 multiTransferToken 进行空投调用Airdrop合约的multiTransferToken()函数参数按下表填写参数填写内容_token第 1 步部署的ERC20代币合约地址_addresses接收空投的地址数组示例填写两个地址_amounts与地址一一对应的数量数组参考示例// _addresses填写 [0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] // _amounts填写 [100, 200]执行交易后Remix 的交易日志中会出现两次Transfer事件分别向两个地址转账 100 与 200 单位代币证明空投成功第 5 步查询余额验证空投结果最后利用ERC20合约的balanceOf()函数分别查询上面两个用户地址的代币余额余额成功变为100和200空投成功安全要点与工程建议授权是代币空投的前提空投合约本身不持有资产资金从调用者账户划转务必在调用multiTransferToken()前完成approve且授权额度需覆盖空投总量。警惕 DoS 与transfer弃用向合约地址转账 ETH 时使用transfer固定 2300 Gas存在被恶意合约拒绝而导致整批空投失败的风险推荐使用call{value: ...}并配合failTransferList失败清单 withdrawFromFailList兜底取回机制详见仓库 S09_DoS/readme.md。批量操作的气体考量空投地址数量越多单笔交易 Gas 消耗越高循环内有大量外部调用需在合约部署与调用前评估区块 Gas 上限当地址规模极大时可考虑分批空投或 Merkle Tree 白名单等更高效的空投方案。依赖标准的可靠性生产环境建议使用审计过的 OpenZeppelinERC20实现并考虑引入重入防护、暂停机制等安全模块避免使用演示级实现直接上线。总结这一讲我们介绍了如何使用Solidity编写ERC20代币空投合约通过getSum汇总金额、require双重校验保证数据一致与授权充足、transferFrom循环实现单笔交易批量转账并基于callfailTransferList解决 ETH 空投中的 DoS 风险与失败补偿问题。配合 Remix 完成部署代币 → mint → 部署空投合约 → approve → multiTransferToken → balanceOf 验证的完整实践后你已掌握用智能合约实现高效代币空投的核心能力。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考