Commit 5f1d8e19 by Nicolás Venturo Committed by Francisco Giordano

Improve GSN documentation (#1900)

* Improve IRelayRecipient docs

* Fix link

* Update IRelayHub docs to the docsite format

* Update IRelayRecipient docs to the dociste format

* Fix typo

* Improve GSN readme

* Fix link

* Update GSNRecipient docs

* Apply suggestions from code review

Co-Authored-By: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com>

* Apply suggestions from code review

Co-Authored-By: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com>

* Apply suggestions from code review

Co-Authored-By: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com>
(cherry picked from commit d8642286)
parent 89a90fda
...@@ -2,7 +2,7 @@ pragma solidity ^0.5.0; ...@@ -2,7 +2,7 @@ pragma solidity ^0.5.0;
import "./Context.sol"; import "./Context.sol";
/* /**
* @dev Enables GSN support on `Context` contracts by recognizing calls from * @dev Enables GSN support on `Context` contracts by recognizing calls from
* RelayHub and extracting the actual sender and call data from the received * RelayHub and extracting the actual sender and call data from the received
* calldata. * calldata.
......
...@@ -5,23 +5,22 @@ import "./GSNContext.sol"; ...@@ -5,23 +5,22 @@ import "./GSNContext.sol";
import "./bouncers/GSNBouncerBase.sol"; import "./bouncers/GSNBouncerBase.sol";
import "./IRelayHub.sol"; import "./IRelayHub.sol";
/* /**
* @dev Base GSN recipient contract, adding the recipient interface and enabling * @dev Base GSN recipient contract: includes the {IRelayRecipient} interface and enables GSN support on all contracts
* GSN support. Not all interface methods are implemented, derived contracts * in the inheritance tree.
* must do so themselves. *
* Not all interface methods are implemented (e.g. {acceptRelayedCall}, derived contracts must provide one themselves.
*/ */
contract GSNRecipient is IRelayRecipient, GSNContext, GSNBouncerBase { contract GSNRecipient is IRelayRecipient, GSNContext, GSNBouncerBase {
/** /**
* @dev Returns the RelayHub address for this recipient contract. * @dev Returns the `RelayHub` address for this recipient contract.
*/ */
function getHubAddr() public view returns (address) { function getHubAddr() public view returns (address) {
return _relayHub; return _relayHub;
} }
/** /**
* @dev This function returns the version string of the RelayHub for which * @dev Returns the version string of the `RelayHub` for which this recipient implementation was built.
* this recipient implementation was built. It's not currently used, but
* may be used by tooling.
*/ */
// This function is view for future-proofing, it may require reading from // This function is view for future-proofing, it may require reading from
// storage in the future. // storage in the future.
...@@ -31,9 +30,9 @@ contract GSNRecipient is IRelayRecipient, GSNContext, GSNBouncerBase { ...@@ -31,9 +30,9 @@ contract GSNRecipient is IRelayRecipient, GSNContext, GSNBouncerBase {
} }
/** /**
* @dev Triggers a withdraw of the recipient's deposits in RelayHub. Can * @dev Withdraws the recipient's deposits in `RelayHub`.
* be used by derived contracts to expose the functionality in an external *
* interface. * Derived contracts should expose this in an external interface with proper access control.
*/ */
function _withdrawDeposits(uint256 amount, address payable payee) internal { function _withdrawDeposits(uint256 amount, address payable payee) internal {
IRelayHub(_relayHub).withdraw(amount, payee); IRelayHub(_relayHub).withdraw(amount, payee);
......
pragma solidity ^0.5.0; pragma solidity ^0.5.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.
*/
contract IRelayHub { contract IRelayHub {
// Relay management // Relay management
// Add stake to a relay and sets its unstakeDelay. /**
// If the relay does not exist, it is created, and the caller * @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 * 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. * 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. * All Ether in this function call will be added to the relay's stake.
// Emits a Staked event. * 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; function stake(address relayaddr, uint256 unstakeDelay) external payable;
// Emited when a relay's stake or unstakeDelay are increased /**
* @dev Emitted when a relay's stake or unstakeDelay are increased
*/
event Staked(address indexed relay, uint256 stake, uint256 unstakeDelay); event Staked(address indexed relay, uint256 stake, uint256 unstakeDelay);
// 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). * @dev Registers the caller as a relay.
// Emits a RelayAdded event. * 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. * 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 memory url) public; function registerRelay(uint256 transactionFee, string memory url) public;
// Emitted when a relay is registered or re-registerd. Looking at these events (and filtering out RelayRemoved /**
// events) lets a client discover the list of available relays. * @dev Emitted when a relay is registered or re-registerd. 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); event RelayAdded(address indexed relay, address indexed owner, uint256 transactionFee, uint256 stake, uint256 unstakeDelay, string url);
// 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. * @dev Removes (deregisters) a relay. Unregistered (but staked for) relays can also be removed.
// Emits a RelayRemoved event. *
* 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) public; function removeRelayByOwner(address relay) public;
// Emitted when a relay is removed (deregistered). unstakeTime is the time when unstake will be callable. /**
* @dev Emitted when a relay is removed (deregistered). `unstakeTime` is the time when unstake will be callable.
*/
event RelayRemoved(address indexed relay, uint256 unstakeTime); 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, /** Deletes the relay from the system, and gives back its stake to the owner.
// after unstakeDelay has elapsed since removeRelayByOwner was called. *
// Emits an Unstaked event. * Can only be called by the relay owner, after `unstakeDelay` has elapsed since {removeRelayByOwner} was called.
*
* Emits an {Unstaked} event.
*/
function unstake(address relay) public; function unstake(address relay) public;
// Emitted when a relay is unstaked for, including the returned stake. /**
* @dev Emitted when a relay is unstaked for, including the returned stake.
*/
event Unstaked(address indexed relay, uint256 stake); event Unstaked(address indexed relay, uint256 stake);
// States a relay can be in // States a relay can be in
...@@ -50,38 +80,58 @@ contract IRelayHub { ...@@ -50,38 +80,58 @@ contract IRelayHub {
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 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
} }
// Returns a relay's status. Note that relays can be deleted when unstaked or penalized. /**
* @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); function getRelay(address relay) external view returns (uint256 totalStake, uint256 unstakeDelay, uint256 unstakeTime, address payable owner, RelayState state);
// Balance management // Balance management
// 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 callingn withdraw. * @dev Deposits Ether for a contract, so that it can receive (and pay for) relayed transactions.
// Emits a Deposited event. *
* Unused balance can only be withdrawn by the contract itself, by calling {withdraw}.
*
* Emits a {Deposited} event.
*/
function depositFor(address target) public payable; function depositFor(address target) public payable;
// Emitted when depositFor is called, including the amount and account that was funded. /**
* @dev Emitted when {depositFor} is called, including the amount and account that was funded.
*/
event Deposited(address indexed recipient, address indexed from, uint256 amount); event Deposited(address indexed recipient, address indexed from, uint256 amount);
// Returns an account's deposits. These can be either a contnract's funds, or a relay owner's revenue. /**
* @dev Returns an account's deposits. These can be either a contracts's funds, or a relay owner's revenue.
*/
function balanceOf(address target) external view returns (uint256); 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 also use it to reduce their funding. * Withdraws from an account's balance, sending it back to it. Relay owners call this to retrieve their revenue, and
// Emits a Withdrawn event. * contracts can use it to reduce their funding.
*
* Emits a {Withdrawn} event.
*/
function withdraw(uint256 amount, address payable dest) public; function withdraw(uint256 amount, address payable dest) public;
// Emitted when an account withdraws funds from RelayHub. /**
* @dev Emitted when an account withdraws funds from `RelayHub`.
*/
event Withdrawn(address indexed account, address indexed dest, uint256 amount); event Withdrawn(address indexed account, address indexed dest, uint256 amount);
// Relaying // Relaying
// Check 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) * @dev Checks if the `RelayHub` will accept a relayed operation.
// - the sender's nonce must be the current one * Multiple things must be true for this to happen:
// - the recipient must accept this transaction (via acceptRelayedCall) * - all arguments must be signed for by the sender (`from`)
// Returns a PreconditionCheck value (OK when the transaction can be relayed), or a recipient-specific error code if * - the sender's nonce must be the current one
// it returns one in acceptRelayedCall. * - 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( function canRelay(
address relay, address relay,
address from, address from,
...@@ -104,31 +154,35 @@ contract IRelayHub { ...@@ -104,31 +154,35 @@ contract IRelayHub {
InvalidRecipientStatusCode // The recipient returned an invalid (reserved) status code InvalidRecipientStatusCode // The recipient returned an invalid (reserved) status code
} }
// Relays a transaction. For this to suceed, multiple conditions must be met: /**
// - canRelay must return PreconditionCheck.OK * @dev Relays a transaction.
// - 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 * For this to succeed, multiple conditions must be met:
// - the transaction must have enough gas to not run out of gas if all internal transactions (calls to the * - {canRelay} must `return PreconditionCheck.OK`
// recipient) use all gas available to them * - the sender must be a registered relay
// - the recipient must have enough balance to pay the relay for the worst-case scenario (i.e. when all gas is * - the transaction's gas price must be larger or equal to the one that was requested by the sender
// spent) * - 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
// If all conditions are met, the call will be relayed and the recipient charged. preRelayedCall, the encoded * - the recipient must have enough balance to pay the relay for the worst-case scenario (i.e. when all gas is
// function and postRelayedCall will be called in order. * spent)
// *
// Arguments: * If all conditions are met, the call will be relayed and the recipient charged. {preRelayedCall}, the encoded
// - from: the client originating the request * function and {postRelayedCall} will be called in that order.
// - recipient: the target IRelayRecipient contract *
// - encodedFunction: the function call to relay, including data * Parameters:
// - transactionFee: fee (%) the relay takes over actual gas cost * - `from`: the client originating the request
// - gasPrice: gas price the client is willing to pay * - `to`: the target {IRelayRecipient} contract
// - gasLimit: gas to forward when calling the encoded function * - `encodedFunction`: the function call to relay, including data
// - nonce: client's nonce * - `transactionFee`: fee (%) the relay takes over actual gas cost
// - signature: client's signature over all previous params, plus the relay and RelayHub addresses * - `gasPrice`: gas price the client is willing to pay
// - approvalData: dapp-specific data forwared to acceptRelayedCall. This value is *not* verified by the Hub, but * - `gasLimit`: gas to forward when calling the encoded function
// it still can be used for e.g. a signature. * - `nonce`: client's nonce
// * - `signature`: client's signature over all previous params, plus the relay and RelayHub addresses
// Emits a TransactionRelayed event. * - `approvalData`: dapp-specific data forwared 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( function relayCall(
address from, address from,
address to, address to,
...@@ -141,16 +195,25 @@ contract IRelayHub { ...@@ -141,16 +195,25 @@ contract IRelayHub {
bytes memory approvalData bytes memory approvalData
) public; ) public;
// 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. * @dev Emitted when an attempt to relay a call failed.
// The reason field contains an error code: values 1-10 correspond to PreconditionCheck entries, and values over 10 *
// are custom recipient error codes returned from acceptRelayedCall. * 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); event CanRelayFailed(address indexed relay, address indexed from, address indexed to, bytes4 selector, uint256 reason);
// Emitted when a transaction is relayed. Note that the actual encoded function might be reverted: this will be /**
// indicated in the status field. * @dev Emitted when a transaction is relayed.
// Useful when monitoring a relay's operation and relayed calls to a contract. * Useful when monitoring a relay's operation and relayed calls to a contract
// Charge is the ether value deducted from the recipient's balance, paid to the relay's owner. *
* 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); event TransactionRelayed(address indexed relay, address indexed from, address indexed to, bytes4 selector, RelayCallStatus status, uint256 charge);
// Reason error codes for the TransactionRelayed event // Reason error codes for the TransactionRelayed event
...@@ -162,27 +225,43 @@ contract IRelayHub { ...@@ -162,27 +225,43 @@ contract IRelayHub {
RecipientBalanceChanged // The transaction was relayed and reverted due to the recipient's balance changing RecipientBalanceChanged // The transaction was relayed and reverted due to the recipient's balance changing
} }
// 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. * @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) public view returns (uint256); function requiredGas(uint256 relayedCallStipend) public view returns (uint256);
// Returns the maximum recipient charge, given the amount of gas forwarded, gas price and relay fee. /**
* @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) public view returns (uint256); function maxPossibleCharge(uint256 relayedCallStipend, uint256 gasPrice, uint256 transactionFee) public view returns (uint256);
// Relay penalization. Any account can penalize relays, removing them from the system immediately, and rewarding the // 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 // 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. // still loses half of its stake.
// 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 * @dev Penalize a relay that signed two transactions using the same nonce (making only the first one valid) and
// both transactions must be provided. * 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 memory unsignedTx1, bytes memory signature1, bytes memory unsignedTx2, bytes memory signature2) public; function penalizeRepeatedNonce(bytes memory unsignedTx1, bytes memory signature1, bytes memory unsignedTx2, bytes memory signature2) public;
// Penalize a relay that sent a transaction that didn't target RelayHub's registerRelay or relayCall. /**
* @dev Penalize a relay that sent a transaction that didn't target `RelayHub`'s {registerRelay} or {relayCall}.
*/
function penalizeIllegalTransaction(bytes memory unsignedTx, bytes memory signature) public; function penalizeIllegalTransaction(bytes memory unsignedTx, bytes memory signature) public;
/**
* @dev Emitted when a relay is penalized.
*/
event Penalized(address indexed relay, address sender, uint256 amount); 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); function getNonce(address from) external view returns (uint256);
} }
pragma solidity ^0.5.0; pragma solidity ^0.5.0;
/* /**
* @dev Interface for a contract that will be called via the GSN from RelayHub. * @dev Base interface for a contract that will be called via the GSN from {IRelayHub}. Do not use directly, inherit from
* {GSNRecipient} instead.
*/ */
contract IRelayRecipient { contract IRelayRecipient {
/** /**
* @dev Returns the address of the RelayHub instance this recipient interacts with. * @dev Returns the address of the {IRelayHub} instance this recipient interacts with.
*/ */
function getHubAddr() public view returns (address); function getHubAddr() public 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( function acceptRelayedCall(
address relay, address relay,
address from, address from,
...@@ -24,7 +42,32 @@ contract IRelayRecipient { ...@@ -24,7 +42,32 @@ contract IRelayRecipient {
view view
returns (uint256, bytes memory); 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 exection 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); function preRelayedCall(bytes calldata context) external returns (bytes32);
function postRelayedCall(bytes calldata context, bool success, uint actualCharge, bytes32 preRetVal) external; /**
* @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;
} }
= Gas Station Network = Gas Station Network (GSN)
NOTE: This feature is being released in the next version of OpenZeppelin Contracts, available right now through `npm install @openzeppelin/contracts@next`. 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:openzeppelin::gsn/what-is-the-gsn.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-bouncers.adoc[GSN Bouncers] easy are available in {GSNBouncerBase}, or you can simply use one of our pre-made bouncers:
* {GSNBouncerERC20Fee} charges the end user for gas costs in an application-specific xref:ROOT:tokens.adoc#ERC20[ERC20 token]
* {GSNBouncerSignature} accepts all relayed calls that have been signed by a trusted third party (e.g. a private key in a backend)
TIP: Check out our guide on the xref:ROOT:gsn.adoc[basics of the GSN] as well as the xref:ROOT:gsn-bouncers.adoc[more advanced topics]. 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.
NOTE: This feature is being released in the next version of OpenZeppelin Contracts, available right now through `npm install @openzeppelin/contracts@next`.
== Recipient == Recipient
...@@ -10,5 +21,11 @@ TIP: Check out our guide on the xref:ROOT:gsn.adoc[basics of the GSN] as well as ...@@ -10,5 +21,11 @@ TIP: Check out our guide on the xref:ROOT:gsn.adoc[basics of the GSN] as well as
== Bouncers == Bouncers
{{GSNBouncerBase}}
{{GSNBouncerERC20Fee}} {{GSNBouncerERC20Fee}}
{{GSNBouncerSignature}} {{GSNBouncerSignature}}
== Protocol
{{IRelayRecipient}}
{{IRelayHub}}
...@@ -2,7 +2,7 @@ pragma solidity ^0.5.0; ...@@ -2,7 +2,7 @@ pragma solidity ^0.5.0;
import "../IRelayRecipient.sol"; import "../IRelayRecipient.sol";
/* /**
* @dev Base contract used to implement GSNBouncers. * @dev Base contract used to implement GSNBouncers.
* *
* > This contract does not perform all required tasks to implement a GSN * > This contract does not perform all required tasks to implement a GSN
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-20[ERC20 Token Standard]. This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-20[ERC20 Token Standard].
TIP: For an overview of ERC20 tokens and a walkthrough on how to create a token contract read our xref:ROOT:tokens.adoc#erc20[ERC20 guide]. TIP: For an overview of ERC20 tokens and a walkthrough on how to create a token contract read our xref:ROOT:tokens.adoc#ERC20[ERC20 guide].
There a few core contracts that implement the behavior specified in the EIP: There a few core contracts that implement the behavior specified in the EIP:
......
= Writing GSN-capable contracts = Writing GSN-capable contracts
The https://gsn.ethereum.org[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. 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:openzeppelin::gsn/what-is-the-gsn.adoc[light overview of the system], to get a clearer picture of how gasless transactions are achieved. Otherwise, strap in! If you're new to the GSN, you probably want to first take a look at the xref:openzeppelin::gsn/what-is-the-gsn.adoc[light overview of the system], to get a clearer picture of how gasless transactions are achieved. Otherwise, strap in!
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment