-
Notifications
You must be signed in to change notification settings - Fork 212
/
Copy pathForwarder.sol
199 lines (166 loc) · 6.75 KB
/
Forwarder.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
// solhint-disable not-rely-on-time
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
pragma abicoder v2;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
// #if ENABLE_CONSOLE_LOG
import "hardhat/console.sol";
// #endif
import "./IForwarder.sol";
/**
* @title The Forwarder Implementation
* @notice This implementation of the `IForwarder` interface uses ERC-712 signatures and stored nonces for verification.
*/
contract Forwarder is IForwarder, ERC165 {
using ECDSA for bytes32;
address private constant DRY_RUN_ADDRESS = 0x0000000000000000000000000000000000000000;
string public constant GENERIC_PARAMS = "address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data,uint256 validUntilTime";
string public constant EIP712_DOMAIN_TYPE = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
mapping(bytes32 => bool) public typeHashes;
mapping(bytes32 => bool) public domains;
// Nonces of senders, used to prevent replay attacks
mapping(address => uint256) private nonces;
// solhint-disable-next-line no-empty-blocks
receive() external payable {}
/// @inheritdoc IForwarder
function getNonce(address from)
public view override
returns (uint256) {
return nonces[from];
}
constructor() {
string memory requestType = string(abi.encodePacked("ForwardRequest(", GENERIC_PARAMS, ")"));
registerRequestTypeInternal(requestType);
}
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
return interfaceId == type(IForwarder).interfaceId ||
super.supportsInterface(interfaceId);
}
/// @inheritdoc IForwarder
function verify(
ForwardRequest calldata req,
bytes32 domainSeparator,
bytes32 requestTypeHash,
bytes calldata suffixData,
bytes calldata sig)
external override view {
_verifyNonce(req);
_verifySig(req, domainSeparator, requestTypeHash, suffixData, sig);
}
/// @inheritdoc IForwarder
function execute(
ForwardRequest calldata req,
bytes32 domainSeparator,
bytes32 requestTypeHash,
bytes calldata suffixData,
bytes calldata sig
)
external payable
override
returns (bool success, bytes memory ret) {
_verifySig(req, domainSeparator, requestTypeHash, suffixData, sig);
_verifyAndUpdateNonce(req);
require(req.validUntilTime == 0 || req.validUntilTime > block.timestamp, "FWD: request expired");
uint256 gasForTransfer = 0;
if ( req.value != 0 ) {
gasForTransfer = 40000; //buffer in case we need to move eth after the transaction.
}
bytes memory callData = abi.encodePacked(req.data, req.from);
require(gasleft()*63/64 >= req.gas + gasForTransfer, "FWD: insufficient gas");
// solhint-disable-next-line avoid-low-level-calls
(success,ret) = req.to.call{gas : req.gas, value : req.value}(callData);
// #if ENABLE_CONSOLE_LOG
console.log("execute result: success: %s ret:", success);
console.logBytes(ret);
// #endif
if ( req.value != 0 && address(this).balance>0 ) {
// can't fail: req.from signed (off-chain) the request, so it must be an EOA...
payable(req.from).transfer(address(this).balance);
}
return (success,ret);
}
function _verifyNonce(ForwardRequest calldata req) internal view {
require(nonces[req.from] == req.nonce, "FWD: nonce mismatch");
}
function _verifyAndUpdateNonce(ForwardRequest calldata req) internal {
require(nonces[req.from]++ == req.nonce, "FWD: nonce mismatch");
}
/// @inheritdoc IForwarder
function registerRequestType(string calldata typeName, string calldata typeSuffix) external override {
for (uint256 i = 0; i < bytes(typeName).length; i++) {
bytes1 c = bytes(typeName)[i];
require(c != "(" && c != ")", "FWD: invalid typename");
}
string memory requestType = string(abi.encodePacked(typeName, "(", GENERIC_PARAMS, ",", typeSuffix));
registerRequestTypeInternal(requestType);
}
/// @inheritdoc IForwarder
function registerDomainSeparator(string calldata name, string calldata version) external override {
uint256 chainId;
/* solhint-disable-next-line no-inline-assembly */
assembly { chainId := chainid() }
bytes memory domainValue = abi.encode(
keccak256(bytes(EIP712_DOMAIN_TYPE)),
keccak256(bytes(name)),
keccak256(bytes(version)),
chainId,
address(this));
bytes32 domainHash = keccak256(domainValue);
domains[domainHash] = true;
emit DomainRegistered(domainHash, domainValue);
}
function registerRequestTypeInternal(string memory requestType) internal {
bytes32 requestTypehash = keccak256(bytes(requestType));
typeHashes[requestTypehash] = true;
emit RequestTypeRegistered(requestTypehash, requestType);
}
function _verifySig(
ForwardRequest calldata req,
bytes32 domainSeparator,
bytes32 requestTypeHash,
bytes calldata suffixData,
bytes calldata sig)
internal
virtual
view
{
require(domains[domainSeparator], "FWD: unregistered domain sep.");
require(typeHashes[requestTypeHash], "FWD: unregistered typehash");
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01", domainSeparator,
keccak256(_getEncoded(req, requestTypeHash, suffixData))
));
// solhint-disable-next-line avoid-tx-origin
require(tx.origin == DRY_RUN_ADDRESS || digest.recover(sig) == req.from, "FWD: signature mismatch");
}
/**
* @notice Creates a byte array that is a valid ABI encoding of a request of a `RequestType` type. See `execute()`.
*/
function _getEncoded(
ForwardRequest calldata req,
bytes32 requestTypeHash,
bytes calldata suffixData
)
public
pure
returns (
bytes memory
) {
// we use encodePacked since we append suffixData as-is, not as dynamic param.
// still, we must make sure all first params are encoded as abi.encode()
// would encode them - as 256-bit-wide params.
return abi.encodePacked(
requestTypeHash,
uint256(uint160(req.from)),
uint256(uint160(req.to)),
req.value,
req.gas,
req.nonce,
keccak256(req.data),
req.validUntilTime,
suffixData
);
}
}