Remove GSNv1 contracts (#2521)

Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
pull/2505/head
Hadrien Croubois 4 years ago committed by GitHub
parent e66e3ca523
commit f7c8252611
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 5
      contracts/GSN/Context.sol
  3. 194
      contracts/GSN/GSNRecipient.sol
  4. 152
      contracts/GSN/GSNRecipientERC20Fee.sol
  5. 72
      contracts/GSN/GSNRecipientSignature.sol
  6. 269
      contracts/GSN/IRelayHub.sol
  7. 76
      contracts/GSN/IRelayRecipient.sol
  8. 31
      contracts/GSN/README.adoc
  9. 30
      contracts/mocks/ERC721GSNRecipientMock.sol
  10. 20
      contracts/mocks/GSNRecipientERC20FeeMock.sol
  11. 38
      contracts/mocks/GSNRecipientMock.sol
  12. 16
      contracts/mocks/GSNRecipientSignatureMock.sol
  13. 2
      contracts/utils/Context.sol
  14. 3
      docs/modules/ROOT/nav.adoc
  15. 160
      docs/modules/ROOT/pages/gsn-strategies.adoc
  16. 90
      docs/modules/ROOT/pages/gsn.adoc
  17. 3
      docs/modules/ROOT/pages/index.adoc
  18. 17020
      package-lock.json
  19. 2
      package.json
  20. 55
      test/GSN/ERC721GSNRecipientMock.test.js
  21. 123
      test/GSN/GSNRecipient.test.js
  22. 77
      test/GSN/GSNRecipientERC20Fee.test.js
  23. 93
      test/GSN/GSNRecipientSignature.test.js
  24. 15
      test/helpers/set-gsn-provider.js
  25. 6
      test/setup.js
  26. 0
      test/utils/Context.behavior.js
  27. 0
      test/utils/Context.test.js

@ -7,6 +7,7 @@
* `ERC20`: Removed the `_setDecimals` function and the storage slot associated to decimals. ([#2502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2502))
* `Strings`: addition of a `toHexString` function. ([#2504](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2504))
* `EnumerableMap`: change implementation to optimize for `key → value` lookups instead of enumeration. ([#2518](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2518))
* `GSN`: Deprecate GSNv1 support in favor of upcomming support for GSNv2. ([#2521](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2521))
## 3.4.0 (2021-02-02)

@ -1,5 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/Context.sol";

@ -1,194 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/Context.sol";
import "./IRelayRecipient.sol";
import "./IRelayHub.sol";
/**
* @dev Base GSN recipient contract: includes the {IRelayRecipient} interface
* and enables GSN support on all contracts in the inheritance tree.
*
* TIP: This contract is abstract. The functions {IRelayRecipient-acceptRelayedCall},
* {_preRelayedCall}, and {_postRelayedCall} are not implemented and must be
* provided by derived contracts. See the
* xref:ROOT:gsn-strategies.adoc#gsn-strategies[GSN strategies] for more
* information on how to use the pre-built {GSNRecipientSignature} and
* {GSNRecipientERC20Fee}, or how to write your own.
*/
abstract contract GSNRecipient is IRelayRecipient, Context {
// Default RelayHub address, deployed on mainnet and all testnets at the same address
address private _relayHub = 0xD216153c06E857cD7f72665E0aF1d7D82172F494;
uint256 constant private _RELAYED_CALL_ACCEPTED = 0;
uint256 constant private _RELAYED_CALL_REJECTED = 11;
// How much gas is forwarded to postRelayedCall
uint256 constant internal _POST_RELAYED_CALL_MAX_GAS = 100000;
/**
* @dev Emitted when a contract changes its {IRelayHub} contract to a new one.
*/
event RelayHubChanged(address indexed oldRelayHub, address indexed newRelayHub);
/**
* @dev Returns the address of the {IRelayHub} contract for this recipient.
*/
function getHubAddr() public view virtual override returns (address) {
return _relayHub;
}
/**
* @dev Switches to a new {IRelayHub} instance. This method is added for future-proofing: there's no reason to not
* use the default instance.
*
* IMPORTANT: After upgrading, the {GSNRecipient} will no longer be able to receive relayed calls from the old
* {IRelayHub} instance. Additionally, all funds should be previously withdrawn via {_withdrawDeposits}.
*/
function _upgradeRelayHub(address newRelayHub) internal virtual {
address currentRelayHub = _relayHub;
require(newRelayHub != address(0), "GSNRecipient: new RelayHub is the zero address");
require(newRelayHub != currentRelayHub, "GSNRecipient: new RelayHub is the current one");
emit RelayHubChanged(currentRelayHub, newRelayHub);
_relayHub = newRelayHub;
}
/**
* @dev Returns the version string of the {IRelayHub} for which this recipient implementation was built. If
* {_upgradeRelayHub} is used, the new {IRelayHub} instance should be compatible with this version.
*/
// This function is view for future-proofing, it may require reading from
// storage in the future.
function relayHubVersion() public view virtual returns (string memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return "1.0.0";
}
/**
* @dev Withdraws the recipient's deposits in `RelayHub`.
*
* Derived contracts should expose this in an external interface with proper access control.
*/
function _withdrawDeposits(uint256 amount, address payable payee) internal virtual {
IRelayHub(getHubAddr()).withdraw(amount, payee);
}
// Overrides for Context's functions: when called from RelayHub, sender and
// data require some pre-processing: the actual sender is stored at the end
// of the call data, which in turns means it needs to be removed from it
// when handling said data.
/**
* @dev Replacement for msg.sender. Returns the actual sender of a transaction: msg.sender for regular transactions,
* and the end-user for GSN relayed calls (where msg.sender is actually `RelayHub`).
*
* IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.sender`, and use {_msgSender} instead.
*/
function _msgSender() internal view virtual override returns (address msgSender) {
if (msg.sender == getHubAddr()) {
assembly { msgSender := shr(96, calldataload(sub(calldatasize(), 20))) }
} else {
return msg.sender;
}
}
/**
* @dev Replacement for msg.data. Returns the actual calldata of a transaction: msg.data for regular transactions,
* and a reduced version for GSN relayed calls (where msg.data contains additional information).
*
* IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.data`, and use {_msgData} instead.
*/
function _msgData() internal view virtual override returns (bytes calldata) {
if (msg.sender == getHubAddr()) {
return msg.data[:msg.data.length - 20];
} else {
return msg.data;
}
}
// Base implementations for pre and post relayedCall: only RelayHub can invoke them, and data is forwarded to the
// internal hook.
/**
* @dev See `IRelayRecipient.preRelayedCall`.
*
* This function should not be overridden directly, use `_preRelayedCall` instead.
*
* * Requirements:
*
* - the caller must be the `RelayHub` contract.
*/
function preRelayedCall(bytes memory context) public virtual override returns (bytes32) {
require(msg.sender == getHubAddr(), "GSNRecipient: caller is not RelayHub");
return _preRelayedCall(context);
}
/**
* @dev See `IRelayRecipient.preRelayedCall`.
*
* Called by `GSNRecipient.preRelayedCall`, which asserts the caller is the `RelayHub` contract. Derived contracts
* must implement this function with any relayed-call preprocessing they may wish to do.
*
*/
function _preRelayedCall(bytes memory context) internal virtual returns (bytes32);
/**
* @dev See `IRelayRecipient.postRelayedCall`.
*
* This function should not be overridden directly, use `_postRelayedCall` instead.
*
* * Requirements:
*
* - the caller must be the `RelayHub` contract.
*/
function postRelayedCall(bytes memory context, bool success, uint256 actualCharge, bytes32 preRetVal) public virtual override {
require(msg.sender == getHubAddr(), "GSNRecipient: caller is not RelayHub");
_postRelayedCall(context, success, actualCharge, preRetVal);
}
/**
* @dev See `IRelayRecipient.postRelayedCall`.
*
* Called by `GSNRecipient.postRelayedCall`, which asserts the caller is the `RelayHub` contract. Derived contracts
* must implement this function with any relayed-call postprocessing they may wish to do.
*
*/
function _postRelayedCall(bytes memory context, bool success, uint256 actualCharge, bytes32 preRetVal) internal virtual;
/**
* @dev Return this in acceptRelayedCall to proceed with the execution of a relayed call. Note that this contract
* will be charged a fee by RelayHub
*/
function _approveRelayedCall() internal pure virtual returns (uint256, bytes memory) {
return _approveRelayedCall("");
}
/**
* @dev See `GSNRecipient._approveRelayedCall`.
*
* This overload forwards `context` to _preRelayedCall and _postRelayedCall.
*/
function _approveRelayedCall(bytes memory context) internal pure virtual returns (uint256, bytes memory) {
return (_RELAYED_CALL_ACCEPTED, context);
}
/**
* @dev Return this in acceptRelayedCall to impede execution of a relayed call. No fees will be charged.
*/
function _rejectRelayedCall(uint256 errorCode) internal pure virtual returns (uint256, bytes memory) {
return (_RELAYED_CALL_REJECTED + errorCode, "");
}
/*
* @dev Calculates how much RelayHub will charge a recipient for using `gas` at a `gasPrice`, given a relayer's
* `serviceFee`.
*/
function _computeCharge(uint256 gas, uint256 gasPrice, uint256 serviceFee) internal pure virtual returns (uint256) {
// The fee is expressed as a percentage. E.g. a value of 40 stands for a 40% fee, so the recipient will be
// charged for 1.4 times the spent amount.
return (gas * gasPrice * (100 + serviceFee)) / 100;
}
}

@ -1,152 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./GSNRecipient.sol";
import "../access/Ownable.sol";
import "../token/ERC20/SafeERC20.sol";
import "../token/ERC20/ERC20.sol";
/**
* @dev A xref:ROOT:gsn-strategies.adoc#gsn-strategies[GSN strategy] that charges transaction fees in a special purpose ERC20
* token, which we refer to as the gas payment token. The amount charged is exactly the amount of Ether charged to the
* recipient. This means that the token is essentially pegged to the value of Ether.
*
* The distribution strategy of the gas payment token to users is not defined by this contract. It's a mintable token
* whose only minter is the recipient, so the strategy must be implemented in a derived contract, making use of the
* internal {_mint} function.
*/
contract GSNRecipientERC20Fee is GSNRecipient {
using SafeERC20 for __unstable__ERC20Owned;
enum GSNRecipientERC20FeeErrorCodes {
INSUFFICIENT_BALANCE
}
__unstable__ERC20Owned private _token;
/**
* @dev The arguments to the constructor are the details that the gas payment token will have: `name` and `symbol`. `decimals` is hard-coded to 18.
*/
constructor(string memory name, string memory symbol) {
_token = new __unstable__ERC20Owned(name, symbol);
}
/**
* @dev Returns the gas payment token.
*/
function token() public view virtual returns (__unstable__ERC20Owned) {
return _token;
}
/**
* @dev Internal function that mints the gas payment token. Derived contracts should expose this function in their public API, with proper access control mechanisms.
*/
function _mint(address account, uint256 amount) internal virtual {
token().mint(account, amount);
}
/**
* @dev Ensures that only users with enough gas payment token balance can have transactions relayed through the GSN.
*/
function acceptRelayedCall(
address,
address from,
bytes memory,
uint256 transactionFee,
uint256 gasPrice,
uint256,
uint256,
bytes memory,
uint256 maxPossibleCharge
)
public
view
virtual
override
returns (uint256, bytes memory)
{
if (token().balanceOf(from) < maxPossibleCharge) {
return _rejectRelayedCall(uint256(GSNRecipientERC20FeeErrorCodes.INSUFFICIENT_BALANCE));
}
return _approveRelayedCall(abi.encode(from, maxPossibleCharge, transactionFee, gasPrice));
}
/**
* @dev Implements the precharge to the user. The maximum possible charge (depending on gas limit, gas price, and
* fee) will be deducted from the user balance of gas payment token. Note that this is an overestimation of the
* actual charge, necessary because we cannot predict how much gas the execution will actually need. The remainder
* is returned to the user in {_postRelayedCall}.
*/
function _preRelayedCall(bytes memory context) internal virtual override returns (bytes32) {
(address from, uint256 maxPossibleCharge) = abi.decode(context, (address, uint256));
// The maximum token charge is pre-charged from the user
token().safeTransferFrom(from, address(this), maxPossibleCharge);
return 0;
}
/**
* @dev Returns to the user the extra amount that was previously charged, once the actual execution cost is known.
*/
function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal virtual override {
(address from, uint256 maxPossibleCharge, uint256 transactionFee, uint256 gasPrice) =
abi.decode(context, (address, uint256, uint256, uint256));
// actualCharge is an _estimated_ charge, which assumes postRelayedCall will use all available gas.
// This implementation's gas cost can be roughly estimated as 10k gas, for the two SSTORE operations in an
// ERC20 transfer.
uint256 overestimation = _computeCharge(_POST_RELAYED_CALL_MAX_GAS - 10000, gasPrice, transactionFee);
actualCharge = actualCharge - overestimation;
// After the relayed call has been executed and the actual charge estimated, the excess pre-charge is returned
token().safeTransfer(from, maxPossibleCharge - actualCharge);
}
}
/**
* @title __unstable__ERC20Owned
* @dev An ERC20 token owned by another contract, which has minting permissions and can use transferFrom to receive
* anyone's tokens. This contract is an internal helper for GSNRecipientERC20Fee, and should not be used
* outside of this context.
*/
// solhint-disable-next-line contract-name-camelcase
contract __unstable__ERC20Owned is ERC20, Ownable {
uint256 private constant _UINT256_MAX = 2**256 - 1;
constructor(string memory name, string memory symbol) ERC20(name, symbol) { }
// The owner (GSNRecipientERC20Fee) can mint tokens
function mint(address account, uint256 amount) public virtual onlyOwner {
_mint(account, amount);
}
// The owner has 'infinite' allowance for all token holders
function allowance(address tokenOwner, address spender) public view virtual override returns (uint256) {
if (spender == owner()) {
return _UINT256_MAX;
} else {
return super.allowance(tokenOwner, spender);
}
}
// Allowance for the owner cannot be changed (it is always 'infinite')
function _approve(address tokenOwner, address spender, uint256 value) internal virtual override {
if (spender == owner()) {
return;
} else {
super._approve(tokenOwner, spender, value);
}
}
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
if (recipient == owner()) {
_transfer(sender, recipient, amount);
return true;
} else {
return super.transferFrom(sender, recipient, amount);
}
}
}

@ -1,72 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./GSNRecipient.sol";
import "../cryptography/ECDSA.sol";
/**
* @dev A xref:ROOT:gsn-strategies.adoc#gsn-strategies[GSN strategy] that allows relayed transactions through when they are
* accompanied by the signature of a trusted signer. The intent is for this signature to be generated by a server that
* performs validations off-chain. Note that nothing is charged to the user in this scheme. Thus, the server should make
* sure to account for this in their economic and threat model.
*/
contract GSNRecipientSignature is GSNRecipient {
using ECDSA for bytes32;
address private _trustedSigner;
enum GSNRecipientSignatureErrorCodes {
INVALID_SIGNER
}
/**
* @dev Sets the trusted signer that is going to be producing signatures to approve relayed calls.
*/
constructor(address trustedSigner) {
require(trustedSigner != address(0), "GSNRecipientSignature: trusted signer is the zero address");
_trustedSigner = trustedSigner;
}
/**
* @dev Ensures that only transactions with a trusted signature can be relayed through the GSN.
*/
function acceptRelayedCall(
address relay,
address from,
bytes memory encodedFunction,
uint256 transactionFee,
uint256 gasPrice,
uint256 gasLimit,
uint256 nonce,
bytes memory approvalData,
uint256
)
public
view
virtual
override
returns (uint256, bytes memory)
{
bytes memory blob = abi.encodePacked(
relay,
from,
encodedFunction,
transactionFee,
gasPrice,
gasLimit,
nonce, // Prevents replays on RelayHub
getHubAddr(), // Prevents replays in multiple RelayHubs
address(this) // Prevents replays in multiple recipients
);
if (keccak256(blob).toEthSignedMessageHash().recover(approvalData) == _trustedSigner) {
return _approveRelayedCall();
} else {
return _rejectRelayedCall(uint256(GSNRecipientSignatureErrorCodes.INVALID_SIGNER));
}
}
function _preRelayedCall(bytes memory) internal virtual override returns (bytes32) { }
function _postRelayedCall(bytes memory, bool, uint256, bytes32) internal virtual override { }
}

@ -1,269 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface for `RelayHub`, the core contract of the GSN. Users should not need to interact with this contract
* directly.
*
* See the https://github.com/OpenZeppelin/openzeppelin-gsn-helpers[OpenZeppelin GSN helpers] for more information on
* how to deploy an instance of `RelayHub` on your local test network.
*/
interface IRelayHub {
// Relay management
/**
* @dev Adds stake to a relay and sets its `unstakeDelay`. If the relay does not exist, it is created, and the caller
* of this function becomes its owner. If the relay already exists, only the owner can call this function. A relay
* cannot be its own owner.
*
* All Ether in this function call will be added to the relay's stake.
* Its unstake delay will be assigned to `unstakeDelay`, but the new value must be greater or equal to the current one.
*
* Emits a {Staked} event.
*/
function stake(address relayaddr, uint256 unstakeDelay) external payable;
/**
* @dev Emitted when a relay's stake or unstakeDelay are increased
*/
event Staked(address indexed relay, uint256 stake, uint256 unstakeDelay);
/**
* @dev Registers the caller as a relay.
* The relay must be staked for, and not be a contract (i.e. this function must be called directly from an EOA).
*
* This function can be called multiple times, emitting new {RelayAdded} events. Note that the received
* `transactionFee` is not enforced by {relayCall}.
*
* Emits a {RelayAdded} event.
*/
function registerRelay(uint256 transactionFee, string calldata url) external;
/**
* @dev Emitted when a relay is registered or re-registered. Looking at these events (and filtering out
* {RelayRemoved} events) lets a client discover the list of available relays.
*/
event RelayAdded(address indexed relay, address indexed owner, uint256 transactionFee, uint256 stake, uint256 unstakeDelay, string url);
/**
* @dev Removes (deregisters) a relay. Unregistered (but staked for) relays can also be removed.
*
* Can only be called by the owner of the relay. After the relay's `unstakeDelay` has elapsed, {unstake} will be
* callable.
*
* Emits a {RelayRemoved} event.
*/
function removeRelayByOwner(address relay) external;
/**
* @dev Emitted when a relay is removed (deregistered). `unstakeTime` is the time when unstake will be callable.
*/
event RelayRemoved(address indexed relay, uint256 unstakeTime);
/** Deletes the relay from the system, and gives back its stake to the owner.
*
* Can only be called by the relay owner, after `unstakeDelay` has elapsed since {removeRelayByOwner} was called.
*
* Emits an {Unstaked} event.
*/
function unstake(address relay) external;
/**
* @dev Emitted when a relay is unstaked for, including the returned stake.
*/
event Unstaked(address indexed relay, uint256 stake);
// States a relay can be in
enum RelayState {
Unknown, // The relay is unknown to the system: it has never been staked for
Staked, // The relay has been staked for, but it is not yet active
Registered, // The relay has registered itself, and is active (can relay calls)
Removed // The relay has been removed by its owner and can no longer relay calls. It must wait for its unstakeDelay to elapse before it can unstake
}
/**
* @dev Returns a relay's status. Note that relays can be deleted when unstaked or penalized, causing this function
* to return an empty entry.
*/
function getRelay(address relay) external view returns (uint256 totalStake, uint256 unstakeDelay, uint256 unstakeTime, address payable owner, RelayState state);
// Balance management
/**
* @dev Deposits Ether for a contract, so that it can receive (and pay for) relayed transactions.
*
* Unused balance can only be withdrawn by the contract itself, by calling {withdraw}.
*
* Emits a {Deposited} event.
*/
function depositFor(address target) external payable;
/**
* @dev Emitted when {depositFor} is called, including the amount and account that was funded.
*/
event Deposited(address indexed recipient, address indexed from, uint256 amount);
/**
* @dev Returns an account's deposits. These can be either a contract's funds, or a relay owner's revenue.
*/
function balanceOf(address target) external view returns (uint256);
/**
* Withdraws from an account's balance, sending it back to it. Relay owners call this to retrieve their revenue, and
* contracts can use it to reduce their funding.
*
* Emits a {Withdrawn} event.
*/
function withdraw(uint256 amount, address payable dest) external;
/**
* @dev Emitted when an account withdraws funds from `RelayHub`.
*/
event Withdrawn(address indexed account, address indexed dest, uint256 amount);
// Relaying
/**
* @dev Checks if the `RelayHub` will accept a relayed operation.
* Multiple things must be true for this to happen:
* - all arguments must be signed for by the sender (`from`)
* - the sender's nonce must be the current one
* - the recipient must accept this transaction (via {acceptRelayedCall})
*
* Returns a `PreconditionCheck` value (`OK` when the transaction can be relayed), or a recipient-specific error
* code if it returns one in {acceptRelayedCall}.
*/
function canRelay(
address relay,
address from,
address to,
bytes calldata encodedFunction,
uint256 transactionFee,
uint256 gasPrice,
uint256 gasLimit,
uint256 nonce,
bytes calldata signature,
bytes calldata approvalData
) external view returns (uint256 status, bytes memory recipientContext);
// Preconditions for relaying, checked by canRelay and returned as the corresponding numeric values.
enum PreconditionCheck {
OK, // All checks passed, the call can be relayed
WrongSignature, // The transaction to relay is not signed by requested sender
WrongNonce, // The provided nonce has already been used by the sender
AcceptRelayedCallReverted, // The recipient rejected this call via acceptRelayedCall
InvalidRecipientStatusCode // The recipient returned an invalid (reserved) status code
}
/**
* @dev Relays a transaction.
*
* For this to succeed, multiple conditions must be met:
* - {canRelay} must `return PreconditionCheck.OK`
* - the sender must be a registered relay
* - the transaction's gas price must be larger or equal to the one that was requested by the sender
* - the transaction must have enough gas to not run out of gas if all internal transactions (calls to the
* recipient) use all gas available to them
* - the recipient must have enough balance to pay the relay for the worst-case scenario (i.e. when all gas is
* spent)
*
* If all conditions are met, the call will be relayed and the recipient charged. {preRelayedCall}, the encoded
* function and {postRelayedCall} will be called in that order.
*
* Parameters:
* - `from`: the client originating the request
* - `to`: the target {IRelayRecipient} contract
* - `encodedFunction`: the function call to relay, including data
* - `transactionFee`: fee (%) the relay takes over actual gas cost
* - `gasPrice`: gas price the client is willing to pay
* - `gasLimit`: gas to forward when calling the encoded function
* - `nonce`: client's nonce
* - `signature`: client's signature over all previous params, plus the relay and RelayHub addresses
* - `approvalData`: dapp-specific data forwarded to {acceptRelayedCall}. This value is *not* verified by the
* `RelayHub`, but it still can be used for e.g. a signature.
*
* Emits a {TransactionRelayed} event.
*/
function relayCall(
address from,
address to,
bytes calldata encodedFunction,
uint256 transactionFee,
uint256 gasPrice,
uint256 gasLimit,
uint256 nonce,
bytes calldata signature,
bytes calldata approvalData
) external;
/**
* @dev Emitted when an attempt to relay a call failed.
*
* This can happen due to incorrect {relayCall} arguments, or the recipient not accepting the relayed call. The
* actual relayed call was not executed, and the recipient not charged.
*
* The `reason` parameter contains an error code: values 1-10 correspond to `PreconditionCheck` entries, and values
* over 10 are custom recipient error codes returned from {acceptRelayedCall}.
*/
event CanRelayFailed(address indexed relay, address indexed from, address indexed to, bytes4 selector, uint256 reason);
/**
* @dev Emitted when a transaction is relayed.
* Useful when monitoring a relay's operation and relayed calls to a contract
*
* Note that the actual encoded function might be reverted: this is indicated in the `status` parameter.
*
* `charge` is the Ether value deducted from the recipient's balance, paid to the relay's owner.
*/
event TransactionRelayed(address indexed relay, address indexed from, address indexed to, bytes4 selector, RelayCallStatus status, uint256 charge);
// Reason error codes for the TransactionRelayed event
enum RelayCallStatus {
OK, // The transaction was successfully relayed and execution successful - never included in the event
RelayedCallFailed, // The transaction was relayed, but the relayed call failed
PreRelayedFailed, // The transaction was not relayed due to preRelatedCall reverting
PostRelayedFailed, // The transaction was relayed and reverted due to postRelatedCall reverting
RecipientBalanceChanged // The transaction was relayed and reverted due to the recipient's balance changing
}
/**
* @dev Returns how much gas should be forwarded to a call to {relayCall}, in order to relay a transaction that will
* spend up to `relayedCallStipend` gas.
*/
function requiredGas(uint256 relayedCallStipend) external view returns (uint256);
/**
* @dev Returns the maximum recipient charge, given the amount of gas forwarded, gas price and relay fee.
*/
function maxPossibleCharge(uint256 relayedCallStipend, uint256 gasPrice, uint256 transactionFee) external view returns (uint256);
// Relay penalization.
// Any account can penalize relays, removing them from the system immediately, and rewarding the
// reporter with half of the relay's stake. The other half is burned so that, even if the relay penalizes itself, it
// still loses half of its stake.
/**
* @dev Penalize a relay that signed two transactions using the same nonce (making only the first one valid) and
* different data (gas price, gas limit, etc. may be different).
*
* The (unsigned) transaction data and signature for both transactions must be provided.
*/
function penalizeRepeatedNonce(bytes calldata unsignedTx1, bytes calldata signature1, bytes calldata unsignedTx2, bytes calldata signature2) external;
/**
* @dev Penalize a relay that sent a transaction that didn't target ``RelayHub``'s {registerRelay} or {relayCall}.
*/
function penalizeIllegalTransaction(bytes calldata unsignedTx, bytes calldata signature) external;
/**
* @dev Emitted when a relay is penalized.
*/
event Penalized(address indexed relay, address sender, uint256 amount);
/**
* @dev Returns an account's nonce in `RelayHub`.
*/
function getNonce(address from) external view returns (uint256);
}

@ -1,76 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Base interface for a contract that will be called via the GSN from {IRelayHub}.
*
* TIP: You don't need to write an implementation yourself! Inherit from {GSNRecipient} instead.
*/
interface IRelayRecipient {
/**
* @dev Returns the address of the {IRelayHub} instance this recipient interacts with.
*/
function getHubAddr() external view returns (address);
/**
* @dev Called by {IRelayHub} to validate if this recipient accepts being charged for a relayed call. Note that the
* recipient will be charged regardless of the execution result of the relayed call (i.e. if it reverts or not).
*
* The relay request was originated by `from` and will be served by `relay`. `encodedFunction` is the relayed call
* calldata, so its first four bytes are the function selector. The relayed call will be forwarded `gasLimit` gas,
* and the transaction executed with a gas price of at least `gasPrice`. ``relay``'s fee is `transactionFee`, and the
* recipient will be charged at most `maxPossibleCharge` (in wei). `nonce` is the sender's (`from`) nonce for
* replay attack protection in {IRelayHub}, and `approvalData` is a optional parameter that can be used to hold a signature
* over all or some of the previous values.
*
* Returns a tuple, where the first value is used to indicate approval (0) or rejection (custom non-zero error code,
* values 1 to 10 are reserved) and the second one is data to be passed to the other {IRelayRecipient} functions.
*
* {acceptRelayedCall} is called with 50k gas: if it runs out during execution, the request will be considered
* rejected. A regular revert will also trigger a rejection.
*/
function acceptRelayedCall(
address relay,
address from,
bytes calldata encodedFunction,
uint256 transactionFee,
uint256 gasPrice,
uint256 gasLimit,
uint256 nonce,
bytes calldata approvalData,
uint256 maxPossibleCharge
)
external
view
returns (uint256, bytes memory);
/**
* @dev Called by {IRelayHub} on approved relay call requests, before the relayed call is executed. This allows to e.g.
* pre-charge the sender of the transaction.
*
* `context` is the second value returned in the tuple by {acceptRelayedCall}.
*
* Returns a value to be passed to {postRelayedCall}.
*
* {preRelayedCall} is called with 100k gas: if it runs out during execution or otherwise reverts, the relayed call
* will not be executed, but the recipient will still be charged for the transaction's cost.
*/
function preRelayedCall(bytes calldata context) external returns (bytes32);
/**
* @dev Called by {IRelayHub} on approved relay call requests, after the relayed call is executed. This allows to e.g.
* charge the user for the relayed call costs, return any overcharges from {preRelayedCall}, or perform
* contract-specific bookkeeping.
*
* `context` is the second value returned in the tuple by {acceptRelayedCall}. `success` is the execution status of
* the relayed call. `actualCharge` is an estimate of how much the recipient will be charged for the transaction,
* not including any gas used by {postRelayedCall} itself. `preRetVal` is {preRelayedCall}'s return value.
*
*
* {postRelayedCall} is called with 100k gas: if it runs out during execution or otherwise reverts, the relayed call
* and the call to {preRelayedCall} will be reverted retroactively, but the recipient will still be charged for the
* transaction's cost.
*/
function postRelayedCall(bytes calldata context, bool success, uint256 actualCharge, bytes32 preRetVal) external;
}

@ -1,31 +0,0 @@
= Gas Station Network (GSN)
[.readme-notice]
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/gsn
This set of contracts provide all the tools required to make a contract callable via the https://gsn.openzeppelin.com[Gas Station Network].
TIP: If you're new to the GSN, head over to our xref:learn::sending-gasless-transactions.adoc[overview of the system] and basic guide to xref:ROOT:gsn.adoc[creating a GSN-capable contract].
The core contract a recipient must inherit from is {GSNRecipient}: it includes all necessary interfaces, as well as some helper methods to make interacting with the GSN easier.
Utilities to make writing xref:ROOT:gsn-strategies.adoc[GSN strategies] easy are available in {GSNRecipient}, or you can simply use one of our pre-made strategies:
* {GSNRecipientERC20Fee} charges the end user for gas costs in an application-specific xref:ROOT:tokens.adoc#ERC20[ERC20 token]
* {GSNRecipientSignature} accepts all relayed calls that have been signed by a trusted third party (e.g. a private key in a backend)
You can also take a look at the two contract interfaces that make up the GSN protocol: {IRelayRecipient} and {IRelayHub}, but you won't need to use those directly.
== Recipient
{{GSNRecipient}}
== Strategies
{{GSNRecipientSignature}}
{{GSNRecipientERC20Fee}}
== Protocol
{{IRelayRecipient}}
{{IRelayHub}}

@ -1,30 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC721/ERC721.sol";
import "../GSN/GSNRecipient.sol";
import "../GSN/GSNRecipientSignature.sol";
/**
* @title ERC721GSNRecipientMock
* A simple ERC721 mock that has GSN support enabled
*/
contract ERC721GSNRecipientMock is ERC721, GSNRecipient, GSNRecipientSignature {
constructor(string memory name, string memory symbol, address trustedSigner)
ERC721(name, symbol)
GSNRecipientSignature(trustedSigner)
{ }
function mint(uint256 tokenId) public {
_mint(_msgSender(), tokenId);
}
function _msgSender() internal view override(Context, GSNRecipient) returns (address) {
return GSNRecipient._msgSender();
}
function _msgData() internal view override(Context, GSNRecipient) returns (bytes memory) {
return GSNRecipient._msgData();
}
}

@ -1,20 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../GSN/GSNRecipient.sol";
import "../GSN/GSNRecipientERC20Fee.sol";
contract GSNRecipientERC20FeeMock is GSNRecipient, GSNRecipientERC20Fee {
constructor(string memory name, string memory symbol) GSNRecipientERC20Fee(name, symbol) { }
function mint(address account, uint256 amount) public {
_mint(account, amount);
}
event MockFunctionCalled(uint256 senderBalance);
function mockFunction() public {
emit MockFunctionCalled(token().balanceOf(_msgSender()));
}
}

@ -1,38 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./ContextMock.sol";
import "../GSN/GSNRecipient.sol";
// By inheriting from GSNRecipient, Context's internal functions are overridden automatically
contract GSNRecipientMock is ContextMock, GSNRecipient {
function withdrawDeposits(uint256 amount, address payable payee) public {
_withdrawDeposits(amount, payee);
}
function acceptRelayedCall(address, address, bytes calldata, uint256, uint256, uint256, uint256, bytes calldata, uint256)
external
pure
override
returns (uint256, bytes memory)
{
return (0, "");
}
function _preRelayedCall(bytes memory) internal override returns (bytes32) { }
function _postRelayedCall(bytes memory, bool, uint256, bytes32) internal override { }
function upgradeRelayHub(address newRelayHub) public {
return _upgradeRelayHub(newRelayHub);
}
function _msgSender() internal override(Context, GSNRecipient) view virtual returns (address) {
return GSNRecipient._msgSender();
}
function _msgData() internal override(Context, GSNRecipient) view virtual returns (bytes calldata) {
return GSNRecipient._msgData();
}
}

@ -1,16 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../GSN/GSNRecipient.sol";
import "../GSN/GSNRecipientSignature.sol";
contract GSNRecipientSignatureMock is GSNRecipient, GSNRecipientSignature {
constructor(address trustedSigner) GSNRecipientSignature(trustedSigner) { }
event MockFunctionCalled();
function mockFunction() public {
emit MockFunctionCalled();
}
}

@ -6,7 +6,7 @@ pragma solidity ^0.8.0;
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*

@ -13,7 +13,4 @@
** xref:erc777.adoc[ERC777]
** xref:erc1155.adoc[ERC1155]
* xref:gsn.adoc[Gas Station Network]
** xref:gsn-strategies.adoc[Strategies]
* xref:utilities.adoc[Utilities]

@ -1,160 +0,0 @@
= GSN Strategies
This guide shows you different strategies to accept relayed calls via the Gas Station Network (GSN) using OpenZeppelin Contracts.
First, we will go over what a 'GSN strategy' is, and then showcase how to use the two most common strategies.
Finally, we will cover how to create your own custom strategies.
If you're still learning about the basics of the Gas Station Network, you should first head over to the xref:gsn.adoc[GSN Guide].
[[gsn-strategies]]
== GSN Strategies Explained
A *GSN strategy* decides which relayed call gets approved and which relayed call gets rejected. Strategies are a key concept within the GSN. Dapps need a strategy to prevent malicious users from spending the dapp's funds for relayed call fees.
As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, your contracts need to extend from xref:api:GSN.adoc#GSNRecipient[`GSNRecipient`].
A GSN recipient contract needs the following to work:
1. It needs to have funds deposited on its RelayHub.
2. It needs to handle `msg.sender` and `msg.data` differently.
3. It needs to decide how to approve and reject relayed calls.
Depositing funds for the GSN recipient contract can be done via the https://gsn.openzeppelin.com/recipients[GSN Dapp tool] or programmatically with xref:gsn-helpers::api.adoc#javascript_interface[*OpenZeppelin GSN Helpers*].
The actual user's `msg.sender` and `msg.data` can be obtained safely via xref:api:GSN.adoc#GSNRecipient-_msgSender--[`_msgSender()`] and xref:api:GSN.adoc#GSNRecipient-_msgData--[`_msgData()`] of xref:api:GSN.adoc#GSNRecipient[`GSNRecipient`].
Deciding how to approve and reject relayed calls is a bit more complex. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these _GSN strategies_.
The base xref:api:GSN.adoc#GSNRecipient[`GSNRecipient`] contract doesn't include a strategy, so you must either use one of the pre-built ones or write your own. We will first go over using the included strategies: xref:api:GSN.adoc#GSNRecipientSignature[`GSNRecipientSignature`] and xref:api:GSN.adoc#GSNRecipientERC20Fee[`GSNRecipientERC20Fee`].
== `GSNRecipientSignature`
xref:api:GSN.adoc#GSNRecipientSignature[`GSNRecipientSignature`] lets users relay calls via the GSN to your recipient contract (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_.
The relayed call must include a signature of the relayed call parameters by the same account that was added to the contract as a trusted signer. If it is not the same, `GSNRecipientSignature` will not accept the relayed call.
This means that you need to set up a system where your trusted account signs the relayed call parameters to then include in the relayed call, as long as they are valid users (according to your business logic).
The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of https://en.wikipedia.org/wiki/OAuth[OAuth] and validation, e.g. gone through a captcha or validated their email address.
You could restrict it further and let new users send a specific number of relayed calls (e.g. limit to 5 relayed calls via the GSN, at which point the user needs to create a wallet).
Alternatively, you could charge the user off-chain (e.g. via credit card) for credit on your system and let them create relayed calls until their credit runs out.
The great thing about this setup is that *your contract doesn't need to change* if you want to change the business rules. All you are doing is changing the backend logic conditions under which users use your contract for free.
On the other hand, you need to have a backend server, microservice, or lambda function to accomplish this.
=== How Does `GSNRecipientSignature` Work?
`GSNRecipientSignature` decides whether or not to accept the relayed call based on the included signature.
The `acceptRelayedCall` implementation recovers the address from the signature of the relayed call parameters in `approvalData` and compares it to the trusted signer.
If the included signature matches the trusted signer, the relayed call is approved.
On the other hand, when the included signature doesn't match the trusted signer, the relayed call gets rejected with an error code of `INVALID_SIGNER`.
=== How to Use `GSNRecipientSignature`
You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls to sign (or not sign, based on your business logic) the relayed call parameters with your trusted signer account. The signature is then included as the `approvalData` in the relayed call.
Instead of using `GSNRecipient` directly, your GSN recipient contract will instead inherit from `GSNRecipientSignature`, as well as setting the trusted signer in the constructor of `GSNRecipientSignature` as per the following sample code below:
[source,solidity]
----
import "@openzeppelin/contracts/GSN/GSNRecipientSignature.sol";
contract MyContract is GSNRecipientSignature {
constructor(address trustedSigner) GSNRecipientSignature(trustedSigner) {
}
}
----
TIP: We wrote an in-depth guide on how to setup a signing server that works with `GSNRecipientSignature`, https://forum.openzeppelin.com/t/advanced-gsn-gsnrecipientsignature-sol/1414[check it out!]
== `GSNRecipientERC20Fee`
xref:api:GSN.adoc#GSNRecipientERC20Fee[`GSNRecipientERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNRecipientSignature`, `GSNRecipientERC20Fee` doesn't require any off-chain services.
Instead of off-chain approving each relayed call, you will give special-purpose ERC20 tokens to your users. These tokens are then used as payment for relayed calls to your recipient contract.
Any user that has enough tokens to pay has their relayed calls automatically approved and the recipient contract will cover their transaction costs!
Each recipient contract has their own special-purpose token. The exchange rate from token to ether is 1:1, as the tokens are used to pay your contract to cover the gas fees when using the GSN.
`GSNRecipientERC20Fee` has an internal xref:api:GSN.adoc#GSNRecipientERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as xref:api:access.adoc#MinterRole-onlyMinter--[`onlyMinter`]).
Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when users buy them off-chain, give tokens based on a users subscription, etc.
NOTE: *Users do not need to call approve* on their tokens for your recipient contract to use them. They are a modified ERC20 variant that lets the recipient contract retrieve them.
=== How Does `GSNRecipientERC20Fee` Work?
`GSNRecipientERC20Fee` decides to approve or reject relayed calls based on the balance of the users tokens.
The `acceptRelayedCall` function implementation checks the users token balance.
If the user doesn't have enough tokens the relayed call gets rejected with an error of `INSUFFICIENT_BALANCE`.
If there are enough tokens, the relayed call is approved with the end users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`.
In `_preRelayedCall` function the `maxPossibleCharge` amount of tokens is transferred to the recipient contract.
The maximum amount of tokens required is transferred assuming that the relayed call will use all the gas available.
Then, in the `_postRelayedCall` function, the actual amount is calculated, including the recipient contract implementation and ERC20 token transfers, and the difference is refunded.
The maximum amount of tokens required is transferred in `_preRelayedCall` to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions).
NOTE: The gas cost estimation is not 100% accurate, we may tweak it further down the road.
NOTE: Always use `_preRelayedCall` and `_postRelayedCall` functions. Internal `_preRelayedCall` and `_postRelayedCall` functions are used instead of public `preRelayedCall` and `postRelayedCall` functions, as the public functions are prevented from being called by non-RelayHub contracts.
=== How to Use `GSNRecipientERC20Fee`
Your GSN recipient contract needs to inherit from `GSNRecipientERC20Fee` along with appropriate xref:access-control.adoc[access control] (for token minting), set the token details in the constructor of `GSNRecipientERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses xref:api:access.adoc#AccessControl[`AccessControl`]):
[source,solidity]
----
// contracts/MyContract.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/GSN/GSNRecipientERC20Fee.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MyContract is GSNRecipientERC20Fee, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() GSNRecipientERC20Fee("FeeToken", "FEE") {
_setupRole(MINTER_ROLE, _msgSender());
}
function _msgSender() internal view override(Context, GSNRecipient) returns (address) {
return GSNRecipient._msgSender();
}
function _msgData() internal view override(Context, GSNRecipient) returns (bytes memory) {
return GSNRecipient._msgData();
}
function mint(address account, uint256 amount) public {
require(hasRole(MINTER_ROLE, _msgSender()), "Caller is not a minter");
_mint(account, amount);
}
}
----
== Custom Strategies
If the included strategies don't quite fit your business needs, you can also write your own! For example, your custom strategy could use a specified token to pay for relayed calls with a custom exchange rate to ether. Alternatively you could issue users who subscribe to your dapp ERC721 tokens, and accounts holding the subscription token could use your contract for free as part of the subscription. There are lots of potential options!
To write a custom strategy, simply inherit from `GSNRecipient` and implement the `acceptRelayedCall`, `_preRelayedCall` and `_postRelayedCall` functions.
Your `acceptRelayedCall` implementation decides whether or not to accept the relayed call: return `_approveRelayedCall` to accept, and return `_rejectRelayedCall` with an error code to reject.
Not all GSN strategies use `_preRelayedCall` and `_postRelayedCall` (though they must still be implemented, e.g. `GSNRecipientSignature` leaves them empty), but are useful when your strategy involves charging end users.
`_preRelayedCall` should take the maximum possible charge, with `_postRelayedCall` refunding any difference from the actual charge once the relayed call has been made.
When returning `_approveRelayedCall` to approve the relayed call, the end users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data can also be returned so that the data can be used in `_preRelayedCall` and `_postRelayedCall`.
See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/GSN/GSNRecipientERC20Fee.sol[the code for `GSNRecipientERC20Fee`] as an example implementation.
Once your strategy is ready, all your GSN recipient needs to do is inherit from it:
[source,solidity]
----
contract MyContract is MyCustomGSNStrategy {
constructor() MyCustomGSNStrategy() {
}
}
----

@ -1,90 +0,0 @@
= Writing GSN-capable contracts
The https://gsn.openzeppelin.com[Gas Station Network] allows you to build apps where you pay for your users transactions, so they do not need to hold Ether to pay for gas, easing their onboarding process. In this guide, we will learn how to write smart contracts that can receive transactions from the GSN, by using OpenZeppelin Contracts.
If you're new to the GSN, you probably want to first take a look at the xref:learn::sending-gasless-transactions.adoc[overview of the system] to get a clearer picture of how gasless transactions are achieved. Otherwise, strap in!
== Receiving a Relayed Call
The first step to writing a recipient is to inherit from our GSNRecipient contract. If you're also inheriting from other contracts, such as ERC20, this will work just fine: adding GSNRecipient will make all of your token functions GSN-callable.
```solidity
import "@openzeppelin/contracts/GSN/GSNRecipient.sol";
contract MyContract is GSNRecipient, ... {
}
```
=== `msg.sender` and `msg.data`
There's only one extra detail you need to take care of when working with GSN recipient contracts: _you must never use `msg.sender` or `msg.data` directly_. On relayed calls, `msg.sender` will be `RelayHub` instead of your user! This doesn't mean however you won't be able to retrieve your users' addresses: `GSNRecipient` provides two functions, `_msgSender()` and `_msgData()`, which are drop-in replacements for `msg.sender` and `msg.data` while taking care of the low-level details. As long as you use these two functions instead of the original getters, you're good to go!
WARNING: Third-party contracts you inherit from may not use these replacement functions, making them unsafe to use when mixed with `GSNRecipient`. If in doubt, head on over to our https://forum.openzeppelin.com/c/support[support forum].
=== Accepting and Charging
Unlike regular contract function calls, each relayed call has an additional number of steps it must go through, which are functions of the `IRelayRecipient` interface `RelayHub` will call on your contract. `GSNRecipient` includes this interface but no implementation: most of writing a recipient involves handling these function calls. They are designed to provide flexibility, but basic recipients can safely ignore most of them while still being secure and sound.
The OpenZeppelin Contracts provide a number of tried-and-tested approaches for you to use out of the box, but you should still have a basic idea of what's going on under the hood.
==== `acceptRelayedCall`
First, RelayHub will ask your recipient contract if it wants to receive a relayed call. Recall that you will be charged for incurred gas costs by the relayer, so you should only accept calls that you're willing to pay for!
[source,solidity]
----
function acceptRelayedCall(
address relay,
address from,
bytes calldata encodedFunction,
uint256 transactionFee,
uint256 gasPrice,
uint256 gasLimit,
uint256 nonce,
bytes calldata approvalData,
uint256 maxPossibleCharge
) external view returns (uint256, bytes memory);
----
There are multiple ways to make this work, including:
. having a whitelist of trusted users
. only accepting calls to an onboarding function
. charging users in tokens (possibly issued by you)
. delegating the acceptance logic off-chain
All relayed call requests can be rejected at no cost to the recipient.
In this function, you return a number indicating whether you accept the call (0) or not (any other number). You can also return some arbitrary data that will get passed along to the following two functions (pre and post) as an execution context.
=== pre and postRelayedCall
After a relayed call is accepted, RelayHub will give your contract two opportunities to charge your user for their call, perform some bookkeeping, etc.: _before_ and _after_ the actual relayed call is made. These functions are aptly named `preRelayedCall` and `postRelayedCall`.
[source,solidity]
----
function preRelayedCall(bytes calldata context) external returns (bytes32);
----
`preRelayedCall` will inform you of the maximum cost the call may have, and can be used to charge the user in advance. This is useful if the user may spend their allowance as part of the call, so you can lock some funds here.
[source,solidity]
----
function postRelayedCall(
bytes calldata context,
bool success,
uint256 actualCharge,
bytes32 preRetVal
) external;
----
`postRelayedCall` will give you an accurate estimate of the transaction cost, making it a natural place to charge users. It will also let you know if the relayed call reverted or not. This allows you, for instance, to not charge users for reverted calls - but remember that you will be charged by the relayer nonetheless.
These functions allow you to implement, for instance, a flow where you charge your users for the relayed transactions in a custom token. You can lock some of their tokens in `pre`, and execute the actual charge in `post`. This is similar to how gas fees work in Ethereum: the network first locks enough ETH to pay for the transaction's gas limit at its gas price, and then pays for what it actually spent.
== Further reading
Read our xref:gsn-strategies.adoc[guide on the payment strategies] pre-built and shipped in OpenZeppelin Contracts, or check out xref:api:GSN.adoc[the API reference of the GSN base contracts].

@ -5,7 +5,6 @@
* Implementations of standards like xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721].
* Flexible xref:access-control.adoc[role-based permissioning] scheme.
* Reusable xref:utilities.adoc[Solidity components] to build custom contracts and complex decentralized systems.
* First-class integration with the xref:gsn.adoc[Gas Station Network] for systems with no gas fees!
* https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/audit[Audited] by leading security firms (_last full audit on v2.0.0_).
== Overview
@ -49,7 +48,6 @@ The guides in the sidebar will teach about different concepts, and how to use th
* xref:access-control.adoc[Access Control]: decide who can perform each of the actions on your system.
* xref:tokens.adoc[Tokens]: create tradable assets or collectibles, like the well known xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721] standards.
* xref:gsn.adoc[Gas Station Network]: let your users interact with your contracts without having to pay for gas themselves.
* xref:utilities.adoc[Utilities]: generic useful tools, including non-overflowing math, signature verification, and trustless paying systems.
The xref:api:token/ERC20.adoc[full API] is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts' development in the https://forum.openzeppelin.com[community forum].
@ -59,4 +57,3 @@ Finally, you may want to take a look at the https://blog.openzeppelin.com/guides
* https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05[The Hitchhiker’s Guide to Smart Contracts in Ethereum] will help you get an overview of the various tools available for smart contract development, and help you set up your environment.
* https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094[A Gentle Introduction to Ethereum Programming, Part 1] provides very useful information on an introductory level, including many basic concepts from the Ethereum platform.
* For a more in-depth dive, you may read the guide https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317[Designing the architecture for your Ethereum application], which discusses how to better structure your application and its relationship to the real world.

17020
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -50,8 +50,6 @@
"@nomiclabs/hardhat-truffle5": "^2.0.0",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/docs-utils": "^0.1.0",
"@openzeppelin/gsn-helpers": "^0.2.3",
"@openzeppelin/gsn-provider": "^0.1.10",
"@openzeppelin/test-helpers": "^0.5.9",
"chai": "^4.2.0",
"eslint": "^6.5.1",

@ -1,55 +0,0 @@
const { constants, expectEvent } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const gsn = require('@openzeppelin/gsn-helpers');
const { fixSignature } = require('../helpers/sign');
const { setGSNProvider } = require('../helpers/set-gsn-provider');
const { utils: { toBN } } = require('web3');
const ERC721GSNRecipientMock = artifacts.require('ERC721GSNRecipientMock');
contract('ERC721GSNRecipient (integration)', function (accounts) {
const [ signer, sender ] = accounts;
const name = 'Non Fungible Token';
const symbol = 'NFT';
const tokenId = '42';
before(function () {
setGSNProvider(ERC721GSNRecipientMock, accounts);
});
beforeEach(async function () {
this.token = await ERC721GSNRecipientMock.new(name, symbol, signer);
});
async function testMintToken (token, from, tokenId, options = {}) {
const { tx } = await token.mint(tokenId, { from, ...options });
await expectEvent.inTransaction(tx, ERC721GSNRecipientMock, 'Transfer', { from: ZERO_ADDRESS, to: from, tokenId });
}
context('when called directly', function () {
it('sender can mint tokens', async function () {
await testMintToken(this.token, sender, tokenId);
});
});
context('when relay-called', function () {
beforeEach(async function () {
await gsn.fundRecipient(web3, { recipient: this.token.address });
});
it('sender can mint tokens', async function () {
const approveFunction = async (data) =>
fixSignature(
await web3.eth.sign(
web3.utils.soliditySha3(
// eslint-disable-next-line max-len
data.relayerAddress, data.from, data.encodedFunctionCall, toBN(data.txFee), toBN(data.gasPrice), toBN(data.gas), toBN(data.nonce), data.relayHubAddress, this.token.address,
), signer,
),
);
await testMintToken(this.token, sender, tokenId, { useGSN: true, approveFunction });
});
});
});

@ -1,123 +0,0 @@
const { balance, BN, constants, ether, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const gsn = require('@openzeppelin/gsn-helpers');
const { setGSNProvider } = require('../helpers/set-gsn-provider');
const { expect } = require('chai');
const GSNRecipientMock = artifacts.require('GSNRecipientMock');
const ContextMockCaller = artifacts.require('ContextMockCaller');
const { shouldBehaveLikeRegularContext } = require('./Context.behavior');
contract('GSNRecipient', function (accounts) {
const [ payee, sender, newRelayHub ] = accounts;
before(function () {
setGSNProvider(GSNRecipientMock, accounts);
setGSNProvider(ContextMockCaller, accounts);
});
beforeEach(async function () {
this.recipient = await GSNRecipientMock.new();
});
it('returns the compatible RelayHub version', async function () {
expect(await this.recipient.relayHubVersion()).to.equal('1.0.0');
});
describe('get/set RelayHub', function () {
const singletonRelayHub = '0xD216153c06E857cD7f72665E0aF1d7D82172F494';
it('initially returns the singleton instance address', async function () {
expect(await this.recipient.getHubAddr()).to.equal(singletonRelayHub);
});
it('can be upgraded to a new RelayHub', async function () {
const { logs } = await this.recipient.upgradeRelayHub(newRelayHub);
expectEvent.inLogs(logs, 'RelayHubChanged', { oldRelayHub: singletonRelayHub, newRelayHub });
});
it('cannot upgrade to the same RelayHub', async function () {
await expectRevert(
this.recipient.upgradeRelayHub(singletonRelayHub),
'GSNRecipient: new RelayHub is the current one',
);
});
it('cannot upgrade to the zero address', async function () {
await expectRevert(
this.recipient.upgradeRelayHub(ZERO_ADDRESS), 'GSNRecipient: new RelayHub is the zero address',
);
});
context('with new RelayHub', function () {
beforeEach(async function () {
await this.recipient.upgradeRelayHub(newRelayHub);
});
it('returns the new instance address', async function () {
expect(await this.recipient.getHubAddr()).to.equal(newRelayHub);
});
});
});
context('when called directly', function () {
beforeEach(async function () {
this.context = this.recipient; // The Context behavior expects the contract in this.context
this.caller = await ContextMockCaller.new();
});
shouldBehaveLikeRegularContext(sender);
});
context('when receiving a relayed call', function () {
beforeEach(async function () {
await gsn.fundRecipient(web3, { recipient: this.recipient.address });
});
describe('msgSender', function () {
it('returns the relayed transaction original sender', async function () {
const { tx } = await this.recipient.msgSender({ from: sender, useGSN: true });
await expectEvent.inTransaction(tx, GSNRecipientMock, 'Sender', { sender });
});
});
describe('msgData', function () {
it('returns the relayed transaction original data', async function () {
const integerValue = new BN('42');
const stringValue = 'OpenZeppelin';
const callData = this.recipient.contract.methods.msgData(integerValue.toString(), stringValue).encodeABI();
// The provider doesn't properly estimate gas for a relayed call, so we need to manually set a higher value
const { tx } = await this.recipient.msgData(integerValue, stringValue, { gas: 1000000, useGSN: true });
await expectEvent.inTransaction(tx, GSNRecipientMock, 'Data', { data: callData, integerValue, stringValue });
});
});
});
context('with deposited funds', async function () {
const amount = ether('1');
beforeEach(async function () {
await gsn.fundRecipient(web3, { recipient: this.recipient.address, amount });
});
it('funds can be withdrawn', async function () {
const balanceTracker = await balance.tracker(payee);
await this.recipient.withdrawDeposits(amount, payee);
expect(await balanceTracker.delta()).to.be.bignumber.equal(amount);
});
it('partial funds can be withdrawn', async function () {
const balanceTracker = await balance.tracker(payee);
await this.recipient.withdrawDeposits(amount.divn(2), payee);
expect(await balanceTracker.delta()).to.be.bignumber.equal(amount.divn(2));
});
it('reverts on overwithdrawals', async function () {
await expectRevert(this.recipient.withdrawDeposits(amount.addn(1), payee), 'insufficient funds');
});
});
});

@ -1,77 +0,0 @@
const { ether, expectEvent } = require('@openzeppelin/test-helpers');
const gsn = require('@openzeppelin/gsn-helpers');
const { setGSNProvider } = require('../helpers/set-gsn-provider');
const { expect } = require('chai');
const GSNRecipientERC20FeeMock = artifacts.require('GSNRecipientERC20FeeMock');
const ERC20 = artifacts.require('ERC20');
const IRelayHub = artifacts.require('IRelayHub');
contract('GSNRecipientERC20Fee', function (accounts) {
const [ sender ] = accounts;
const name = 'FeeToken';
const symbol = 'FTKN';
before(function () {
setGSNProvider(GSNRecipientERC20FeeMock, accounts);
setGSNProvider(ERC20, accounts);
setGSNProvider(IRelayHub, accounts);
});
beforeEach(async function () {
this.recipient = await GSNRecipientERC20FeeMock.new(name, symbol);
this.token = await ERC20.at(await this.recipient.token());
});
describe('token', function () {
it('has a name', async function () {
expect(await this.token.name()).to.equal(name);
});
it('has a symbol', async function () {
expect(await this.token.symbol()).to.equal(symbol);
});
it('has 18 decimals', async function () {
expect(await this.token.decimals()).to.be.bignumber.equal('18');
});
});
context('when called directly', function () {
it('mock function can be called', async function () {
const { logs } = await this.recipient.mockFunction();
expectEvent.inLogs(logs, 'MockFunctionCalled');
});
});
context('when relay-called', function () {
beforeEach(async function () {
await gsn.fundRecipient(web3, { recipient: this.recipient.address });
this.relayHub = await IRelayHub.at('0xD216153c06E857cD7f72665E0aF1d7D82172F494');
});
it('charges the sender for GSN fees in tokens', async function () {
// The recipient will be charged from its RelayHub balance, and in turn charge the sender from its sender balance.
// Both amounts should be roughly equal.
// The sender has a balance in tokens, not ether, but since the exchange rate is 1:1, this works fine.
const senderPreBalance = ether('2');
await this.recipient.mint(sender, senderPreBalance);
const recipientPreBalance = await this.relayHub.balanceOf(this.recipient.address);
const { tx } = await this.recipient.mockFunction({ from: sender, useGSN: true });
await expectEvent.inTransaction(tx, IRelayHub, 'TransactionRelayed', { status: '0' });
const senderPostBalance = await this.token.balanceOf(sender);
const recipientPostBalance = await this.relayHub.balanceOf(this.recipient.address);
const senderCharge = senderPreBalance.sub(senderPostBalance);
const recipientCharge = recipientPreBalance.sub(recipientPostBalance);
expect(senderCharge).to.be.bignumber.closeTo(recipientCharge, recipientCharge.divn(10));
});
});
});

@ -1,93 +0,0 @@
const { expectEvent, expectRevert, constants } = require('@openzeppelin/test-helpers');
const gsn = require('@openzeppelin/gsn-helpers');
const { fixSignature } = require('../helpers/sign');
const { setGSNProvider } = require('../helpers/set-gsn-provider');
const { utils: { toBN } } = require('web3');
const { ZERO_ADDRESS } = constants;
const GSNRecipientSignatureMock = artifacts.require('GSNRecipientSignatureMock');
contract('GSNRecipientSignature', function (accounts) {
const [ signer, other ] = accounts;
before(function () {
setGSNProvider(GSNRecipientSignatureMock, accounts);
});
beforeEach(async function () {
this.recipient = await GSNRecipientSignatureMock.new(signer);
});
context('when called directly', function () {
it('mock function can be called', async function () {
const { logs } = await this.recipient.mockFunction();
expectEvent.inLogs(logs, 'MockFunctionCalled');
});
});
context('when constructor is called with a zero address', function () {
it('fails when constructor called with a zero address', async function () {
await expectRevert(
GSNRecipientSignatureMock.new(
ZERO_ADDRESS,
),
'GSNRecipientSignature: trusted signer is the zero address',
);
});
});
context('when relay-called', function () {
beforeEach(async function () {
await gsn.fundRecipient(web3, { recipient: this.recipient.address });
});
it('rejects unsigned relay requests', async function () {
await gsn.expectError(this.recipient.mockFunction({ value: 0, useGSN: true }));
});
it('rejects relay requests where some parameters are signed', async function () {
const approveFunction = async (data) =>
fixSignature(
await web3.eth.sign(
web3.utils.soliditySha3(
// the nonce is not signed
// eslint-disable-next-line max-len
data.relayerAddress, data.from, data.encodedFunctionCall, toBN(data.txFee), toBN(data.gasPrice), toBN(data.gas),
), signer,
),
);
await gsn.expectError(this.recipient.mockFunction({ value: 0, useGSN: true, approveFunction }));
});
it('accepts relay requests where all parameters are signed', async function () {
const approveFunction = async (data) =>
fixSignature(
await web3.eth.sign(
web3.utils.soliditySha3(
// eslint-disable-next-line max-len
data.relayerAddress, data.from, data.encodedFunctionCall, toBN(data.txFee), toBN(data.gasPrice), toBN(data.gas), toBN(data.nonce), data.relayHubAddress, data.to,
), signer,
),
);
const { tx } = await this.recipient.mockFunction({ value: 0, useGSN: true, approveFunction });
await expectEvent.inTransaction(tx, GSNRecipientSignatureMock, 'MockFunctionCalled');
});
it('rejects relay requests where all parameters are signed by an invalid signer', async function () {
const approveFunction = async (data) =>
fixSignature(
await web3.eth.sign(
web3.utils.soliditySha3(
// eslint-disable-next-line max-len
data.relayerAddress, data.from, data.encodedFunctionCall, toBN(data.txFee), toBN(data.gasPrice), toBN(data.gas), toBN(data.nonce), data.relayHubAddress, data.to,
), other,
),
);
await gsn.expectError(this.recipient.mockFunction({ value: 0, useGSN: true, approveFunction }));
});
});
});

@ -1,15 +0,0 @@
const { GSNDevProvider } = require('@openzeppelin/gsn-provider');
function setGSNProvider (Contract, accounts) {
const baseProvider = Contract.currentProvider;
Contract.setProvider(
new GSNDevProvider(baseProvider, {
txfee: 70,
useGSN: false,
ownerAddress: accounts[8],
relayerAddress: accounts[9],
}),
);
};
module.exports = { setGSNProvider };

@ -1,6 +0,0 @@
const { deployRelayHub } = require('@openzeppelin/gsn-helpers');
before('deploy GSN RelayHub', async function () {
const [defaultSender] = await web3.eth.getAccounts();
await deployRelayHub(web3, { from: defaultSender });
});
Loading…
Cancel
Save