commit
734bf8e85a
@ -0,0 +1,5 @@ |
|||||||
|
--- |
||||||
|
'openzeppelin-solidity': major |
||||||
|
--- |
||||||
|
|
||||||
|
`ERC2771Forwarder`: Added `deadline` for expiring transactions, batching, and more secure handling of `msg.value`. |
@ -0,0 +1,5 @@ |
|||||||
|
--- |
||||||
|
'openzeppelin-solidity': minor |
||||||
|
--- |
||||||
|
|
||||||
|
`TimelockController`: Add a state getter that returns an `OperationState` enum. |
@ -0,0 +1,5 @@ |
|||||||
|
--- |
||||||
|
'openzeppelin-solidity': major |
||||||
|
--- |
||||||
|
|
||||||
|
Switched to using explicit Solidity import statements. Some previously available symbols may now have to be separately imported. |
@ -0,0 +1,5 @@ |
|||||||
|
--- |
||||||
|
'openzeppelin-solidity': major |
||||||
|
--- |
||||||
|
|
||||||
|
`Governor`: Add support for casting votes with ERC-1271 signatures by using a `bytes memory signature` instead of `r`, `s` and `v` arguments in the `castVoteBySig` and `castVoteWithReasonAndParamsBySig` functions. |
@ -0,0 +1,5 @@ |
|||||||
|
--- |
||||||
|
'openzeppelin-solidity': major |
||||||
|
--- |
||||||
|
|
||||||
|
`Governor`: Add `voter` and `nonce` parameters in signed ballots, to avoid forging signatures for random addresses, prevent signature replay, and allow invalidating signatures. Add `voter` as a new parameter in the `castVoteBySig` and `castVoteWithReasonAndParamsBySig` functions. |
@ -0,0 +1,308 @@ |
|||||||
|
// SPDX-License-Identifier: MIT |
||||||
|
// OpenZeppelin Contracts (last updated v4.9.0) (metatx/ERC2771Forwarder.sol) |
||||||
|
|
||||||
|
pragma solidity ^0.8.19; |
||||||
|
|
||||||
|
import {ECDSA} from "../utils/cryptography/ECDSA.sol"; |
||||||
|
import {EIP712} from "../utils/cryptography/EIP712.sol"; |
||||||
|
import {Nonces} from "../utils/Nonces.sol"; |
||||||
|
import {Address} from "../utils/Address.sol"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev A forwarder compatible with ERC2771 contracts. See {ERC2771Context}. |
||||||
|
* |
||||||
|
* This forwarder operates on forward requests that include: |
||||||
|
* |
||||||
|
* * `from`: An address to operate on behalf of. It is required to be equal to the request signer. |
||||||
|
* * `to`: The address that should be called. |
||||||
|
* * `value`: The amount of native token to attach with the requested call. |
||||||
|
* * `gas`: The amount of gas limit that will be forwarded with the requested call. |
||||||
|
* * `nonce`: A unique transaction ordering identifier to avoid replayability and request invalidation. |
||||||
|
* * `deadline`: A timestamp after which the request is not executable anymore. |
||||||
|
* * `data`: Encoded `msg.data` to send with the requested call. |
||||||
|
* |
||||||
|
* Relayers are able to submit batches if they are processing a high volume of requests. With high |
||||||
|
* throughput, relayers may run into limitations of the chain such as limits on the number of |
||||||
|
* transactions in the mempool. In these cases the recommendation is to distribute the load among |
||||||
|
* multiple accounts. |
||||||
|
* |
||||||
|
* ==== Security Considerations |
||||||
|
* |
||||||
|
* If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount |
||||||
|
* specified in the request. This contract does not implement any kind of retribution for this gas, |
||||||
|
* and it is assumed that there is an out of band incentive for relayers to pay for execution on |
||||||
|
* behalf of signers. Often, the relayer is operated by a project that will consider it a user |
||||||
|
* acquisition cost. |
||||||
|
* |
||||||
|
* By offering to pay for gas, relayers are at risk of having that gas used by an attacker toward |
||||||
|
* some other purpose that is not aligned with the expected out of band incentives. If you operate a |
||||||
|
* relayer, consider whitelisting target contracts and function selectors. When relaying ERC-721 or |
||||||
|
* ERC-1155 transfers specifically, consider rejecting the use of the `data` field, since it can be |
||||||
|
* used to execute arbitrary code. |
||||||
|
*/ |
||||||
|
contract ERC2771Forwarder is EIP712, Nonces { |
||||||
|
using ECDSA for bytes32; |
||||||
|
|
||||||
|
struct ForwardRequestData { |
||||||
|
address from; |
||||||
|
address to; |
||||||
|
uint256 value; |
||||||
|
uint256 gas; |
||||||
|
uint48 deadline; |
||||||
|
bytes data; |
||||||
|
bytes signature; |
||||||
|
} |
||||||
|
|
||||||
|
bytes32 private constant _FORWARD_REQUEST_TYPEHASH = |
||||||
|
keccak256( |
||||||
|
"ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,uint48 deadline,bytes data)" |
||||||
|
); |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Emitted when a `ForwardRequest` is executed. |
||||||
|
* |
||||||
|
* NOTE: An unsuccessful forward request could be due to an invalid signature, an expired deadline, |
||||||
|
* or simply a revert in the requested call. The contract guarantees that the relayer is not able to force |
||||||
|
* the requested call to run out of gas. |
||||||
|
*/ |
||||||
|
event ExecutedForwardRequest(address indexed signer, uint256 nonce, bool success); |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev The request `from` doesn't match with the recovered `signer`. |
||||||
|
*/ |
||||||
|
error ERC2771ForwarderInvalidSigner(address signer, address from); |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev The `requestedValue` doesn't match with the available `msgValue`. |
||||||
|
*/ |
||||||
|
error ERC2771ForwarderMismatchedValue(uint256 requestedValue, uint256 msgValue); |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev The request `deadline` has expired. |
||||||
|
*/ |
||||||
|
error ERC2771ForwarderExpiredRequest(uint48 deadline); |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev See {EIP712-constructor}. |
||||||
|
*/ |
||||||
|
constructor(string memory name) EIP712(name, "1") {} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Returns `true` if a request is valid for a provided `signature` at the current block timestamp. |
||||||
|
* |
||||||
|
* A transaction is considered valid when it hasn't expired (deadline is not met), and the signer |
||||||
|
* matches the `from` parameter of the signed request. |
||||||
|
* |
||||||
|
* NOTE: A request may return false here but it won't cause {executeBatch} to revert if a refund |
||||||
|
* receiver is provided. |
||||||
|
*/ |
||||||
|
function verify(ForwardRequestData calldata request) public view virtual returns (bool) { |
||||||
|
(bool alive, bool signerMatch, ) = _validate(request); |
||||||
|
return alive && signerMatch; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Executes a `request` on behalf of `signature`'s signer using the ERC-2771 protocol. The gas |
||||||
|
* provided to the requested call may not be exactly the amount requested, but the call will not run |
||||||
|
* out of gas. Will revert if the request is invalid or the call reverts, in this case the nonce is not consumed. |
||||||
|
* |
||||||
|
* Requirements: |
||||||
|
* |
||||||
|
* - The request value should be equal to the provided `msg.value`. |
||||||
|
* - The request should be valid according to {verify}. |
||||||
|
*/ |
||||||
|
function execute(ForwardRequestData calldata request) public payable virtual { |
||||||
|
// We make sure that msg.value and request.value match exactly. |
||||||
|
// If the request is invalid or the call reverts, this whole function |
||||||
|
// will revert, ensuring value isn't stuck. |
||||||
|
if (msg.value != request.value) { |
||||||
|
revert ERC2771ForwarderMismatchedValue(request.value, msg.value); |
||||||
|
} |
||||||
|
|
||||||
|
if (!_execute(request, true)) { |
||||||
|
revert Address.FailedInnerCall(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Batch version of {execute} with optional refunding and atomic execution. |
||||||
|
* |
||||||
|
* In case a batch contains at least one invalid request (see {verify}), the |
||||||
|
* request will be skipped and the `refundReceiver` parameter will receive back the |
||||||
|
* unused requested value at the end of the execution. This is done to prevent reverting |
||||||
|
* the entire batch when a request is invalid or has already been submitted. |
||||||
|
* |
||||||
|
* If the `refundReceiver` is the `address(0)`, this function will revert when at least |
||||||
|
* one of the requests was not valid instead of skipping it. This could be useful if |
||||||
|
* a batch is required to get executed atomically (at least at the top-level). For example, |
||||||
|
* refunding (and thus atomicity) can be opt-out if the relayer is using a service that avoids |
||||||
|
* including reverted transactions. |
||||||
|
* |
||||||
|
* Requirements: |
||||||
|
* |
||||||
|
* - The sum of the requests' values should be equal to the provided `msg.value`. |
||||||
|
* - All of the requests should be valid (see {verify}) when `refundReceiver` is the zero address. |
||||||
|
* |
||||||
|
* NOTE: Setting a zero `refundReceiver` guarantees an all-or-nothing requests execution only for |
||||||
|
* the first-level forwarded calls. In case a forwarded request calls to a contract with another |
||||||
|
* subcall, the second-level call may revert without the top-level call reverting. |
||||||
|
*/ |
||||||
|
function executeBatch( |
||||||
|
ForwardRequestData[] calldata requests, |
||||||
|
address payable refundReceiver |
||||||
|
) public payable virtual { |
||||||
|
bool atomic = refundReceiver == address(0); |
||||||
|
|
||||||
|
uint256 requestsValue; |
||||||
|
uint256 refundValue; |
||||||
|
|
||||||
|
for (uint256 i; i < requests.length; ++i) { |
||||||
|
requestsValue += requests[i].value; |
||||||
|
bool success = _execute(requests[i], atomic); |
||||||
|
if (!success) { |
||||||
|
refundValue += requests[i].value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// The batch should revert if there's a mismatched msg.value provided |
||||||
|
// to avoid request value tampering |
||||||
|
if (requestsValue != msg.value) { |
||||||
|
revert ERC2771ForwarderMismatchedValue(requestsValue, msg.value); |
||||||
|
} |
||||||
|
|
||||||
|
// Some requests with value were invalid (possibly due to frontrunning). |
||||||
|
// To avoid leaving ETH in the contract this value is refunded. |
||||||
|
if (refundValue != 0) { |
||||||
|
// We know refundReceiver != address(0) && requestsValue == msg.value |
||||||
|
// meaning we can ensure refundValue is not taken from the original contract's balance |
||||||
|
// and refundReceiver is a known account. |
||||||
|
Address.sendValue(refundReceiver, refundValue); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Validates if the provided request can be executed at current block timestamp with |
||||||
|
* the given `request.signature` on behalf of `request.signer`. |
||||||
|
*/ |
||||||
|
function _validate( |
||||||
|
ForwardRequestData calldata request |
||||||
|
) internal view virtual returns (bool alive, bool signerMatch, address signer) { |
||||||
|
signer = _recoverForwardRequestSigner(request); |
||||||
|
return (request.deadline >= block.timestamp, signer == request.from, signer); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Recovers the signer of an EIP712 message hash for a forward `request` and its corresponding `signature`. |
||||||
|
* See {ECDSA-recover}. |
||||||
|
*/ |
||||||
|
function _recoverForwardRequestSigner(ForwardRequestData calldata request) internal view virtual returns (address) { |
||||||
|
return |
||||||
|
_hashTypedDataV4( |
||||||
|
keccak256( |
||||||
|
abi.encode( |
||||||
|
_FORWARD_REQUEST_TYPEHASH, |
||||||
|
request.from, |
||||||
|
request.to, |
||||||
|
request.value, |
||||||
|
request.gas, |
||||||
|
nonces(request.from), |
||||||
|
request.deadline, |
||||||
|
keccak256(request.data) |
||||||
|
) |
||||||
|
) |
||||||
|
).recover(request.signature); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Validates and executes a signed request returning the request call `success` value. |
||||||
|
* |
||||||
|
* Internal function without msg.value validation. |
||||||
|
* |
||||||
|
* Requirements: |
||||||
|
* |
||||||
|
* - The caller must have provided enough gas to forward with the call. |
||||||
|
* - The request must be valid (see {verify}) if the `requireValidRequest` is true. |
||||||
|
* |
||||||
|
* Emits an {ExecutedForwardRequest} event. |
||||||
|
* |
||||||
|
* IMPORTANT: Using this function doesn't check that all the `msg.value` was sent, potentially |
||||||
|
* leaving value stuck in the contract. |
||||||
|
*/ |
||||||
|
function _execute( |
||||||
|
ForwardRequestData calldata request, |
||||||
|
bool requireValidRequest |
||||||
|
) internal virtual returns (bool success) { |
||||||
|
(bool alive, bool signerMatch, address signer) = _validate(request); |
||||||
|
|
||||||
|
// Need to explicitly specify if a revert is required since non-reverting is default for |
||||||
|
// batches and reversion is opt-in since it could be useful in some scenarios |
||||||
|
if (requireValidRequest) { |
||||||
|
if (!alive) { |
||||||
|
revert ERC2771ForwarderExpiredRequest(request.deadline); |
||||||
|
} |
||||||
|
|
||||||
|
if (!signerMatch) { |
||||||
|
revert ERC2771ForwarderInvalidSigner(signer, request.from); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Ignore an invalid request because requireValidRequest = false |
||||||
|
if (signerMatch && alive) { |
||||||
|
// Nonce should be used before the call to prevent reusing by reentrancy |
||||||
|
uint256 currentNonce = _useNonce(signer); |
||||||
|
|
||||||
|
(success, ) = request.to.call{gas: request.gas, value: request.value}( |
||||||
|
abi.encodePacked(request.data, request.from) |
||||||
|
); |
||||||
|
|
||||||
|
_checkForwardedGas(request); |
||||||
|
|
||||||
|
emit ExecutedForwardRequest(signer, currentNonce, success); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Checks if the requested gas was correctly forwarded to the callee. |
||||||
|
* |
||||||
|
* As a consequence of https://eips.ethereum.org/EIPS/eip-150[EIP-150]: |
||||||
|
* - At most `gasleft() - floor(gasleft() / 64)` is forwarded to the callee. |
||||||
|
* - At least `floor(gasleft() / 64)` is kept in the caller. |
||||||
|
* |
||||||
|
* It reverts consuming all the available gas if the forwarded gas is not the requested gas. |
||||||
|
* |
||||||
|
* IMPORTANT: This function should be called exactly the end of the forwarded call. Any gas consumed |
||||||
|
* in between will make room for bypassing this check. |
||||||
|
*/ |
||||||
|
function _checkForwardedGas(ForwardRequestData calldata request) private view { |
||||||
|
// To avoid insufficient gas griefing attacks, as referenced in https://ronan.eth.limo/blog/ethereum-gas-dangers/ |
||||||
|
// |
||||||
|
// A malicious relayer can attempt to shrink the gas forwarded so that the underlying call reverts out-of-gas |
||||||
|
// but the forwarding itself still succeeds. In order to make sure that the subcall received sufficient gas, |
||||||
|
// we will inspect gasleft() after the forwarding. |
||||||
|
// |
||||||
|
// Let X be the gas available before the subcall, such that the subcall gets at most X * 63 / 64. |
||||||
|
// We can't know X after CALL dynamic costs, but we want it to be such that X * 63 / 64 >= req.gas. |
||||||
|
// Let Y be the gas used in the subcall. gasleft() measured immediately after the subcall will be gasleft() = X - Y. |
||||||
|
// If the subcall ran out of gas, then Y = X * 63 / 64 and gasleft() = X - Y = X / 64. |
||||||
|
// Under this assumption req.gas / 63 > gasleft() is true is true if and only if |
||||||
|
// req.gas / 63 > X / 64, or equivalently req.gas > X * 63 / 64. |
||||||
|
// This means that if the subcall runs out of gas we are able to detect that insufficient gas was passed. |
||||||
|
// |
||||||
|
// We will now also see that req.gas / 63 > gasleft() implies that req.gas >= X * 63 / 64. |
||||||
|
// The contract guarantees Y <= req.gas, thus gasleft() = X - Y >= X - req.gas. |
||||||
|
// - req.gas / 63 > gasleft() |
||||||
|
// - req.gas / 63 >= X - req.gas |
||||||
|
// - req.gas >= X * 63 / 64 |
||||||
|
// In other words if req.gas < X * 63 / 64 then req.gas / 63 <= gasleft(), thus if the relayer behaves honestly |
||||||
|
// the forwarding does not revert. |
||||||
|
if (gasleft() < request.gas / 63) { |
||||||
|
// We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since |
||||||
|
// neither revert or assert consume all gas since Solidity 0.8.0 |
||||||
|
// https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require |
||||||
|
/// @solidity memory-safe-assembly |
||||||
|
assembly { |
||||||
|
invalid() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,104 +0,0 @@ |
|||||||
// SPDX-License-Identifier: MIT |
|
||||||
// OpenZeppelin Contracts (last updated v4.9.0) (metatx/MinimalForwarder.sol) |
|
||||||
|
|
||||||
pragma solidity ^0.8.19; |
|
||||||
|
|
||||||
import "../utils/cryptography/ECDSA.sol"; |
|
||||||
import "../utils/cryptography/EIP712.sol"; |
|
||||||
|
|
||||||
/** |
|
||||||
* @dev Simple minimal forwarder to be used together with an ERC2771 compatible contract. See {ERC2771Context}. |
|
||||||
* |
|
||||||
* MinimalForwarder is mainly meant for testing, as it is missing features to be a good production-ready forwarder. This |
|
||||||
* contract does not intend to have all the properties that are needed for a sound forwarding system. A fully |
|
||||||
* functioning forwarding system with good properties requires more complexity. We suggest you look at other projects |
|
||||||
* such as the GSN which do have the goal of building a system like that. |
|
||||||
*/ |
|
||||||
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; |
|
||||||
|
|
||||||
/** |
|
||||||
* @dev The request `from` doesn't match with the recovered `signer`. |
|
||||||
*/ |
|
||||||
error MinimalForwarderInvalidSigner(address signer, address from); |
|
||||||
|
|
||||||
/** |
|
||||||
* @dev The request nonce doesn't match with the `current` nonce for the request signer. |
|
||||||
*/ |
|
||||||
error MinimalForwarderInvalidNonce(address signer, uint256 current); |
|
||||||
|
|
||||||
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 = _recover(req, signature); |
|
||||||
(bool correctFrom, bool correctNonce) = _validateReq(req, signer); |
|
||||||
return correctFrom && correctNonce; |
|
||||||
} |
|
||||||
|
|
||||||
function execute( |
|
||||||
ForwardRequest calldata req, |
|
||||||
bytes calldata signature |
|
||||||
) public payable returns (bool, bytes memory) { |
|
||||||
address signer = _recover(req, signature); |
|
||||||
(bool correctFrom, bool correctNonce) = _validateReq(req, signer); |
|
||||||
|
|
||||||
if (!correctFrom) { |
|
||||||
revert MinimalForwarderInvalidSigner(signer, req.from); |
|
||||||
} |
|
||||||
if (!correctNonce) { |
|
||||||
revert MinimalForwarderInvalidNonce(signer, _nonces[req.from]); |
|
||||||
} |
|
||||||
|
|
||||||
_nonces[req.from] = req.nonce + 1; |
|
||||||
|
|
||||||
(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.limo/blog/ethereum-gas-dangers/ |
|
||||||
if (gasleft() <= req.gas / 63) { |
|
||||||
// We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since |
|
||||||
// neither revert or assert consume all gas since Solidity 0.8.0 |
|
||||||
// https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require |
|
||||||
/// @solidity memory-safe-assembly |
|
||||||
assembly { |
|
||||||
invalid() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return (success, returndata); |
|
||||||
} |
|
||||||
|
|
||||||
function _recover(ForwardRequest calldata req, bytes calldata signature) internal view returns (address) { |
|
||||||
return |
|
||||||
_hashTypedDataV4( |
|
||||||
keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data))) |
|
||||||
).recover(signature); |
|
||||||
} |
|
||||||
|
|
||||||
function _validateReq( |
|
||||||
ForwardRequest calldata req, |
|
||||||
address signer |
|
||||||
) internal view returns (bool correctFrom, bool correctNonce) { |
|
||||||
return (signer == req.from, _nonces[req.from] == req.nonce); |
|
||||||
} |
|
||||||
} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue