-
Notifications
You must be signed in to change notification settings - Fork 213
/
Copy pathForwarder.sol
167 lines (137 loc) · 5.52 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
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
pragma abicoder v2;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./IForwarder.sol";
contract Forwarder is IForwarder {
using ECDSA for bytes32;
string public constant GENERIC_PARAMS = "address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data,uint256 validUntil";
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 {}
function getNonce(address from)
public view override
returns (uint256) {
return nonces[from];
}
constructor() {
string memory requestType = string(abi.encodePacked("ForwardRequest(", GENERIC_PARAMS, ")"));
registerRequestTypeInternal(requestType);
}
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);
}
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.validUntil == 0 || req.validUntil > block.number, "FWD: request expired");
uint 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 ( 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");
}
function registerRequestType(string calldata typeName, string calldata typeSuffix) external override {
for (uint 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);
}
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
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))
));
require(digest.recover(sig) == req.from, "FWD: signature mismatch");
}
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.validUntil,
suffixData
);
}
}