Skip to content

Commit

Permalink
Merge pull request AmazingAng#519 from joeyzone/main
Browse files Browse the repository at this point in the history
修复: 第33讲:空投 代码中有Dos攻击风险
  • Loading branch information
AmazingAng authored Jul 10, 2023
2 parents c9b9963 + fc3ef57 commit 790d08c
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 85 deletions.
66 changes: 46 additions & 20 deletions 33_Airdrop/Airdrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import "./IERC20.sol"; //import IERC20

/// @notice 向多个地址转账ERC20代币
contract Airdrop {
mapping(address => uint) failTransferList;

/// @notice 向多个地址转账ERC20代币,使用前需要先授权
///
/// @param _token 转账的ERC20代币地址
Expand All @@ -15,14 +17,20 @@ contract Airdrop {
address _token,
address[] calldata _addresses,
uint256[] calldata _amounts
) external {
) external {
// 检查:_addresses和_amounts数组的长度相等
require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL");
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");

require(
token.allowance(msg.sender, address(this)) > _amountSum,
"Need Approve ERC20 token"
);

// for循环,利用transferFrom函数发送空投
for (uint256 i; i < _addresses.length; i++) {
token.transferFrom(msg.sender, _addresses[i], _amounts[i]);
Expand All @@ -35,55 +43,74 @@ contract Airdrop {
uint256[] calldata _amounts
) public payable {
// 检查:_addresses和_amounts数组的长度相等
require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL");
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++) {
_addresses[i].transfer(_amounts[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];
}
}
}

// 给空投失败提供主动操作机会
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");
}

// 数组求和函数
function getSum(uint256[] calldata _arr) public pure returns(uint sum)
{
for(uint i = 0; i < _arr.length; i++)
sum = sum + _arr[i];
function getSum(uint256[] calldata _arr) public pure returns (uint sum) {
for (uint i = 0; i < _arr.length; i++) sum = sum + _arr[i];
}
}


// ERC20代币合约
contract ERC20 is IERC20 {

mapping(address => uint256) public override balanceOf;

mapping(address => mapping(address => uint256)) public override allowance;

uint256 public override totalSupply; // 代币总供给
uint256 public override totalSupply; // 代币总供给

string public name; // 名称
string public symbol; // 符号

string public name; // 名称
string public symbol; // 符号

uint8 public decimals = 18; // 小数位数

constructor(string memory name_, string memory symbol_){
constructor(string memory name_, string memory symbol_) {
name = name_;
symbol = symbol_;
}

// @dev 实现`transfer`函数,代币转账逻辑
function transfer(address recipient, uint amount) external override returns (bool) {
function transfer(
address recipient,
uint amount
) external override returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}

// @dev 实现 `approve` 函数, 代币授权逻辑
function approve(address spender, uint amount) external override returns (bool) {
function approve(
address spender,
uint amount
) external override returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
Expand Down Expand Up @@ -115,5 +142,4 @@ contract ERC20 is IERC20 {
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}

}
138 changes: 73 additions & 65 deletions 33_Airdrop/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ tags:
- airdrop
---

# WTF Solidity极简入门: 33. 发送空投
# WTF Solidity 极简入门: 33. 发送空投

我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲
我最近在重新学 solidity,巩固一下细节,也写一个“WTF Solidity 极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲

欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science)

欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk)
欢迎加入 WTF 科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk)

所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity)
所有代码和教程开源在 github(1024 个 star 发课程认证,2048 个 star 发社群 NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity)

-----
---

在币圈,最开心的一件事就是领空投,空手套白狼。这一讲,我们将学习如何使用智能合约发送`ERC20`代币空投。

Expand All @@ -34,68 +34,76 @@ tags:

- `getSum()`函数:返回`uint`数组的和。

```solidity
// 数组求和函数
function getSum(uint256[] calldata _arr) public pure returns(uint sum)
{
for(uint i = 0; i < _arr.length; i++)
sum = sum + _arr[i];
}
```
```solidity
// 数组求和函数
function getSum(uint256[] calldata _arr) public pure returns(uint sum)
{
for(uint i = 0; i < _arr.length; i++)
sum = sum + _arr[i];
}
```

- `multiTransferToken()`函数:发送`ERC20`代币空投,包含`3`个参数:
- `_token`:代币合约地址(`address`类型)
- `_addresses`:接收空投的用户地址数组(`address[]`类型)
- `_amounts`:空投数量数组,对应`_addresses`里每个地址的数量(`uint[]`类型)
该函数有两个检查:第一个`require`检查了`_addresses`和`_amounts`两个数组长度是否相等;第二个`require`检查了空投合约的授权额度大于要空投的代币数量总和。
```solidity
/// @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]);
}
}
```

- `_token`:代币合约地址(`address`类型)
- `_addresses`:接收空投的用户地址数组(`address[]`类型)
- `_amounts`:空投数量数组,对应`_addresses`里每个地址的数量(`uint[]`类型)

该函数有两个检查:第一个`require`检查了`_addresses``_amounts`两个数组长度是否相等;第二个`require`检查了空投合约的授权额度大于要空投的代币数量总和。

```solidity
/// @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]);
}
}
```

- `multiTransferETH()`函数:发送`ETH`空投,包含`2`个参数:
- `_addresses`:接收空投的用户地址数组(`address[]`类型)
- `_amounts`:空投数量数组,对应`_addresses`里每个地址的数量(`uint[]`类型)
```solidity
/// 向多个地址转账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++) {
_addresses[i].transfer(_amounts[i]);
}
}
```

- `_addresses`:接收空投的用户地址数组(`address[]`类型)
- `_amounts`:空投数量数组,对应`_addresses`里每个地址的数量(`uint[]`类型)

```solidity
/// 向多个地址转账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];
}
}
}
```

### 空投实践

Expand All @@ -109,7 +117,7 @@ tags:

![部署`Airdrop`合约](./img/33-3.png)

3. 利用`ERC20`代币合约中的`approve()`函数,给`Airdrop`空投合约授权10000 单位代币。
3. 利用`ERC20`代币合约中的`approve()`函数,给`Airdrop`空投合约授权 10000 单位代币。

![授权`Airdrop`合约](./img/33-4.png)

Expand All @@ -131,4 +139,4 @@ tags:

## 总结

这一讲,我们介绍了如何使用`solidity`写`ERC20`代币空投合约,极大增加空投效率。我撸空投收获最大的一次是`ENS`空投,你们呢?
这一讲,我们介绍了如何使用`solidity``ERC20`代币空投合约,极大增加空投效率。我撸空投收获最大的一次是`ENS`空投,你们呢?

0 comments on commit 790d08c

Please sign in to comment.