/
MevWalletV1.sol
231 lines (209 loc) · 7.57 KB
/
MevWalletV1.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
222
223
224
225
226
227
228
229
230
231
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import {Mevitize} from "mev-weth/Mevitize.sol";
contract MevWalletV1 is Mevitize {
error ProvideValue(uint256); // 0x73883387
error HighBaseFee(uint256); // 0x74878d58
error WrongSigner(address); // 0x32c15fc2
error Reverted(bytes); // 0xa8159920
error NotBefore(uint64); // 0x08567e55
error UsedNonce(uint256); // 0x6ac964b0
error MissingNonce(uint256); // 0x299aa731
error PermanentlyInvalid(); // 0xa04d981f
// 0xbcf6a68a2f901be4a23a41b53acd7697893a7e34def4e28acba584da75283b67
event Executed(uint256 indexed nonce);
// 0x5679fb6ec38d3c67731b4def49181a8fbbb334cda5c263b0993e50cfe699d4e8
bytes32 public constant TX_TYPEHASH = keccak256(
"MevTx(address to,bytes data,int256 value,bool delegate,int256 tip,uint256 maxBaseFee,uint256 timing,uint256 nonce)"
);
bytes32 public _DOMAIN_SEPARATOR;
address public owner;
uint256 public nonce;
fallback() external payable {}
receive() external payable {}
constructor() {
owner = address(0xff); // factor that, jerks
}
/**
* @notice initializes the owner and domain separator
*/
function initialize(address newOwner) public {
require(owner == address(0));
// Enforced because contracts cannot produce signatures
uint256 s;
assembly {
s := extcodesize(newOwner)
}
require(s == 0, "No contract owner");
owner = newOwner;
_DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("MevTx"),
keccak256(bytes("1")),
block.chainid,
address(this)
)
);
}
/**
* @notice onlyOwner does what it says on the tin
*/
modifier onlyOwner() {
// we allow address(this) so that the wallet can be administered with
// its own meta-tx
require(msg.sender == owner || msg.sender == address(this));
_;
}
/**
* @notice transferOwnership does what it says on the tin
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0) && newOwner != address(this));
uint256 s;
// Enforced because contracts cannot produce signatures
assembly {
s := extcodesize(newOwner)
}
require(s == 0, "No contract owner");
owner = newOwner;
}
/**
* @notice checks the EIP-712 signsture
*/
function check712(
address to,
bytes memory data,
int256 value,
bool delegate,
int256 tip,
uint256 maxBaseFee,
uint256 timing,
uint256 n,
uint8 v,
bytes32 r,
bytes32 s
) internal view {
bytes32 hashStruct =
keccak256(abi.encode(TX_TYPEHASH, to, keccak256(data), value, delegate, tip, maxBaseFee, timing, n));
bytes32 h = keccak256(abi.encodePacked("\x19\x01", _DOMAIN_SEPARATOR, hashStruct));
address signer = ecrecover(h, v, r, s);
// signature must be valid
if (signer == address(0)) revert PermanentlyInvalid();
// signature must be from owner
if (signer != owner) revert WrongSigner(signer);
}
/**
* @notice checks that the basefee is in user-acceptable range.
*/
function checkBaseFee(uint256 maxBaseFee) internal view {
// if there's a limit on the basefee, it cannot be over that limit
if (maxBaseFee != 0 && block.basefee > maxBaseFee) revert HighBaseFee(maxBaseFee);
}
/**
* @notice checks that the block timestamp is in user-acceptable range.
*/
function checkTiming(uint256 timing) internal view {
// Timing is encoded as `notBefore << 64 | notAfter`
uint64 time = uint64(block.timestamp);
uint64 notAfter = uint64(timing);
uint64 notBefore = uint64(timing >> 64);
// if notAfter is non-zero, timestamp cannot be after it
if (notAfter != 0 && time > notAfter) {
revert PermanentlyInvalid();
}
// if notBefore is non-zero, timestamp cannot be before it
if (notBefore != 0 && time < notBefore) {
revert NotBefore(notBefore);
}
}
/**
* @notice checks that the value is as user specified.
*/
function checkValue(bool delegate, int256 value) internal view {
// value cannot be negative
if (value < 0) revert PermanentlyInvalid();
// delegate calls cannot have value
if (delegate && value != 0) revert PermanentlyInvalid();
unchecked {
// cast checked by previous if statement
uint256 val = uint256(value);
uint256 bal = address(this).balance;
uint256 msgVal = msg.value;
// becase bal cannot be less than msgval
uint256 preBal = bal - msgVal;
// if the value BEFORE getting the Searcher's input ETH was
// sufficient, don't allow input ETH. This prevents the Searcher
// from converting wallet MevWeth into ETH by providing extra value
if (preBal >= val && msgVal != 0) revert ProvideValue(0);
// if the value BEFORE getting the Searcher's input ETH was
// insufficient, require the msg has the exact value necessary
if (preBal < val) {
// checked by the if statement
uint256 deficit = val - preBal;
if (msgVal != deficit) revert ProvideValue(deficit);
}
}
}
/**
* @notice checks that the nonce is correct
*/
function checkNonce(uint256 n) internal view {
uint256 _nonce = nonce;
// Nonce cannot be
if (n < _nonce) revert UsedNonce(_nonce);
if (n > _nonce) revert MissingNonce(_nonce);
// pass if equal
}
/**
* @notice executes the meta-tx
*/
function execute(address to, bytes memory data, uint256 value, bool delegate) internal {
bool success;
// overwrite data because we don't need it anymore
if (delegate) {
(success, data) = to.delegatecall(data);
} else {
(success, data) = to.call{value: value}(data);
}
// okay this seems crazy but hear me out
// MEV block builders already drop reverting txns.
// This just makes it so they never get included at all
// which is desirable for a metatx
if (!success) {
revert Reverted(data);
}
}
/**
* @notice execute a MEV-driven meta-transaction
*/
function mevTx(
address to,
bytes memory data,
int256 value,
bool delegate,
int256 tip,
uint256 maxBaseFee,
uint256 timing,
uint256 n,
uint8 v,
bytes32 r,
bytes32 s
) external payable subsidize(tip + int256(msg.value)) {
// check sig first, as this is most likely to produce reverts
check712(to, data, value, delegate, tip, maxBaseFee, timing, n, v, r, s);
// other condition of use checks
if (to == address(0)) revert PermanentlyInvalid();
checkBaseFee(maxBaseFee);
checkTiming(timing);
checkValue(delegate, value);
checkNonce(n);
// re-entrancy protection
nonce = type(uint256).max;
// execute the tx
execute(to, data, uint256(value), delegate);
// emit executed, and incement nonce
nonce = n + 1;
emit Executed(n);
}
}