Minimal support for ERC2771 (GSNv2) (#2508)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
This commit is contained in:
@ -72,9 +72,9 @@ library ECDSA {
|
||||
|
||||
/**
|
||||
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
|
||||
* replicates the behavior of the
|
||||
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
|
||||
* JSON-RPC method.
|
||||
* produces hash corresponding to the one signed with the
|
||||
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
|
||||
* JSON-RPC method as part of EIP-191.
|
||||
*
|
||||
* See {recover}.
|
||||
*/
|
||||
@ -83,4 +83,17 @@ library ECDSA {
|
||||
// enforced by the type signature above
|
||||
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns an Ethereum Signed Typed Data, created from a
|
||||
* `domainSeparator` and a `structHash`. This produces hash corresponding
|
||||
* to the one signed with the
|
||||
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
|
||||
* JSON-RPC method as part of EIP-712.
|
||||
*
|
||||
* See {recover}.
|
||||
*/
|
||||
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
|
||||
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../cryptography/ECDSA.sol";
|
||||
|
||||
/**
|
||||
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
|
||||
*
|
||||
@ -95,6 +97,6 @@ abstract contract EIP712 {
|
||||
* ```
|
||||
*/
|
||||
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
|
||||
return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
|
||||
return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
|
||||
}
|
||||
}
|
||||
|
||||
37
contracts/metatx/ERC2771Context.sol
Normal file
37
contracts/metatx/ERC2771Context.sol
Normal file
@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../utils/Context.sol";
|
||||
|
||||
/*
|
||||
* @dev Context variant with ERC2771 support.
|
||||
*/
|
||||
abstract contract ERC2771Context is Context {
|
||||
address immutable _trustedForwarder;
|
||||
|
||||
constructor(address trustedForwarder) {
|
||||
_trustedForwarder = trustedForwarder;
|
||||
}
|
||||
|
||||
function isTrustedForwarder(address forwarder) public view virtual returns(bool) {
|
||||
return forwarder == _trustedForwarder;
|
||||
}
|
||||
|
||||
function _msgSender() internal view virtual override returns (address sender) {
|
||||
if (isTrustedForwarder(msg.sender)) {
|
||||
// The assembly code is more direct than the Solidity version using `abi.decode`.
|
||||
assembly { sender := shr(96, calldataload(sub(calldatasize(), 20))) }
|
||||
} else {
|
||||
return super._msgSender();
|
||||
}
|
||||
}
|
||||
|
||||
function _msgData() internal view virtual override returns (bytes calldata) {
|
||||
if (isTrustedForwarder(msg.sender)) {
|
||||
return msg.data[:msg.data.length-20];
|
||||
} else {
|
||||
return super._msgData();
|
||||
}
|
||||
}
|
||||
}
|
||||
58
contracts/metatx/MinimalForwarder.sol
Normal file
58
contracts/metatx/MinimalForwarder.sol
Normal file
@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../cryptography/ECDSA.sol";
|
||||
import "../drafts/EIP712.sol";
|
||||
|
||||
/*
|
||||
* @dev Simple minimal forwarder to be used together with an ERC2771 compatible contract. See {ERC2771Context}.
|
||||
*/
|
||||
contract MinimalForwarder is EIP712 {
|
||||
using ECDSA for bytes32;
|
||||
|
||||
struct ForwardRequest {
|
||||
address from;
|
||||
address to;
|
||||
uint256 value;
|
||||
uint256 gas;
|
||||
uint256 nonce;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
bytes32 private constant TYPEHASH = keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)");
|
||||
|
||||
mapping(address => uint256) private _nonces;
|
||||
|
||||
constructor() EIP712("MinimalForwarder", "0.0.1") {}
|
||||
|
||||
function getNonce(address from) public view returns (uint256) {
|
||||
return _nonces[from];
|
||||
}
|
||||
|
||||
function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {
|
||||
address signer = _hashTypedDataV4(keccak256(abi.encode(
|
||||
TYPEHASH,
|
||||
req.from,
|
||||
req.to,
|
||||
req.value,
|
||||
req.gas,
|
||||
req.nonce,
|
||||
keccak256(req.data)
|
||||
))).recover(signature);
|
||||
return _nonces[req.from] == req.nonce && signer == req.from;
|
||||
}
|
||||
|
||||
function execute(ForwardRequest calldata req, bytes calldata signature) public payable returns (bool, bytes memory) {
|
||||
require(verify(req, signature), "MinimalForwarder: signature does not match request");
|
||||
_nonces[req.from] = req.nonce + 1;
|
||||
|
||||
// solhint-disable-next-line avoid-low-level-calls
|
||||
(bool success, bytes memory returndata) = req.to.call{gas: req.gas, value: req.value}(abi.encodePacked(req.data, req.from));
|
||||
// Validate that the relayer has sent enough gas for the call.
|
||||
// See https://ronan.eth.link/blog/ethereum-gas-dangers/
|
||||
assert(gasleft() > req.gas / 63);
|
||||
|
||||
return (success, returndata);
|
||||
}
|
||||
}
|
||||
12
contracts/metatx/README.adoc
Normal file
12
contracts/metatx/README.adoc
Normal file
@ -0,0 +1,12 @@
|
||||
= Meta Transactions
|
||||
|
||||
[.readme-notice]
|
||||
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/math
|
||||
|
||||
== Core
|
||||
|
||||
{{ERC2771Context}}
|
||||
|
||||
== Utils
|
||||
|
||||
{{MinimalForwarder}}
|
||||
19
contracts/mocks/ERC2771ContextMock.sol
Normal file
19
contracts/mocks/ERC2771ContextMock.sol
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./ContextMock.sol";
|
||||
import "../metatx/ERC2771Context.sol";
|
||||
|
||||
// By inheriting from ERC2771Context, Context's internal functions are overridden automatically
|
||||
contract ERC2771ContextMock is ContextMock, ERC2771Context {
|
||||
constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}
|
||||
|
||||
function _msgSender() internal override(Context, ERC2771Context) view virtual returns (address) {
|
||||
return ERC2771Context._msgSender();
|
||||
}
|
||||
|
||||
function _msgData() internal override(Context, ERC2771Context) view virtual returns (bytes calldata) {
|
||||
return ERC2771Context._msgData();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user