Skip to content

Commit 2e0478f

Browse files
committed
add ERC2771 context and forwarder
1 parent 56621fd commit 2e0478f

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts (last updated v4.5.0) (metatx/ERC2771Context.sol)
3+
4+
pragma solidity 0.8.13;
5+
6+
import '@openzeppelin/contracts/utils/Context.sol';
7+
8+
/**
9+
* @dev Context variant with ERC2771 support.
10+
*/
11+
abstract contract ERC2771Context is Context {
12+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
13+
address private immutable _trustedForwarder;
14+
15+
/// @custom:oz-upgrades-unsafe-allow constructor
16+
constructor(address trustedForwarder) {
17+
_trustedForwarder = trustedForwarder;
18+
}
19+
20+
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
21+
return forwarder == _trustedForwarder;
22+
}
23+
24+
function _msgSender() internal view virtual override returns (address sender) {
25+
if (isTrustedForwarder(msg.sender)) {
26+
// The assembly code is more direct than the Solidity version using `abi.decode`.
27+
assembly {
28+
sender := shr(96, calldataload(sub(calldatasize(), 20)))
29+
}
30+
} else {
31+
return super._msgSender();
32+
}
33+
}
34+
35+
function _msgData() internal view virtual override returns (bytes calldata) {
36+
if (isTrustedForwarder(msg.sender)) {
37+
return msg.data[:msg.data.length - 20];
38+
} else {
39+
return super._msgData();
40+
}
41+
}
42+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.8.13;
4+
5+
import '@openzeppelin/contracts/access/Ownable.sol';
6+
import '@openzeppelin/contracts/utils/math/SafeMath.sol';
7+
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
8+
import '@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol';
9+
10+
/**
11+
* @dev Simple minimal forwarder to be used together with an ERC2771 compatible contract. See {ERC2771Context}.
12+
*/
13+
contract Forwarder is Ownable, EIP712 {
14+
using ECDSA for bytes32;
15+
using SafeMath for uint256;
16+
17+
struct ForwardRequest {
18+
address from;
19+
address to;
20+
uint256 value;
21+
uint256 gas;
22+
uint256 nonce;
23+
bytes data;
24+
}
25+
26+
bytes32 private constant _TYPEHASH =
27+
keccak256('ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)');
28+
29+
mapping(address => uint256) private _nonces;
30+
31+
constructor(string memory name, string memory version) EIP712(name, version) {}
32+
33+
function getNonce(address from) public view returns (uint256) {
34+
return _nonces[from];
35+
}
36+
37+
function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {
38+
address signer = _hashTypedDataV4(
39+
keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data)))
40+
).recover(signature);
41+
return _nonces[req.from] == req.nonce && signer == req.from;
42+
}
43+
44+
function execute(ForwardRequest calldata req, bytes calldata signature)
45+
public
46+
payable
47+
onlyOwner
48+
returns (bool, bytes memory)
49+
{
50+
require(verify(req, signature), 'Forwarder: signature does not match request');
51+
_nonces[req.from] = req.nonce.add(1);
52+
53+
(bool success, bytes memory returndata) = req.to.call{ gas: req.gas, value: req.value }(
54+
abi.encodePacked(req.data, req.from)
55+
);
56+
57+
// Validate that the relayer has sent enough gas for the call.
58+
// See https://ronan.eth.link/blog/ethereum-gas-dangers/
59+
if (gasleft() <= req.gas / 63) {
60+
// We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since
61+
// neither revert or assert consume all gas since Solidity 0.8.0
62+
// https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require
63+
assembly {
64+
invalid()
65+
}
66+
}
67+
68+
return (success, returndata);
69+
}
70+
}

0 commit comments

Comments
 (0)