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:
-
GSNRecipientERC20Fee
charges the end user for gas costs in an application-specific 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
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.
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.
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.
_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
.
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.
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.
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
.
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.