Browse Source

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>
Nicolás Venturo 6 years ago
parent
commit
d864228624

+ 1 - 1
contracts/GSN/GSNContext.sol

@@ -2,7 +2,7 @@ pragma solidity ^0.5.0;
 
 import "./Context.sol";
 
-/*
+/**
  * @dev Enables GSN support on `Context` contracts by recognizing calls from
  * RelayHub and extracting the actual sender and call data from the received
  * calldata.

+ 10 - 11
contracts/GSN/GSNRecipient.sol

@@ -5,23 +5,22 @@ import "./GSNContext.sol";
 import "./bouncers/GSNBouncerBase.sol";
 import "./IRelayHub.sol";
 
-/*
- * @dev Base GSN recipient contract, adding the recipient interface and enabling
- * GSN support. Not all interface methods are implemented, derived contracts
- * must do so themselves.
+/**
+ * @dev Base GSN recipient contract: includes the {IRelayRecipient} interface and enables GSN support on all contracts
+ * in the inheritance tree.
+ *
+ * Not all interface methods are implemented (e.g. {acceptRelayedCall}, derived contracts must provide one themselves.
  */
 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) {
         return _relayHub;
     }
 
     /**
-     * @dev This function returns the version string of the RelayHub for which
-     * this recipient implementation was built. It's not currently used, but
-     * may be used by tooling.
+     * @dev Returns the version string of the `RelayHub` for which this recipient implementation was built.
      */
     // This function is view for future-proofing, it may require reading from
     // storage in the future.
@@ -31,9 +30,9 @@ contract GSNRecipient is IRelayRecipient, GSNContext, GSNBouncerBase {
     }
 
     /**
-     * @dev Triggers a withdraw of the recipient's deposits in RelayHub. Can
-     * be used by derived contracts to expose the functionality in an external
-     * interface.
+     * @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 {
         IRelayHub(_relayHub).withdraw(amount, payee);

+ 159 - 80
contracts/GSN/IRelayHub.sol

@@ -1,45 +1,75 @@
 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 {
     // Relay management
 
-    // Add 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.
+    /**
+     * @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;
 
-    // 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);
 
-    // 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).
-    // Emits a RelayAdded event.
-    // This function can be called multiple times, emitting new RelayAdded events. Note that the received transactionFee
-    // is not enforced by relayCall.
+    /**
+     * @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 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);
 
-    // 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.
+    /**
+     * @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) 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);
 
-    // 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.
+    /** 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) 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);
 
     // States a relay can be in
@@ -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
     }
 
-    // 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);
 
     // 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.
-    // Emits a Deposited event.
+    /**
+     * @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) 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);
 
-    // 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);
 
-    // 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.
-    // Emits a Withdrawn event.
+    /**
+     * 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) 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);
 
     // 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)
-    //  - 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.
+    /**
+     * @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,
@@ -104,31 +154,35 @@ contract IRelayHub {
         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
-    //  - 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 order.
-    //
-    // Arguments:
-    //  - from: the client originating the request
-    //  - recipient: 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 forwared to acceptRelayedCall. This value is *not* verified by the Hub, but
-    //    it still can be used for e.g. a signature.
-    //
-    // Emits a TransactionRelayed event.
+    /**
+     * @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 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(
         address from,
         address to,
@@ -141,16 +195,25 @@ contract IRelayHub {
         bytes memory approvalData
     ) 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.
-    // 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.
+    /**
+     * @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);
 
-    // Emitted when a transaction is relayed. Note that the actual encoded function might be reverted: this will be
-    // indicated in the status field.
-    // 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.
+    /**
+     * @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
@@ -162,27 +225,43 @@ contract IRelayHub {
         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);
 
-    // 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);
 
-    // 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
     // 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
-    // both transactions must be provided.
+    /**
+     * @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 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;
 
+    /**
+     * @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);
 }
 

+ 47 - 4
contracts/GSN/IRelayRecipient.sol

@@ -1,14 +1,32 @@
 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 {
     /**
-     * @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);
 
+    /**
+     * @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,
@@ -24,7 +42,32 @@ contract IRelayRecipient {
         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 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 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;
 }

+ 20 - 3
contracts/GSN/README.adoc

@@ -1,8 +1,19 @@
-= 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
 
@@ -10,5 +21,11 @@ TIP: Check out our guide on the xref:ROOT:gsn.adoc[basics of the GSN] as well as
 
 == Bouncers
 
+{{GSNBouncerBase}}
 {{GSNBouncerERC20Fee}}
 {{GSNBouncerSignature}}
+
+== Protocol
+
+{{IRelayRecipient}}
+{{IRelayHub}}

+ 1 - 1
contracts/GSN/bouncers/GSNBouncerBase.sol

@@ -2,7 +2,7 @@ pragma solidity ^0.5.0;
 
 import "../IRelayRecipient.sol";
 
-/*
+/**
  * @dev Base contract used to implement GSNBouncers.
  *
  * > This contract does not perform all required tasks to implement a GSN

+ 1 - 1
contracts/token/ERC20/README.adoc

@@ -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].
 
-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:
 

+ 1 - 1
docs/modules/ROOT/pages/gsn.adoc

@@ -1,6 +1,6 @@
 = 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!