MinimalForwarder.sol 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v4.9.0) (metatx/MinimalForwarder.sol)
  3. pragma solidity ^0.8.19;
  4. import "../utils/cryptography/ECDSA.sol";
  5. import "../utils/cryptography/EIP712.sol";
  6. /**
  7. * @dev Simple minimal forwarder to be used together with an ERC2771 compatible contract. See {ERC2771Context}.
  8. *
  9. * MinimalForwarder is mainly meant for testing, as it is missing features to be a good production-ready forwarder. This
  10. * contract does not intend to have all the properties that are needed for a sound forwarding system. A fully
  11. * functioning forwarding system with good properties requires more complexity. We suggest you look at other projects
  12. * such as the GSN which do have the goal of building a system like that.
  13. */
  14. contract MinimalForwarder is EIP712 {
  15. using ECDSA for bytes32;
  16. struct ForwardRequest {
  17. address from;
  18. address to;
  19. uint256 value;
  20. uint256 gas;
  21. uint256 nonce;
  22. bytes data;
  23. }
  24. bytes32 private constant _TYPEHASH =
  25. keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)");
  26. mapping(address => uint256) private _nonces;
  27. /**
  28. * @dev The request `from` doesn't match with the recovered `signer`.
  29. */
  30. error MinimalForwarderInvalidSigner(address signer, address from);
  31. /**
  32. * @dev The request nonce doesn't match with the `current` nonce for the request signer.
  33. */
  34. error MinimalForwarderInvalidNonce(address signer, uint256 current);
  35. constructor() EIP712("MinimalForwarder", "0.0.1") {}
  36. function getNonce(address from) public view returns (uint256) {
  37. return _nonces[from];
  38. }
  39. function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {
  40. address signer = _recover(req, signature);
  41. (bool correctFrom, bool correctNonce) = _validateReq(req, signer);
  42. return correctFrom && correctNonce;
  43. }
  44. function execute(
  45. ForwardRequest calldata req,
  46. bytes calldata signature
  47. ) public payable returns (bool, bytes memory) {
  48. address signer = _recover(req, signature);
  49. (bool correctFrom, bool correctNonce) = _validateReq(req, signer);
  50. if (!correctFrom) {
  51. revert MinimalForwarderInvalidSigner(signer, req.from);
  52. }
  53. if (!correctNonce) {
  54. revert MinimalForwarderInvalidNonce(signer, _nonces[req.from]);
  55. }
  56. _nonces[req.from] = req.nonce + 1;
  57. (bool success, bytes memory returndata) = req.to.call{gas: req.gas, value: req.value}(
  58. abi.encodePacked(req.data, req.from)
  59. );
  60. // Validate that the relayer has sent enough gas for the call.
  61. // See https://ronan.eth.limo/blog/ethereum-gas-dangers/
  62. if (gasleft() <= req.gas / 63) {
  63. // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since
  64. // neither revert or assert consume all gas since Solidity 0.8.0
  65. // https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require
  66. /// @solidity memory-safe-assembly
  67. assembly {
  68. invalid()
  69. }
  70. }
  71. return (success, returndata);
  72. }
  73. function _recover(ForwardRequest calldata req, bytes calldata signature) internal view returns (address) {
  74. return
  75. _hashTypedDataV4(
  76. keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data)))
  77. ).recover(signature);
  78. }
  79. function _validateReq(
  80. ForwardRequest calldata req,
  81. address signer
  82. ) internal view returns (bool correctFrom, bool correctNonce) {
  83. return (signer == req.from, _nonces[req.from] == req.nonce);
  84. }
  85. }