Gas Station Network (GSN)

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 Gas Station Network.

If you’re new to the GSN, head over to our overview of the system and basic guide to 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 GSN strategies easy are available in GSNRecipient, or you can simply use one of our pre-made strategies:

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

Base GSN recipient contract: includes the IRelayRecipient interface and enables GSN support on all contracts in the inheritance tree.

This contract is abstract. The functions IRelayRecipient.acceptRelayedCall, _preRelayedCall, and _postRelayedCall are not implemented and must be provided by derived contracts. See the GSN strategies for more information on how to use the pre-built GSNRecipientSignature and GSNRecipientERC20Fee, or how to write your own.

getHubAddr() → address public

Returns the address of the IRelayHub contract for this recipient.

_upgradeRelayHub(address newRelayHub) internal

Switches to a new IRelayHub instance. This method is added for future-proofing: there’s no reason to not use the default instance.

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.

relayHubVersion() → string public

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.

_withdrawDeposits(uint256 amount, address payable payee) internal

Withdraws the recipient’s deposits in RelayHub.

Derived contracts should expose this in an external interface with proper access control.

_msgSender() → address payable internal

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).

Contracts derived from GSNRecipient should never use msg.sender, and use _msgSender instead.

_msgData() → bytes internal

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).

Contracts derived from GSNRecipient should never use msg.data, and use _msgData instead.

preRelayedCall(bytes context) → bytes32 public

See IRelayRecipient.preRelayedCall.

This function should not be overridden directly, use _preRelayedCall instead.

  • Requirements:

    • the caller must be the RelayHub contract.

_preRelayedCall(bytes context) → bytes32 internal

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.

postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal) public

See IRelayRecipient.postRelayedCall.

This function should not be overridden directly, use _postRelayedCall instead.

  • Requirements:

    • the caller must be the RelayHub contract.

_postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal) internal

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.

_approveRelayedCall() → uint256, bytes internal

Return this in acceptRelayedCall to proceed with the execution of a relayed call. Note that this contract will be charged a fee by RelayHub

_approveRelayedCall(bytes context) → uint256, bytes internal

See GSNRecipient._approveRelayedCall.

This overload forwards context to _preRelayedCall and _postRelayedCall.

_rejectRelayedCall(uint256 errorCode) → uint256, bytes internal

Return this in acceptRelayedCall to impede execution of a relayed call. No fees will be charged.

_computeCharge(uint256 gas, uint256 gasPrice, uint256 serviceFee) → uint256 internal

RelayHubChanged(address oldRelayHub, address newRelayHub) event

Emitted when a contract changes its IRelayHub contract to a new one.

Strategies

GSNRecipientSignature

A 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.

constructor(address trustedSigner) public

Sets the trusted signer that is going to be producing signatures to approve relayed calls.

acceptRelayedCall(address relay, address from, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes approvalData, uint256) → uint256, bytes public

Ensures that only transactions with a trusted signature can be relayed through the GSN.

_preRelayedCall(bytes) → bytes32 internal

_postRelayedCall(bytes, bool, uint256, bytes32) internal

GSNRecipientERC20Fee

A 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.

constructor(string name, string symbol) public

The arguments to the constructor are the details that the gas payment token will have: name and symbol. decimals is hard-coded to 18.

token() → contract __unstable__ERC20Owned public

Returns the gas payment token.

_mint(address account, uint256 amount) internal

Internal function that mints the gas payment token. Derived contracts should expose this function in their public API, with proper access control mechanisms.

acceptRelayedCall(address, address from, bytes, uint256 transactionFee, uint256 gasPrice, uint256, uint256, bytes, uint256 maxPossibleCharge) → uint256, bytes public

Ensures that only users with enough gas payment token balance can have transactions relayed through the GSN.

_preRelayedCall(bytes context) → bytes32 internal

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.

_postRelayedCall(bytes context, bool, uint256 actualCharge, bytes32) internal

Returns to the user the extra amount that was previously charged, once the actual execution cost is known.

Protocol

IRelayRecipient

Base interface for a contract that will be called via the GSN from IRelayHub.

You don’t need to write an implementation yourself! Inherit from GSNRecipient instead.

getHubAddr() → address external

Returns the address of the IRelayHub instance this recipient interacts with.

acceptRelayedCall(address relay, address from, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes approvalData, uint256 maxPossibleCharge) → uint256, bytes external

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.

preRelayedCall(bytes context) → bytes32 external

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.

postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal) external

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.

IRelayHub

Interface for RelayHub, the core contract of the GSN. Users should not need to interact with this contract directly.

See the OpenZeppelin GSN helpers for more information on how to deploy an instance of RelayHub on your local test network.

stake(address relayaddr, uint256 unstakeDelay) external

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.

registerRelay(uint256 transactionFee, string url) external

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.

removeRelayByOwner(address relay) external

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.

unstake(address relay) external

getRelay(address relay) → uint256 totalStake, uint256 unstakeDelay, uint256 unstakeTime, address payable owner, enum IRelayHub.RelayState state external

Returns a relay’s status. Note that relays can be deleted when unstaked or penalized, causing this function to return an empty entry.

depositFor(address target) external

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.

balanceOf(address target) → uint256 external

Returns an account’s deposits. These can be either a contract’s funds, or a relay owner’s revenue.

withdraw(uint256 amount, address payable dest) external

canRelay(address relay, address from, address to, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes signature, bytes approvalData) → uint256 status, bytes recipientContext external

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.

relayCall(address from, address to, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes signature, bytes approvalData) external

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.

requiredGas(uint256 relayedCallStipend) → uint256 external

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.

maxPossibleCharge(uint256 relayedCallStipend, uint256 gasPrice, uint256 transactionFee) → uint256 external

Returns the maximum recipient charge, given the amount of gas forwarded, gas price and relay fee.

penalizeRepeatedNonce(bytes unsignedTx1, bytes signature1, bytes unsignedTx2, bytes signature2) external

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.

penalizeIllegalTransaction(bytes unsignedTx, bytes signature) external

Penalize a relay that sent a transaction that didn’t target RelayHub's registerRelay or relayCall.

getNonce(address from) → uint256 external

Returns an account’s nonce in RelayHub.

Staked(address relay, uint256 stake, uint256 unstakeDelay) event

Emitted when a relay’s stake or unstakeDelay are increased

RelayAdded(address relay, address owner, uint256 transactionFee, uint256 stake, uint256 unstakeDelay, string url) event

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.

RelayRemoved(address relay, uint256 unstakeTime) event

Emitted when a relay is removed (deregistered). unstakeTime is the time when unstake will be callable.

Unstaked(address relay, uint256 stake) event

Emitted when a relay is unstaked for, including the returned stake.

Deposited(address recipient, address from, uint256 amount) event

Emitted when depositFor is called, including the amount and account that was funded.

Withdrawn(address account, address dest, uint256 amount) event

Emitted when an account withdraws funds from RelayHub.

CanRelayFailed(address relay, address from, address to, bytes4 selector, uint256 reason) event

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.

TransactionRelayed(address relay, address from, address to, bytes4 selector, enum IRelayHub.RelayCallStatus status, uint256 charge) event

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.

Penalized(address relay, address sender, uint256 amount) event

Emitted when a relay is penalized.