From d0e3ff7486888ff8a8eef049327620fb8e429885 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 11 Sep 2019 14:36:39 +1000 Subject: [PATCH 01/24] Fix typo --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 7a3affca36a..7723925a6e5 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -21,7 +21,7 @@ A GSN recipient contract needs the following to work: * It needs to handle `msg.sender` and `msg.data` differently * It needs to decide how to approve and reject transactions. -The first can be done via the https://gsn.openzeppelin.com/recipients[GSN Tools] or programatically with our SDK. +The first can be done via the https://gsn.openzeppelin.com/recipients[GSN Tools] or programmatically with our SDK. The sender and data can be used safely when using `GSNRecipient` and _msgSender & _msgData. From fc5ea12be4cb1501f762b01f04cbc50d4fbe1a26 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 11 Sep 2019 14:42:54 +1000 Subject: [PATCH 02/24] Replace pseudo code contracts with sample code --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 7723925a6e5..0f0e08b4394 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -125,9 +125,11 @@ Create your contract using the following: [source,solidity] ---- - mycontract is GSNRecipient, GSNBouncerSignature { - constructor(trusted_address) // that's it - } +contract MyContract is GSNRecipient, GSNBouncerSignature { + constructor(address trustedSigner) public GSNBouncerSignature(trustedSigner) { + // solhint-disable-previous-line no-empty-blocks + } +} ---- == GSNBouncerERC20Fee @@ -221,13 +223,15 @@ Create your contract using the following: [source,solidity] ---- - mycontract is GSNRecipient, GSNBouncerERC20Fee { - constructor(name symbol decimals) +contract MyContract is GSNRecipient, GSNBouncerERC20Fee, MinterRole { + constructor() public GSNBouncerERC20Fee("FeeToken", "FEE", 18) { + // solhint-disable-previous-line no-empty-blocks + } - mint() { - _mint() + function mint(address account, uint256 amount) public onlyMinter { + _mint(account, amount); } - } +} ---- == Create your custom Bouncer [optional, for power users] From 93e19adebecb1a47f237f1a4459132784d9c5382 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 11 Sep 2019 16:34:16 +1000 Subject: [PATCH 03/24] Update GSN Bouncers text --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 89 +++++++++++------------ 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 0f0e08b4394..993c39f9690 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -1,56 +1,51 @@ -= Advanced GSN Guide (Bouncers) += GSN Bouncers -This guide shows you different strategies (Bouncers) to accept transactions via Gas Station Network. +This guide shows you different strategies to accept transactions via Gas Station Network using GSN Bouncers. First, we will explain the Bouncer concept, and then we will showcase how to use the two most common strategies. - Finally, we will cover how to create your own custom Bouncer. -If you're still learning about the basics of the Gas Station Network, you should head over to our xref:gsn.adoc[GSN Guide], which will help you get started from scratch. +If you're still learning about the basics of the Gas Station Network, you should head over to the xref:gsn.adoc[GSN Guide]. [[gsn-bouncers]] -== GSN Bouncers +== GSN Bouncers explained -A *GSN Bouncer* decides which transaction gets approved and which transaction gets rejected. Bouncers are a key concept within GSN. Dapps need Bouncers to prevent malicious users from spending the subsidies for the transactions. +A *GSN Bouncer* decides which transaction gets approved and which transaction gets rejected. Bouncers are a key concept within the GSN. Dapps need Bouncers to prevent malicious users from spending the dapps transaction fee subsidies. -As we have seen in the Basic Guide, in order to use GSN, your contracts need to extend from GSN Recipient. +As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, your contracts need to extend from `GSNRecipient`. A GSN recipient contract needs the following to work: -* It needs to have funds deposited on its RelayHub. -* It needs to handle `msg.sender` and `msg.data` differently -* It needs to decide how to approve and reject transactions. +1. It needs to have funds deposited on its RelayHub. +2. It needs to handle `msg.sender` and `msg.data` differently +3. It needs to decide how to approve and reject transactions. -The first can be done via the https://gsn.openzeppelin.com/recipients[GSN Tools] or programmatically with our SDK. +Depositing funds can be done via the https://gsn.openzeppelin.com/recipients[GSN Dapp tool] or programmatically with https://github.com/OpenZeppelin/openzeppelin-gsn-helpers#usage-from-code[OpenZeppelin GSN Helpers]. -The sender and data can be used safely when using `GSNRecipient` and _msgSender & _msgData. +The sender and data can be used safely when using `GSNRecipient` along with `_msgSender() and `_msgData()`. -The third is a bit more complex. The GSN Recipient, by default, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSNBouncers_. +Deciding how to approve and reject transactions is a bit more complex. The GSN recipient contract, by default, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. -We include two of them below, ready to use out of the box. +We include `GSNBouncerSignature` and `GSNBouncerERC20Fee`, ready to use out of the box, along with how to create your own custom Bouncer. == GSNBouncerSignature -This bouncer lets users call into your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. +The `GSNBouncerSignature` lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. -The signature used to create the transaction must be added to the contract as a trusted signer. If it is not the same, this bouncer will not accept the transaction. +The transaction must be signed by the account that was added to the contract as a trusted signer. If it is not the same, this bouncer will not accept the transaction. This means that you need to set up a system where your trusted account signs call requests, as long as they are valid users. -The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of oauth and validation, e.g., gone through a captcha or validated their email address. -You could restrict it further and let new users send a specific number of transactions (e.g., 5 requests via the GSN, at which point they need to create a wallet). -Alternatively, you could charge them off-chain (e.g., via credit card) for credit on your system and let them run GSN calls until said credit runs out. +The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of https://en.wikipedia.org/wiki/OAuth[OAuth] and validation, e.g., gone through a captcha or validated their email address. +You could restrict it further and let new users send a specific number of transactions (e.g. 5 requests via the GSN, at which point they need to create a wallet). +Alternatively, you could charge them off-chain (e.g. via credit card) for credit on your system and let them run GSN calls until their credit runs out. -The great thing about this setup is that *your contract doesn't need to change*. All you're doing is changing the backend logic conditions under which users call into your contract for free. +The great thing about this setup is that *your contract doesn't need to change* if you want to change the business rules. All you are doing is changing the backend logic conditions under which users call into your contract for free. On the other hand, you need to have a backend server, microservice, or lambda function to accomplish this. -=== How does it work? - -Here is the definition of acceptRelayedCall function. - -It decides whether or not to accept the call based on the signature. No further gsn-actions need to be taken. +=== How does GSNBouncerSignature work? -It only relies on the approvalData and does not use callData. +The definition of `acceptRelayedCall` function is below. The function decides whether or not to accept the call based on the signature. No further GSN actions need to be taken. The function only relies on the `approvalData`. [source,solidity] ---- @@ -119,35 +114,34 @@ On the other hand, when the signatures don't match, the call gets rejected with ---- -=== How to use it +=== How to use GSNBouncerSignature -Create your contract using the following: +Create your contract inheriting from `GSNRecipient` and `GSNBouncerSignature`, as well as setting the trusted signer in the constructor of `GSNBouncerSignature` as per the following sample code: [source,solidity] ---- contract MyContract is GSNRecipient, GSNBouncerSignature { constructor(address trustedSigner) public GSNBouncerSignature(trustedSigner) { - // solhint-disable-previous-line no-empty-blocks } } ---- == GSNBouncerERC20Fee -This bouncer is a bit more complex (but don't worry, we've already written it for you!). Unlike `GSNBouncerSignature`, this Bouncer doesn't require any off-chain services. -Instead of manually approving each transaction, you will give tokens to your users. These tokens are then used to pay for GSN calls to your recipient contract. -Any user that has enough tokens is automatically approved and the recipient contract will cover his transaction costs! +The `GSNBouncerERC20Fee` is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, the `GSNBouncerERC20Fee` doesn't require any off-chain services. +Instead of off-chain approving the user for each transaction, you will give ERC20 tokens to your users. These tokens are then used to pay for GSN calls to your recipient contract. +Any user that has enough tokens is automatically approved and the recipient contract will cover their transaction costs! This bouncer charges users for the ether cost your recipient will incur. Each recipient contract has their own unique token, with a baked-in exchange rate of 1:1 to ether, since they act as an ether replacement when using the GSN. -The recipient has an internal mint function. Firstly, you need to setup a way to call it (e.g., add a public function with onlyOwner or some other form of access control). -Then, issue tokens to users based on your business logic. For example, you could mint limited tokens to new users, mint tokens when they buy them off-chain, give tokens based on the user subscription, etc. +The `GSNBouncerSignature` has an internal mint function. Firstly, you need to setup a way to call it (e.g. add a public function with `onlyMinter` or some other form of xref:access-control.adoc[access control]). +Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when they buy them off-chain, give tokens based on the user subscription, etc. NOTE: *Users do not need call approve* on their tokens for your recipient to use them. They are a modified ERC20 variant that lets the recipient contract retrieve them. -=== How does it work? +=== How does GSNBouncerERC20Fee work? -Let's look at how this Bouncer decides to approve or reject transactions. +Let's look at how the `GSNBouncerERC20Fee` decides to approve or reject transactions. [source,solidity] ---- @@ -176,10 +170,10 @@ function acceptRelayedCall( } ---- -The bouncer rejects the tx if the real sender doesn't have enough tokens or they are not allowed to spend that amount. -If the sender can spend the tokens, the bouncers approve the transaction and overrides _confirmRelayedCall to make that data available to pre and post. +The bouncer rejects the transaction if the real sender doesn't have enough tokens or the recipient is not allowed to spend that amount of tokens. +If the sender can spend the required amount of tokens, the bouncer approves the transaction and returns `_approveRelayedCall` to make that data available to `_preRelayedCall` and `_postRelayedCall`. -Now, let's see how we perform the token transfer inside the _preRelayedCall method. +Now, let's see how the token transfer is performed inside the `_preRelayedCall` function. [source,solidity] ---- @@ -191,8 +185,8 @@ function _preRelayedCall(bytes memory context) internal returns (bytes32) { } ---- -We transfer the max amount of tokens assuming that the call will use all the gas available. -Then, in the _postRelayedCall method, we calculate the actual amount - including the implementation and ERC transfers - and refund the difference. +The maximum amount of tokens required is transferred assuming that the call will use all the gas available. +Then, in the `_postRelayedCall` function, the actual amount is calculated - including the implementation and ERC20 token transfers - and refund the difference. [source,solidity] ---- @@ -213,19 +207,18 @@ function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, byte This is required to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions). -Please note how the gas cost estimation is not 100% accurate, we may tweak it further down the road. +NOTE: The gas cost estimation is not 100% accurate, we may tweak it further down the road. -NOTE: `_preRelayedCall` and `_postRelayedCall` are used instead of preRelayedCall and postRelayedCall. This prevents them from being called by non-relayhub. Always use _pre and _post methods. +NOTE: `_preRelayedCall` and `_postRelayedCall` are used instead of `preRelayedCall` and `postRelayedCall`. This prevents them from being called by non-relayhub. Always use `_preRelayedCall` and `_postRelayedCall` functions. -=== How to use it +=== How to use GSNBouncerERC20Fee -Create your contract using the following: +Create your contract inheriting from `GSNRecipient` and `GSNBouncerERC20Fee` along with any xref:access-control.adoc[access control], set the token details in the constructor of `GSNBouncerERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses the https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole[MinterRole]): [source,solidity] ---- contract MyContract is GSNRecipient, GSNBouncerERC20Fee, MinterRole { constructor() public GSNBouncerERC20Fee("FeeToken", "FEE", 18) { - // solhint-disable-previous-line no-empty-blocks } function mint(address account, uint256 amount) public onlyMinter { @@ -236,8 +229,8 @@ contract MyContract is GSNRecipient, GSNBouncerERC20Fee, MinterRole { == Create your custom Bouncer [optional, for power users] -You can use 'GSNBouncerBase' as an example to guide your Bouncer implementation. +You can use `GSNBouncerBase` as an example to guide your Bouncer implementation. -The only thing you must do is extend from `GSNRecipient` and implement the accept method. +The only thing you must do is extend from `GSNRecipient` and implement the `accept` function. Depending on your logic, you may need to implement `_postRelayedCall` and `_preRelayedCall`. From b613e6875e4533aaaacf3b24567ea1555cb24b50 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Thu, 12 Sep 2019 16:28:53 +1000 Subject: [PATCH 04/24] More text changes --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 88 ++++++++++++++--------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 993c39f9690..cf659bfbb86 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -1,28 +1,28 @@ = GSN Bouncers -This guide shows you different strategies to accept transactions via Gas Station Network using GSN Bouncers. +This guide shows you different strategies to accept transactions via the Gas Station Network (GSN) using GSN Bouncers. First, we will explain the Bouncer concept, and then we will showcase how to use the two most common strategies. Finally, we will cover how to create your own custom Bouncer. -If you're still learning about the basics of the Gas Station Network, you should head over to the xref:gsn.adoc[GSN Guide]. +If you're still learning about the basics of the Gas Station Network, you should first head over to the xref:gsn.adoc[GSN Guide]. [[gsn-bouncers]] == GSN Bouncers explained -A *GSN Bouncer* decides which transaction gets approved and which transaction gets rejected. Bouncers are a key concept within the GSN. Dapps need Bouncers to prevent malicious users from spending the dapps transaction fee subsidies. +A *GSN Bouncer* decides which transaction gets approved and which transaction gets rejected. Bouncers are a key concept within the GSN. Dapps need Bouncers to prevent malicious users from spending the dapps funds for transaction fees. -As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, your contracts need to extend from `GSNRecipient`. +As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, your contracts need to extend from https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNRecipient[`GSNRecipient`]. A GSN recipient contract needs the following to work: 1. It needs to have funds deposited on its RelayHub. -2. It needs to handle `msg.sender` and `msg.data` differently +2. It needs to use the actual user's `msg.sender` and `msg.data` 3. It needs to decide how to approve and reject transactions. -Depositing funds can be done via the https://gsn.openzeppelin.com/recipients[GSN Dapp tool] or programmatically with https://github.com/OpenZeppelin/openzeppelin-gsn-helpers#usage-from-code[OpenZeppelin GSN Helpers]. +Depositing funds for the GSN recipient contract can be done via the https://gsn.openzeppelin.com/recipients[GSN Dapp tool] or programmatically with https://github.com/OpenZeppelin/openzeppelin-gsn-helpers#usage-from-code[OpenZeppelin GSN Helpers]. -The sender and data can be used safely when using `GSNRecipient` along with `_msgSender() and `_msgData()`. +The actual user's `msg.sender` and `msg.data` can be obtained safely via `_msgSender()` and `_msgData()` of https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNRecipient[`GSNRecipient`]. Deciding how to approve and reject transactions is a bit more complex. The GSN recipient contract, by default, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. @@ -30,22 +30,22 @@ We include `GSNBouncerSignature` and `GSNBouncerERC20Fee`, ready to use out of t == GSNBouncerSignature -The `GSNBouncerSignature` lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. +The https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerSignature[`GSNBouncerSignature`] lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. -The transaction must be signed by the account that was added to the contract as a trusted signer. If it is not the same, this bouncer will not accept the transaction. +The transaction request must be signed by the same account that was added to the contract as a trusted signer. If it is not the same, the `GSNBouncerSignature` will not accept the transaction. -This means that you need to set up a system where your trusted account signs call requests, as long as they are valid users. +This means that you need to set up a system where your trusted account signs transaction requests, as long as they are valid users (according to your business logic). -The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of https://en.wikipedia.org/wiki/OAuth[OAuth] and validation, e.g., gone through a captcha or validated their email address. -You could restrict it further and let new users send a specific number of transactions (e.g. 5 requests via the GSN, at which point they need to create a wallet). -Alternatively, you could charge them off-chain (e.g. via credit card) for credit on your system and let them run GSN calls until their credit runs out. +The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of https://en.wikipedia.org/wiki/OAuth[OAuth] and validation, e.g. gone through a captcha or validated their email address. +You could restrict it further and let new users send a specific number of transactions (e.g. limit to 5 requests via the GSN, at which point the user needs to create a wallet). +Alternatively, you could charge the user off-chain (e.g. via credit card) for credit on your system and let them create GSN transactions until their credit runs out. -The great thing about this setup is that *your contract doesn't need to change* if you want to change the business rules. All you are doing is changing the backend logic conditions under which users call into your contract for free. +The great thing about this setup is that *your contract doesn't need to change* if you want to change the business rules. All you are doing is changing the backend logic conditions under which users use your contract for free. On the other hand, you need to have a backend server, microservice, or lambda function to accomplish this. === How does GSNBouncerSignature work? -The definition of `acceptRelayedCall` function is below. The function decides whether or not to accept the call based on the signature. No further GSN actions need to be taken. The function only relies on the `approvalData`. +The `GSNBouncerSignature` decides whether or not to accept the transaction based on the signature. No further GSN actions need to be taken. The `acceptRelayedCall` function only relies on the `approvalData`. The definition of `acceptRelayedCall` function from `GSNBouncerSignature` is below: [source,solidity] ---- @@ -85,7 +85,7 @@ function acceptRelayedCall( ==== Approve/Reject -When the signatures match, the function returns the following: +When the signature matches the trusted signer, the function returns the following: [source,solidity] ---- @@ -99,7 +99,7 @@ When the signatures match, the function returns the following: // } ---- -On the other hand, when the signatures don't match, the call gets rejected with the following: +On the other hand, when the signature doesn't match the trusted signer, the call gets rejected with the following: [source,solidity] ---- @@ -116,7 +116,9 @@ On the other hand, when the signatures don't match, the call gets rejected with === How to use GSNBouncerSignature -Create your contract inheriting from `GSNRecipient` and `GSNBouncerSignature`, as well as setting the trusted signer in the constructor of `GSNBouncerSignature` as per the following sample code: +You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a GSN transaction, which signs (or doesn't sign) each transaction request based on your business logic. + +Your GSN recipient contract needs to inherit from `GSNRecipient` and `GSNBouncerSignature`, as well as setting the trusted signer in the constructor of `GSNBouncerSignature` as per the following sample code below: [source,solidity] ---- @@ -128,20 +130,20 @@ contract MyContract is GSNRecipient, GSNBouncerSignature { == GSNBouncerERC20Fee -The `GSNBouncerERC20Fee` is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, the `GSNBouncerERC20Fee` doesn't require any off-chain services. -Instead of off-chain approving the user for each transaction, you will give ERC20 tokens to your users. These tokens are then used to pay for GSN calls to your recipient contract. +The https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, the `GSNBouncerERC20Fee` doesn't require any off-chain services. +Instead of off-chain approving each transaction, you will give contract specific ERC20 tokens to your users. These tokens are then used as payment for GSN transactions to your recipient contract. Any user that has enough tokens is automatically approved and the recipient contract will cover their transaction costs! -This bouncer charges users for the ether cost your recipient will incur. Each recipient contract has their own unique token, with a baked-in exchange rate of 1:1 to ether, since they act as an ether replacement when using the GSN. +This bouncer charges users for the ether cost your recipient contract will incur. Each recipient contract has their own unique token, with a baked-in exchange rate of 1:1 to ether, since they act as an ether replacement when using the GSN. -The `GSNBouncerSignature` has an internal mint function. Firstly, you need to setup a way to call it (e.g. add a public function with `onlyMinter` or some other form of xref:access-control.adoc[access control]). -Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when they buy them off-chain, give tokens based on the user subscription, etc. +The `GSNBouncerSignature` has an internal https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole-onlyMinter--[`onlyMinter`]). +Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when users buy them off-chain, give tokens based on a users subscription, etc. -NOTE: *Users do not need call approve* on their tokens for your recipient to use them. They are a modified ERC20 variant that lets the recipient contract retrieve them. +NOTE: *Users do not need to call approve* on their tokens for your recipient contract to use them. They are a modified ERC20 variant that lets the recipient contract retrieve them. === How does GSNBouncerERC20Fee work? -Let's look at how the `GSNBouncerERC20Fee` decides to approve or reject transactions. +The `GSNBouncerERC20Fee` decides to approve or reject transactions based on the balance and allowance of the users tokens. The definition of `acceptRelayedCall` function from `GSNBouncerERC20Fee` is below: [source,solidity] ---- @@ -170,8 +172,12 @@ function acceptRelayedCall( } ---- -The bouncer rejects the transaction if the real sender doesn't have enough tokens or the recipient is not allowed to spend that amount of tokens. -If the sender can spend the required amount of tokens, the bouncer approves the transaction and returns `_approveRelayedCall` to make that data available to `_preRelayedCall` and `_postRelayedCall`. +==== Approve/Reject + +The `GSNBouncerERC20Fee` rejects the transaction if the user doesn't have enough tokens or the recipient contract doesn't have a high enough allowance to cover the required amount of tokens. +If the recipient contract can spend the required amount of tokens, `GSNBouncerERC20Fee` approves the transaction and returns `_approveRelayedCall` to make that data available to `_preRelayedCall` and `_postRelayedCall`. + +==== Token transfer Now, let's see how the token transfer is performed inside the `_preRelayedCall` function. @@ -186,7 +192,7 @@ function _preRelayedCall(bytes memory context) internal returns (bytes32) { ---- The maximum amount of tokens required is transferred assuming that the call will use all the gas available. -Then, in the `_postRelayedCall` function, the actual amount is calculated - including the implementation and ERC20 token transfers - and refund the difference. +Then, in the `_postRelayedCall` function, the actual amount is calculated - including the recipient contract implementation and ERC20 token transfers - and refund the difference. [source,solidity] ---- @@ -205,15 +211,18 @@ function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, byte } ---- -This is required to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions). +The maximum amount of tokens required is transferred to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions). NOTE: The gas cost estimation is not 100% accurate, we may tweak it further down the road. -NOTE: `_preRelayedCall` and `_postRelayedCall` are used instead of `preRelayedCall` and `postRelayedCall`. This prevents them from being called by non-relayhub. Always use `_preRelayedCall` and `_postRelayedCall` functions. +NOTE: Internal `_preRelayedCall` and `_postRelayedCall` functions are used instead of public `preRelayedCall` and `postRelayedCall` functions. This prevents them from being called by non-RelayHub contracts. +Always use `_preRelayedCall` and `_postRelayedCall` functions. === How to use GSNBouncerERC20Fee -Create your contract inheriting from `GSNRecipient` and `GSNBouncerERC20Fee` along with any xref:access-control.adoc[access control], set the token details in the constructor of `GSNBouncerERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses the https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole[MinterRole]): +Your GSN recipient contract needs to inherit from `GSNRecipient` and `GSNBouncerERC20Fee` along with appropriate xref:access-control.adoc[access control] (for token minting), set the token details in the constructor of `GSNBouncerERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses the https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole[MinterRole]): + +NOTE: The token must have decimals of 18 to match that of ether, due to the baked-in exchange rate of 1:1. [source,solidity] ---- @@ -227,10 +236,19 @@ contract MyContract is GSNRecipient, GSNBouncerERC20Fee, MinterRole { } ---- -== Create your custom Bouncer [optional, for power users] +== Custom Bouncer + +You can create your own Custom Bouncer! For example, your Custom Bouncer could use a specified token to pay for transactions with a custom exchange rate to ether. Alternatively you could issue users who subscribe to your dapp ERC721 tokens and accounts holding the subscription token could use your contract for free as part of the subscription. There are lots of potential options for your custom Bouncer. -You can use `GSNBouncerBase` as an example to guide your Bouncer implementation. +Your Custom Bouncer can inherit from `GSNBouncerBase` and must implement the `acceptRelayedCall` function. -The only thing you must do is extend from `GSNRecipient` and implement the `accept` function. +Depending on the logic for your Custom Bouncer, you may need to implement `_postRelayedCall` and `_preRelayedCall`. -Depending on your logic, you may need to implement `_postRelayedCall` and `_preRelayedCall`. +Your GSN recipient contract needs to inherit from `GSNRecipient` and your Custom Bouncer as per the following sample code: + +[source,solidity] +---- +contract MyContract is GSNRecipient, MyCustomBouncer { + constructor() public MyCustomBouncer() { + } +} From c6d2566a2409522e6515ec9f25750d69bab79254 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Thu, 12 Sep 2019 16:46:00 +1000 Subject: [PATCH 05/24] Update with latest code and remove reference to allowance --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 99 +++++++++++------------ 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index cf659bfbb86..a2a4143f09a 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -50,37 +50,37 @@ The `GSNBouncerSignature` decides whether or not to accept the transaction based [source,solidity] ---- function acceptRelayedCall( - address relay, - address from, - bytes calldata encodedFunction, - uint256 transactionFee, - uint256 gasPrice, - uint256 gasLimit, - uint256 nonce, - bytes calldata approvalData, - uint256 - ) - external - view - returns (uint256, bytes memory) - { - bytes memory blob = abi.encodePacked( - relay, - from, - encodedFunction, - transactionFee, - gasPrice, - gasLimit, - nonce, // Prevents replays on RelayHub - getHubAddr(), // Prevents replays in multiple RelayHubs - address(this) // Prevents replays in multiple recipients - ); - if (keccak256(blob).toEthSignedMessageHash().recover(approvalData) == _trustedSigner) { - return _approveRelayedCall(); - } else { - return _rejectRelayedCall(uint256(GSNRecipientSignedDataErrorCodes.INVALID_SIGNER)); - } - } + address relay, + address from, + bytes calldata encodedFunction, + uint256 transactionFee, + uint256 gasPrice, + uint256 gasLimit, + uint256 nonce, + bytes calldata approvalData, + uint256 +) + external + view + returns (uint256, bytes memory) +{ + bytes memory blob = abi.encodePacked( + relay, + from, + encodedFunction, + transactionFee, + gasPrice, + gasLimit, + nonce, // Prevents replays on RelayHub + getHubAddr(), // Prevents replays in multiple RelayHubs + address(this) // Prevents replays in multiple recipients + ); + if (keccak256(blob).toEthSignedMessageHash().recover(approvalData) == _trustedSigner) { + return _approveRelayedCall(); + } else { + return _rejectRelayedCall(uint256(GSNBouncerSignatureErrorCodes.INVALID_SIGNER)); + } +} ---- ==== Approve/Reject @@ -89,31 +89,28 @@ When the signature matches the trusted signer, the function returns the followin [source,solidity] ---- +return _approveRelayedCall(); - return _approveRelayedCall(); - - // Defined on base class GSNBouncerBase - // uint256 constant private RELAYED_CALL_ACCEPTED = 0; - // function _approveRelayedCall(bytes memory context) internal pure returns (uint256, bytes memory) { - // return (RELAYED_CALL_ACCEPTED, context); - // } +// Defined on base class GSNBouncerBase +// uint256 constant private RELAYED_CALL_ACCEPTED = 0; +// function _approveRelayedCall(bytes memory context) internal pure returns (uint256, bytes memory) { +// return (RELAYED_CALL_ACCEPTED, context); +// } ---- On the other hand, when the signature doesn't match the trusted signer, the call gets rejected with the following: [source,solidity] ---- +return _rejectRelayedCall(uint256(GSNBouncerSignatureErrorCodes.INVALID_SIGNER)); - return _rejectRelayedCall(uint256(GSNBouncerSignatureErrorCodes.INVALID_SIGNER)); - - // Defined on base class GSNBouncerBase - // uint256 constant private RELAYED_CALL_REJECTED = 11; - // function _rejectRelayedCall(uint256 errorCode) internal pure returns (uint256, bytes memory) { - // return (RELAYED_CALL_REJECTED + errorCode, ""); - // } +// Defined on base class GSNBouncerBase +// uint256 constant private RELAYED_CALL_REJECTED = 11; +// function _rejectRelayedCall(uint256 errorCode) internal pure returns (uint256, bytes memory) { +// return (RELAYED_CALL_REJECTED + errorCode, ""); +// } ---- - === How to use GSNBouncerSignature You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a GSN transaction, which signs (or doesn't sign) each transaction request based on your business logic. @@ -136,14 +133,14 @@ Any user that has enough tokens is automatically approved and the recipient cont This bouncer charges users for the ether cost your recipient contract will incur. Each recipient contract has their own unique token, with a baked-in exchange rate of 1:1 to ether, since they act as an ether replacement when using the GSN. -The `GSNBouncerSignature` has an internal https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole-onlyMinter--[`onlyMinter`]). +The `GSNBouncerERC20Fee` has an internal https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole-onlyMinter--[`onlyMinter`]). Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when users buy them off-chain, give tokens based on a users subscription, etc. NOTE: *Users do not need to call approve* on their tokens for your recipient contract to use them. They are a modified ERC20 variant that lets the recipient contract retrieve them. === How does GSNBouncerERC20Fee work? -The `GSNBouncerERC20Fee` decides to approve or reject transactions based on the balance and allowance of the users tokens. The definition of `acceptRelayedCall` function from `GSNBouncerERC20Fee` is below: +The `GSNBouncerERC20Fee` decides to approve or reject transactions based on the balance of the users tokens. The definition of `acceptRelayedCall` function from `GSNBouncerERC20Fee` is below: [source,solidity] ---- @@ -164,8 +161,6 @@ function acceptRelayedCall( { if (_token.balanceOf(from) < maxPossibleCharge) { return _rejectRelayedCall(uint256(GSNBouncerERC20FeeErrorCodes.INSUFFICIENT_BALANCE)); - } else if (_token.allowance(from, address(this)) < maxPossibleCharge) { - return _rejectRelayedCall(uint256(GSNBouncerERC20FeeErrorCodes.INSUFFICIENT_ALLOWANCE)); } return _approveRelayedCall(abi.encode(from, maxPossibleCharge, transactionFee, gasPrice)); @@ -174,8 +169,8 @@ function acceptRelayedCall( ==== Approve/Reject -The `GSNBouncerERC20Fee` rejects the transaction if the user doesn't have enough tokens or the recipient contract doesn't have a high enough allowance to cover the required amount of tokens. -If the recipient contract can spend the required amount of tokens, `GSNBouncerERC20Fee` approves the transaction and returns `_approveRelayedCall` to make that data available to `_preRelayedCall` and `_postRelayedCall`. +The `GSNBouncerERC20Fee` rejects the transaction if the user doesn't have enough tokens. +If the user has the required amount of tokens, `GSNBouncerERC20Fee` approves the transaction and returns `_approveRelayedCall` to make that data available to `_preRelayedCall` and `_postRelayedCall`. ==== Token transfer From 0dac830ac2d2640ebac47e943aa1693dcc1d9bb2 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Thu, 12 Sep 2019 17:07:24 +1000 Subject: [PATCH 06/24] Capitalize Custom Bouncer --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index a2a4143f09a..59636286602 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -3,7 +3,7 @@ This guide shows you different strategies to accept transactions via the Gas Station Network (GSN) using GSN Bouncers. First, we will explain the Bouncer concept, and then we will showcase how to use the two most common strategies. -Finally, we will cover how to create your own custom Bouncer. +Finally, we will cover how to create your own Custom Bouncer. If you're still learning about the basics of the Gas Station Network, you should first head over to the xref:gsn.adoc[GSN Guide]. @@ -26,7 +26,7 @@ The actual user's `msg.sender` and `msg.data` can be obtained safely via `_msgSe Deciding how to approve and reject transactions is a bit more complex. The GSN recipient contract, by default, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. -We include `GSNBouncerSignature` and `GSNBouncerERC20Fee`, ready to use out of the box, along with how to create your own custom Bouncer. +We include `GSNBouncerSignature` and `GSNBouncerERC20Fee`, ready to use out of the box, along with how to create your own Custom Bouncer. == GSNBouncerSignature @@ -233,7 +233,7 @@ contract MyContract is GSNRecipient, GSNBouncerERC20Fee, MinterRole { == Custom Bouncer -You can create your own Custom Bouncer! For example, your Custom Bouncer could use a specified token to pay for transactions with a custom exchange rate to ether. Alternatively you could issue users who subscribe to your dapp ERC721 tokens and accounts holding the subscription token could use your contract for free as part of the subscription. There are lots of potential options for your custom Bouncer. +You can create your own Custom Bouncer! For example, your Custom Bouncer could use a specified token to pay for transactions with a custom exchange rate to ether. Alternatively you could issue users who subscribe to your dapp ERC721 tokens and accounts holding the subscription token could use your contract for free as part of the subscription. There are lots of potential options for your Custom Bouncer. Your Custom Bouncer can inherit from `GSNBouncerBase` and must implement the `acceptRelayedCall` function. From ceafc2242258eeba77fe7c36c47d6aed27dfdf73 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Tue, 17 Sep 2019 13:23:39 +1000 Subject: [PATCH 07/24] Update docs/modules/ROOT/pages/gsn-bouncers.adoc Co-Authored-By: Francisco Giordano --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 59636286602..6096b37292b 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -10,7 +10,7 @@ If you're still learning about the basics of the Gas Station Network, you should [[gsn-bouncers]] == GSN Bouncers explained -A *GSN Bouncer* decides which transaction gets approved and which transaction gets rejected. Bouncers are a key concept within the GSN. Dapps need Bouncers to prevent malicious users from spending the dapps funds for transaction fees. +A *GSN Bouncer* decides which transaction gets approved and which transaction gets rejected. Bouncers are a key concept within the GSN. Dapps need Bouncers to prevent malicious users from spending the dapp's funds for transaction fees. As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, your contracts need to extend from https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNRecipient[`GSNRecipient`]. From 91acc7992e16cf192d2f006712ff8480682465b1 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Tue, 17 Sep 2019 13:24:28 +1000 Subject: [PATCH 08/24] Update gsn-bouncers.adoc with Antora cross reference Co-Authored-By: Francisco Giordano --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 6096b37292b..8e7f74e3be7 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -12,7 +12,7 @@ If you're still learning about the basics of the Gas Station Network, you should A *GSN Bouncer* decides which transaction gets approved and which transaction gets rejected. Bouncers are a key concept within the GSN. Dapps need Bouncers to prevent malicious users from spending the dapp's funds for transaction fees. -As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, your contracts need to extend from https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNRecipient[`GSNRecipient`]. +As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, your contracts need to extend from xref:api:gsn.adoc#GSNRecipient[`GSNRecipient`]. A GSN recipient contract needs the following to work: From eeda27aaea0427988281306ebdf2237cc458c095 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 15:21:41 +1000 Subject: [PATCH 09/24] Revert to handling msg.sender msg.data differently --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 8e7f74e3be7..2efda2e3312 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -17,7 +17,7 @@ As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, you A GSN recipient contract needs the following to work: 1. It needs to have funds deposited on its RelayHub. -2. It needs to use the actual user's `msg.sender` and `msg.data` +2. It needs to handle `msg.sender` and `msg.data` differently 3. It needs to decide how to approve and reject transactions. Depositing funds for the GSN recipient contract can be done via the https://gsn.openzeppelin.com/recipients[GSN Dapp tool] or programmatically with https://github.com/OpenZeppelin/openzeppelin-gsn-helpers#usage-from-code[OpenZeppelin GSN Helpers]. From 25c685fe48d6b4423f63760839ac4a20efd27aa1 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 15:53:23 +1000 Subject: [PATCH 10/24] Change by default to simplest implementation --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 2efda2e3312..89b4a2450f1 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -24,7 +24,7 @@ Depositing funds for the GSN recipient contract can be done via the https://gsn. The actual user's `msg.sender` and `msg.data` can be obtained safely via `_msgSender()` and `_msgData()` of https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNRecipient[`GSNRecipient`]. -Deciding how to approve and reject transactions is a bit more complex. The GSN recipient contract, by default, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. +Deciding how to approve and reject transactions is a bit more complex. The GSN recipient contract, with the simplest implementation, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. We include `GSNBouncerSignature` and `GSNBouncerERC20Fee`, ready to use out of the box, along with how to create your own Custom Bouncer. From f6e51bdca8469d493fa9b74ebb542947633a51dc Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 16:10:22 +1000 Subject: [PATCH 11/24] Change signing to include signature for GSNBouncerSignature --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 89b4a2450f1..315f21490e6 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -32,9 +32,9 @@ We include `GSNBouncerSignature` and `GSNBouncerERC20Fee`, ready to use out of t The https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerSignature[`GSNBouncerSignature`] lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. -The transaction request must be signed by the same account that was added to the contract as a trusted signer. If it is not the same, the `GSNBouncerSignature` will not accept the transaction. +The transaction request must include a signature by the same account that was added to the contract as a trusted signer. If it is not the same, the `GSNBouncerSignature` will not accept the transaction. -This means that you need to set up a system where your trusted account signs transaction requests, as long as they are valid users (according to your business logic). +This means that you need to set up a system where your trusted account includes their signature with transaction requests, as long as they are valid users (according to your business logic). The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of https://en.wikipedia.org/wiki/OAuth[OAuth] and validation, e.g. gone through a captcha or validated their email address. You could restrict it further and let new users send a specific number of transactions (e.g. limit to 5 requests via the GSN, at which point the user needs to create a wallet). @@ -45,7 +45,7 @@ On the other hand, you need to have a backend server, microservice, or lambda fu === How does GSNBouncerSignature work? -The `GSNBouncerSignature` decides whether or not to accept the transaction based on the signature. No further GSN actions need to be taken. The `acceptRelayedCall` function only relies on the `approvalData`. The definition of `acceptRelayedCall` function from `GSNBouncerSignature` is below: +The `GSNBouncerSignature` decides whether or not to accept the transaction based on the included signature. No further GSN actions need to be taken. The `acceptRelayedCall` function only relies on the `approvalData`. The definition of `acceptRelayedCall` function from `GSNBouncerSignature` is below: [source,solidity] ---- @@ -85,7 +85,7 @@ function acceptRelayedCall( ==== Approve/Reject -When the signature matches the trusted signer, the function returns the following: +When the included signature matches the trusted signer, the function returns the following: [source,solidity] ---- @@ -98,7 +98,7 @@ return _approveRelayedCall(); // } ---- -On the other hand, when the signature doesn't match the trusted signer, the call gets rejected with the following: +On the other hand, when the included signature doesn't match the trusted signer, the call gets rejected with the following: [source,solidity] ---- @@ -113,7 +113,7 @@ return _rejectRelayedCall(uint256(GSNBouncerSignatureErrorCodes.INVALID_SIGNER)) === How to use GSNBouncerSignature -You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a GSN transaction, which signs (or doesn't sign) each transaction request based on your business logic. +You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a GSN transaction, which includes (or doesn't include) their signature with each transaction request based on your business logic. Your GSN recipient contract needs to inherit from `GSNRecipient` and `GSNBouncerSignature`, as well as setting the trusted signer in the constructor of `GSNBouncerSignature` as per the following sample code below: From ea94adaebb79888848779d47a650198d5597dfed Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 16:29:01 +1000 Subject: [PATCH 12/24] Reword summary of what is in the guide --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 315f21490e6..2096e82dd63 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -26,7 +26,7 @@ The actual user's `msg.sender` and `msg.data` can be obtained safely via `_msgSe Deciding how to approve and reject transactions is a bit more complex. The GSN recipient contract, with the simplest implementation, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. -We include `GSNBouncerSignature` and `GSNBouncerERC20Fee`, ready to use out of the box, along with how to create your own Custom Bouncer. +In this guide we describe how to use the included bouncers xref:api:gsn.adoc#GSNBouncerSignature[`GSNBouncerSignature`] and xref:api:gsn.adoc#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`], along with how to create your own Custom Bouncer. == GSNBouncerSignature From 6dc3aed5e7d6c2242e19dd9cc9f5b9eb044e3a31 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 16:38:03 +1000 Subject: [PATCH 13/24] Remove "The" from before `GSNBouncer...` --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 2096e82dd63..b81b5d9c728 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -30,9 +30,9 @@ In this guide we describe how to use the included bouncers xref:api:gsn.adoc#GSN == GSNBouncerSignature -The https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerSignature[`GSNBouncerSignature`] lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. +https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerSignature[`GSNBouncerSignature`] lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. -The transaction request must include a signature by the same account that was added to the contract as a trusted signer. If it is not the same, the `GSNBouncerSignature` will not accept the transaction. +The transaction request must include a signature by the same account that was added to the contract as a trusted signer. If it is not the same, `GSNBouncerSignature` will not accept the transaction. This means that you need to set up a system where your trusted account includes their signature with transaction requests, as long as they are valid users (according to your business logic). @@ -45,7 +45,7 @@ On the other hand, you need to have a backend server, microservice, or lambda fu === How does GSNBouncerSignature work? -The `GSNBouncerSignature` decides whether or not to accept the transaction based on the included signature. No further GSN actions need to be taken. The `acceptRelayedCall` function only relies on the `approvalData`. The definition of `acceptRelayedCall` function from `GSNBouncerSignature` is below: +`GSNBouncerSignature` decides whether or not to accept the transaction based on the included signature. No further GSN actions need to be taken. The `acceptRelayedCall` function only relies on the `approvalData`. The definition of `acceptRelayedCall` function from `GSNBouncerSignature` is below: [source,solidity] ---- @@ -127,20 +127,20 @@ contract MyContract is GSNRecipient, GSNBouncerSignature { == GSNBouncerERC20Fee -The https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, the `GSNBouncerERC20Fee` doesn't require any off-chain services. +https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, `GSNBouncerERC20Fee` doesn't require any off-chain services. Instead of off-chain approving each transaction, you will give contract specific ERC20 tokens to your users. These tokens are then used as payment for GSN transactions to your recipient contract. Any user that has enough tokens is automatically approved and the recipient contract will cover their transaction costs! This bouncer charges users for the ether cost your recipient contract will incur. Each recipient contract has their own unique token, with a baked-in exchange rate of 1:1 to ether, since they act as an ether replacement when using the GSN. -The `GSNBouncerERC20Fee` has an internal https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole-onlyMinter--[`onlyMinter`]). +`GSNBouncerERC20Fee` has an internal https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole-onlyMinter--[`onlyMinter`]). Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when users buy them off-chain, give tokens based on a users subscription, etc. NOTE: *Users do not need to call approve* on their tokens for your recipient contract to use them. They are a modified ERC20 variant that lets the recipient contract retrieve them. === How does GSNBouncerERC20Fee work? -The `GSNBouncerERC20Fee` decides to approve or reject transactions based on the balance of the users tokens. The definition of `acceptRelayedCall` function from `GSNBouncerERC20Fee` is below: +`GSNBouncerERC20Fee` decides to approve or reject transactions based on the balance of the users tokens. The definition of `acceptRelayedCall` function from `GSNBouncerERC20Fee` is below: [source,solidity] ---- @@ -169,7 +169,7 @@ function acceptRelayedCall( ==== Approve/Reject -The `GSNBouncerERC20Fee` rejects the transaction if the user doesn't have enough tokens. +`GSNBouncerERC20Fee` rejects the transaction if the user doesn't have enough tokens. If the user has the required amount of tokens, `GSNBouncerERC20Fee` approves the transaction and returns `_approveRelayedCall` to make that data available to `_preRelayedCall` and `_postRelayedCall`. ==== Token transfer From 2fe4946e4b3b4955899f5ac3f1794187b039e6c6 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 16:43:32 +1000 Subject: [PATCH 14/24] Fix code snippet markdown --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index b81b5d9c728..c36ef8d0625 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -247,3 +247,4 @@ contract MyContract is GSNRecipient, MyCustomBouncer { constructor() public MyCustomBouncer() { } } +---- From 78dab062d891993e6a36c5b361d656c09786192a Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 17:26:46 +1000 Subject: [PATCH 15/24] Change to API references to xref:api --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index c36ef8d0625..08ef974a7e6 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -22,7 +22,7 @@ A GSN recipient contract needs the following to work: Depositing funds for the GSN recipient contract can be done via the https://gsn.openzeppelin.com/recipients[GSN Dapp tool] or programmatically with https://github.com/OpenZeppelin/openzeppelin-gsn-helpers#usage-from-code[OpenZeppelin GSN Helpers]. -The actual user's `msg.sender` and `msg.data` can be obtained safely via `_msgSender()` and `_msgData()` of https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNRecipient[`GSNRecipient`]. +The actual user's `msg.sender` and `msg.data` can be obtained safely via `_msgSender()` and `_msgData()` of xref:api:gsn.adoc#GSNRecipient[`GSNRecipient`]. Deciding how to approve and reject transactions is a bit more complex. The GSN recipient contract, with the simplest implementation, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. @@ -30,7 +30,7 @@ In this guide we describe how to use the included bouncers xref:api:gsn.adoc#GSN == GSNBouncerSignature -https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerSignature[`GSNBouncerSignature`] lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. +xref:api:gsn.adoc#GSNBouncerSignature[`GSNBouncerSignature`] lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. The transaction request must include a signature by the same account that was added to the contract as a trusted signer. If it is not the same, `GSNBouncerSignature` will not accept the transaction. @@ -127,13 +127,13 @@ contract MyContract is GSNRecipient, GSNBouncerSignature { == GSNBouncerERC20Fee -https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, `GSNBouncerERC20Fee` doesn't require any off-chain services. +xref:api:gsn.adoc#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, `GSNBouncerERC20Fee` doesn't require any off-chain services. Instead of off-chain approving each transaction, you will give contract specific ERC20 tokens to your users. These tokens are then used as payment for GSN transactions to your recipient contract. Any user that has enough tokens is automatically approved and the recipient contract will cover their transaction costs! This bouncer charges users for the ether cost your recipient contract will incur. Each recipient contract has their own unique token, with a baked-in exchange rate of 1:1 to ether, since they act as an ether replacement when using the GSN. -`GSNBouncerERC20Fee` has an internal https://docs.openzeppelin.com/contracts/2.x/api/gsn#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole-onlyMinter--[`onlyMinter`]). +`GSNBouncerERC20Fee` has an internal xref:api:gsn.adoc#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as xref:api:access.adoc#MinterRole-onlyMinter--[`onlyMinter`]). Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when users buy them off-chain, give tokens based on a users subscription, etc. NOTE: *Users do not need to call approve* on their tokens for your recipient contract to use them. They are a modified ERC20 variant that lets the recipient contract retrieve them. @@ -215,7 +215,7 @@ Always use `_preRelayedCall` and `_postRelayedCall` functions. === How to use GSNBouncerERC20Fee -Your GSN recipient contract needs to inherit from `GSNRecipient` and `GSNBouncerERC20Fee` along with appropriate xref:access-control.adoc[access control] (for token minting), set the token details in the constructor of `GSNBouncerERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses the https://docs.openzeppelin.com/contracts/2.x/api/access#MinterRole[MinterRole]): +Your GSN recipient contract needs to inherit from `GSNRecipient` and `GSNBouncerERC20Fee` along with appropriate xref:access-control.adoc[access control] (for token minting), set the token details in the constructor of `GSNBouncerERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses the xref:api:access.adoc#MinterRole[MinterRole]): NOTE: The token must have decimals of 18 to match that of ether, due to the baked-in exchange rate of 1:1. From 8dc7b0775576bf16c63c78cbeeadca14e5195b5d Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 19:07:08 +1000 Subject: [PATCH 16/24] Remove code from How it works sections --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 136 ++-------------------- 1 file changed, 10 insertions(+), 126 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 08ef974a7e6..bd2bad605f2 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -45,71 +45,11 @@ On the other hand, you need to have a backend server, microservice, or lambda fu === How does GSNBouncerSignature work? -`GSNBouncerSignature` decides whether or not to accept the transaction based on the included signature. No further GSN actions need to be taken. The `acceptRelayedCall` function only relies on the `approvalData`. The definition of `acceptRelayedCall` function from `GSNBouncerSignature` is below: +`GSNBouncerSignature` decides whether or not to accept the transaction request based on the included signature. -[source,solidity] ----- -function acceptRelayedCall( - address relay, - address from, - bytes calldata encodedFunction, - uint256 transactionFee, - uint256 gasPrice, - uint256 gasLimit, - uint256 nonce, - bytes calldata approvalData, - uint256 -) - external - view - returns (uint256, bytes memory) -{ - bytes memory blob = abi.encodePacked( - relay, - from, - encodedFunction, - transactionFee, - gasPrice, - gasLimit, - nonce, // Prevents replays on RelayHub - getHubAddr(), // Prevents replays in multiple RelayHubs - address(this) // Prevents replays in multiple recipients - ); - if (keccak256(blob).toEthSignedMessageHash().recover(approvalData) == _trustedSigner) { - return _approveRelayedCall(); - } else { - return _rejectRelayedCall(uint256(GSNBouncerSignatureErrorCodes.INVALID_SIGNER)); - } -} ----- - -==== Approve/Reject - -When the included signature matches the trusted signer, the function returns the following: - -[source,solidity] ----- -return _approveRelayedCall(); - -// Defined on base class GSNBouncerBase -// uint256 constant private RELAYED_CALL_ACCEPTED = 0; -// function _approveRelayedCall(bytes memory context) internal pure returns (uint256, bytes memory) { -// return (RELAYED_CALL_ACCEPTED, context); -// } ----- - -On the other hand, when the included signature doesn't match the trusted signer, the call gets rejected with the following: - -[source,solidity] ----- -return _rejectRelayedCall(uint256(GSNBouncerSignatureErrorCodes.INVALID_SIGNER)); - -// Defined on base class GSNBouncerBase -// uint256 constant private RELAYED_CALL_REJECTED = 11; -// function _rejectRelayedCall(uint256 errorCode) internal pure returns (uint256, bytes memory) { -// return (RELAYED_CALL_REJECTED + errorCode, ""); -// } ----- +The `acceptRelayedCall` implementation recovers the address from the signature in `approvalData` and compares to the trusted signer. +If the included signature matches the trusted signer, the transaction request is approved. +On the other hand, when the included signature doesn't match the trusted signer, the transaction request gets rejected with an error code of `INVALID_SIGNER`. === How to use GSNBouncerSignature @@ -140,71 +80,15 @@ NOTE: *Users do not need to call approve* on their tokens for your recipient con === How does GSNBouncerERC20Fee work? -`GSNBouncerERC20Fee` decides to approve or reject transactions based on the balance of the users tokens. The definition of `acceptRelayedCall` function from `GSNBouncerERC20Fee` is below: +`GSNBouncerERC20Fee` decides to approve or reject transactions based on the balance of the users tokens. -[source,solidity] ----- -function acceptRelayedCall( - address, - address from, - bytes calldata, - uint256 transactionFee, - uint256 gasPrice, - uint256, - uint256, - bytes calldata, - uint256 maxPossibleCharge -) - external - view - returns (uint256, bytes memory) -{ - if (_token.balanceOf(from) < maxPossibleCharge) { - return _rejectRelayedCall(uint256(GSNBouncerERC20FeeErrorCodes.INSUFFICIENT_BALANCE)); - } - - return _approveRelayedCall(abi.encode(from, maxPossibleCharge, transactionFee, gasPrice)); -} ----- - -==== Approve/Reject - -`GSNBouncerERC20Fee` rejects the transaction if the user doesn't have enough tokens. -If the user has the required amount of tokens, `GSNBouncerERC20Fee` approves the transaction and returns `_approveRelayedCall` to make that data available to `_preRelayedCall` and `_postRelayedCall`. - -==== Token transfer - -Now, let's see how the token transfer is performed inside the `_preRelayedCall` function. - -[source,solidity] ----- -function _preRelayedCall(bytes memory context) internal returns (bytes32) { - (address from, uint256 maxPossibleCharge) = abi.decode(context, (address, uint256)); - - // The maximum token charge is pre-charged from the user - _token.safeTransferFrom(from, address(this), maxPossibleCharge); -} ----- +The `acceptRelayedCall` function implementation checks the users token balance. +if the user doesn't have enough tokens the transaction request gets rejected with an error of `INSUFFICIENT_BALANCE`. +If there are enough tokens, the transaction request is approved with the users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`. +In `_preRelayedCall` function the `maxPossibleCharge` amount of tokens is transferred to the recipient contract. The maximum amount of tokens required is transferred assuming that the call will use all the gas available. -Then, in the `_postRelayedCall` function, the actual amount is calculated - including the recipient contract implementation and ERC20 token transfers - and refund the difference. - -[source,solidity] ----- -function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal { - (address from, uint256 maxPossibleCharge, uint256 transactionFee, uint256 gasPrice) = - abi.decode(context, (address, uint256, uint256, uint256)); - - // actualCharge is an _estimated_ charge, which assumes postRelayedCall will use all available gas. - // This implementation's gas cost can be roughly estimated as 10k gas, for the two SSTORE operations in an - // ERC20 transfer. - uint256 overestimation = _computeCharge(POST_RELAYED_CALL_MAX_GAS.sub(10000), gasPrice, transactionFee); - actualCharge = actualCharge.sub(overestimation); - - // After the relayed call has been executed and the actual charge estimated, the excess pre-charge is returned - _token.safeTransfer(from, maxPossibleCharge.sub(actualCharge)); -} ----- +Then, in the `_postRelayedCall` function, the actual amount is calculated, including the recipient contract implementation and ERC20 token transfers, and refund the difference. The maximum amount of tokens required is transferred to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions). From 5b8d52142909bd29ca920637b99bdb38942e4df8 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 19:18:47 +1000 Subject: [PATCH 17/24] Explain 1:1 exchange rate --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index bd2bad605f2..0fed0284e91 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -68,10 +68,10 @@ contract MyContract is GSNRecipient, GSNBouncerSignature { == GSNBouncerERC20Fee xref:api:gsn.adoc#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, `GSNBouncerERC20Fee` doesn't require any off-chain services. -Instead of off-chain approving each transaction, you will give contract specific ERC20 tokens to your users. These tokens are then used as payment for GSN transactions to your recipient contract. +Instead of off-chain approving each transaction, you will give special-purpose ERC20 tokens to your users. These tokens are then used as payment for GSN transactions to your recipient contract. Any user that has enough tokens is automatically approved and the recipient contract will cover their transaction costs! -This bouncer charges users for the ether cost your recipient contract will incur. Each recipient contract has their own unique token, with a baked-in exchange rate of 1:1 to ether, since they act as an ether replacement when using the GSN. +Each recipient contract has their own unique special-purpose token. The exchange rate to ether is 1:1, as the tokens are used to pay your contract to cover the gas fees when using the GSN. `GSNBouncerERC20Fee` has an internal xref:api:gsn.adoc#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as xref:api:access.adoc#MinterRole-onlyMinter--[`onlyMinter`]). Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when users buy them off-chain, give tokens based on a users subscription, etc. From 4ec9e6b4e7f55b7c0e9b108ed4ae65646ce2c0ae Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 18 Sep 2019 19:37:21 +1000 Subject: [PATCH 18/24] Change transaction request to relayed call --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 0fed0284e91..8ee1502bf60 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -1,6 +1,6 @@ = GSN Bouncers -This guide shows you different strategies to accept transactions via the Gas Station Network (GSN) using GSN Bouncers. +This guide shows you different strategies to accept relayed calls via the Gas Station Network (GSN) using GSN Bouncers. First, we will explain the Bouncer concept, and then we will showcase how to use the two most common strategies. Finally, we will cover how to create your own Custom Bouncer. @@ -10,7 +10,7 @@ If you're still learning about the basics of the Gas Station Network, you should [[gsn-bouncers]] == GSN Bouncers explained -A *GSN Bouncer* decides which transaction gets approved and which transaction gets rejected. Bouncers are a key concept within the GSN. Dapps need Bouncers to prevent malicious users from spending the dapp's funds for transaction fees. +A *GSN Bouncer* decides which relayed call gets approved and which relayed call gets rejected. Bouncers are a key concept within the GSN. Dapps need Bouncers to prevent malicious users from spending the dapp's funds for relayed call fees. As we have seen in the xref:gsn.adoc[GSN Guide], in order to be GSN enabled, your contracts need to extend from xref:api:gsn.adoc#GSNRecipient[`GSNRecipient`]. @@ -18,26 +18,26 @@ A GSN recipient contract needs the following to work: 1. It needs to have funds deposited on its RelayHub. 2. It needs to handle `msg.sender` and `msg.data` differently -3. It needs to decide how to approve and reject transactions. +3. It needs to decide how to approve and reject relayed calls. Depositing funds for the GSN recipient contract can be done via the https://gsn.openzeppelin.com/recipients[GSN Dapp tool] or programmatically with https://github.com/OpenZeppelin/openzeppelin-gsn-helpers#usage-from-code[OpenZeppelin GSN Helpers]. The actual user's `msg.sender` and `msg.data` can be obtained safely via `_msgSender()` and `_msgData()` of xref:api:gsn.adoc#GSNRecipient[`GSNRecipient`]. -Deciding how to approve and reject transactions is a bit more complex. The GSN recipient contract, with the simplest implementation, will accept and pay for all transactions. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. +Deciding how to approve and reject relayed calls is a bit more complex. The GSN recipient contract, with the simplest implementation, will accept and pay for all relayed calls. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. In this guide we describe how to use the included bouncers xref:api:gsn.adoc#GSNBouncerSignature[`GSNBouncerSignature`] and xref:api:gsn.adoc#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`], along with how to create your own Custom Bouncer. == GSNBouncerSignature -xref:api:gsn.adoc#GSNBouncerSignature[`GSNBouncerSignature`] lets users call your recipient contract via the GSN (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. +xref:api:gsn.adoc#GSNBouncerSignature[`GSNBouncerSignature`] lets users relay calls via the GSN to your recipient contract (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. -The transaction request must include a signature by the same account that was added to the contract as a trusted signer. If it is not the same, `GSNBouncerSignature` will not accept the transaction. +The relayed call must include a signature by the same account that was added to the contract as a trusted signer. If it is not the same, `GSNBouncerSignature` will not accept the relayed call. -This means that you need to set up a system where your trusted account includes their signature with transaction requests, as long as they are valid users (according to your business logic). +This means that you need to set up a system where your trusted account includes their signature with the relayed call, as long as they are valid users (according to your business logic). The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of https://en.wikipedia.org/wiki/OAuth[OAuth] and validation, e.g. gone through a captcha or validated their email address. -You could restrict it further and let new users send a specific number of transactions (e.g. limit to 5 requests via the GSN, at which point the user needs to create a wallet). +You could restrict it further and let new users send a specific number of relayed calls (e.g. limit to 5 relayed calls via the GSN, at which point the user needs to create a wallet). Alternatively, you could charge the user off-chain (e.g. via credit card) for credit on your system and let them create GSN transactions until their credit runs out. The great thing about this setup is that *your contract doesn't need to change* if you want to change the business rules. All you are doing is changing the backend logic conditions under which users use your contract for free. @@ -45,15 +45,15 @@ On the other hand, you need to have a backend server, microservice, or lambda fu === How does GSNBouncerSignature work? -`GSNBouncerSignature` decides whether or not to accept the transaction request based on the included signature. +`GSNBouncerSignature` decides whether or not to accept the relayed call based on the included signature. The `acceptRelayedCall` implementation recovers the address from the signature in `approvalData` and compares to the trusted signer. -If the included signature matches the trusted signer, the transaction request is approved. -On the other hand, when the included signature doesn't match the trusted signer, the transaction request gets rejected with an error code of `INVALID_SIGNER`. +If the included signature matches the trusted signer, the relayed call is approved. +On the other hand, when the included signature doesn't match the trusted signer, the relayed call gets rejected with an error code of `INVALID_SIGNER`. === How to use GSNBouncerSignature -You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a GSN transaction, which includes (or doesn't include) their signature with each transaction request based on your business logic. +You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a GSN transaction, which includes (or doesn't include) their signature with each relayed call based on your business logic. Your GSN recipient contract needs to inherit from `GSNRecipient` and `GSNBouncerSignature`, as well as setting the trusted signer in the constructor of `GSNBouncerSignature` as per the following sample code below: @@ -68,8 +68,8 @@ contract MyContract is GSNRecipient, GSNBouncerSignature { == GSNBouncerERC20Fee xref:api:gsn.adoc#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, `GSNBouncerERC20Fee` doesn't require any off-chain services. -Instead of off-chain approving each transaction, you will give special-purpose ERC20 tokens to your users. These tokens are then used as payment for GSN transactions to your recipient contract. -Any user that has enough tokens is automatically approved and the recipient contract will cover their transaction costs! +Instead of off-chain approving each relayed call, you will give special-purpose ERC20 tokens to your users. These tokens are then used as payment for GSN transactions to your recipient contract. +Any user that has enough tokens has their relayed calls automatically approved and the recipient contract will cover their transaction costs! Each recipient contract has their own unique special-purpose token. The exchange rate to ether is 1:1, as the tokens are used to pay your contract to cover the gas fees when using the GSN. @@ -80,11 +80,11 @@ NOTE: *Users do not need to call approve* on their tokens for your recipient con === How does GSNBouncerERC20Fee work? -`GSNBouncerERC20Fee` decides to approve or reject transactions based on the balance of the users tokens. +`GSNBouncerERC20Fee` decides to approve or reject relayed calls based on the balance of the users tokens. The `acceptRelayedCall` function implementation checks the users token balance. -if the user doesn't have enough tokens the transaction request gets rejected with an error of `INSUFFICIENT_BALANCE`. -If there are enough tokens, the transaction request is approved with the users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`. +if the user doesn't have enough tokens the relayed call gets rejected with an error of `INSUFFICIENT_BALANCE`. +If there are enough tokens, the relayed call is approved with the users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`. In `_preRelayedCall` function the `maxPossibleCharge` amount of tokens is transferred to the recipient contract. The maximum amount of tokens required is transferred assuming that the call will use all the gas available. @@ -117,7 +117,7 @@ contract MyContract is GSNRecipient, GSNBouncerERC20Fee, MinterRole { == Custom Bouncer -You can create your own Custom Bouncer! For example, your Custom Bouncer could use a specified token to pay for transactions with a custom exchange rate to ether. Alternatively you could issue users who subscribe to your dapp ERC721 tokens and accounts holding the subscription token could use your contract for free as part of the subscription. There are lots of potential options for your Custom Bouncer. +You can create your own Custom Bouncer! For example, your Custom Bouncer could use a specified token to pay for relayed calls with a custom exchange rate to ether. Alternatively you could issue users who subscribe to your dapp ERC721 tokens and accounts holding the subscription token could use your contract for free as part of the subscription. There are lots of potential options for your Custom Bouncer. Your Custom Bouncer can inherit from `GSNBouncerBase` and must implement the `acceptRelayedCall` function. From c263a9e37ee645c7f7cae183294a75066198414d Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Thu, 19 Sep 2019 13:03:55 +1000 Subject: [PATCH 19/24] Minor fixes --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 8ee1502bf60..e5df1079634 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -83,14 +83,14 @@ NOTE: *Users do not need to call approve* on their tokens for your recipient con `GSNBouncerERC20Fee` decides to approve or reject relayed calls based on the balance of the users tokens. The `acceptRelayedCall` function implementation checks the users token balance. -if the user doesn't have enough tokens the relayed call gets rejected with an error of `INSUFFICIENT_BALANCE`. +Iif the user doesn't have enough tokens the relayed call gets rejected with an error of `INSUFFICIENT_BALANCE`. If there are enough tokens, the relayed call is approved with the users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`. In `_preRelayedCall` function the `maxPossibleCharge` amount of tokens is transferred to the recipient contract. -The maximum amount of tokens required is transferred assuming that the call will use all the gas available. +The maximum amount of tokens required is transferred assuming that the relayed call will use all the gas available. Then, in the `_postRelayedCall` function, the actual amount is calculated, including the recipient contract implementation and ERC20 token transfers, and refund the difference. -The maximum amount of tokens required is transferred to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions). +The maximum amount of tokens required is transferred in `_preRelayedCall` to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions). NOTE: The gas cost estimation is not 100% accurate, we may tweak it further down the road. From 7858a8760e5551f0fc874be053b55690ffcf54b6 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Thu, 19 Sep 2019 13:38:10 +1000 Subject: [PATCH 20/24] Add info to Custom Bouncers --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index e5df1079634..4f8fb7d90a0 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -84,7 +84,7 @@ NOTE: *Users do not need to call approve* on their tokens for your recipient con The `acceptRelayedCall` function implementation checks the users token balance. Iif the user doesn't have enough tokens the relayed call gets rejected with an error of `INSUFFICIENT_BALANCE`. -If there are enough tokens, the relayed call is approved with the users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`. +If there are enough tokens, the relayed call is approved with the end users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`. In `_preRelayedCall` function the `maxPossibleCharge` amount of tokens is transferred to the recipient contract. The maximum amount of tokens required is transferred assuming that the relayed call will use all the gas available. @@ -94,8 +94,7 @@ The maximum amount of tokens required is transferred in `_preRelayedCall` to pro NOTE: The gas cost estimation is not 100% accurate, we may tweak it further down the road. -NOTE: Internal `_preRelayedCall` and `_postRelayedCall` functions are used instead of public `preRelayedCall` and `postRelayedCall` functions. This prevents them from being called by non-RelayHub contracts. -Always use `_preRelayedCall` and `_postRelayedCall` functions. +NOTE: Always use `_preRelayedCall` and `_postRelayedCall` functions. Internal `_preRelayedCall` and `_postRelayedCall` functions are used instead of public `preRelayedCall` and `postRelayedCall` functions, as the public functions are prevented from being called by non-RelayHub contracts. === How to use GSNBouncerERC20Fee @@ -121,7 +120,15 @@ You can create your own Custom Bouncer! For example, your Custom Bouncer could Your Custom Bouncer can inherit from `GSNBouncerBase` and must implement the `acceptRelayedCall` function. -Depending on the logic for your Custom Bouncer, you may need to implement `_postRelayedCall` and `_preRelayedCall`. +Your `acceptRelayedCall` implementation decides whether or not to accept the relayed call. +If your logic accepts the relayed call then you should return `_approveRelayedCall`. +If your logic rejects the relayed call then you should return `_rejectRelayedCall` with an error code. +See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/GSN/bouncers/GSNBouncerSignature.sol[GSNBouncerSignature.sol] as an example implementation. + +For Custom Bouncers charging end users, `_postRelayedCall` and `_preRelayedCall` should be implemented to handle the charging. +Your `_preRelayedCall` implementation should take the maximum possible charge, with your `_postRelayedCall` implementation refunding any difference from the actual charge once the relayed call has been made. +When returning `_approveRelayedCall` to approve the relayed call, the end users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data can also be returned so that the data can be used in `_preRelayedCall` and `_postRelayedCall`. +See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v2.4.0/contracts/GSN/bouncers/GSNBouncerERC20Fee.sol[GSNBouncerERC20Fee.sol] as an example implementation. Your GSN recipient contract needs to inherit from `GSNRecipient` and your Custom Bouncer as per the following sample code: From 1a0bc77418d87c6c688d7b22919e8531b4dd1982 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Thu, 19 Sep 2019 14:39:26 +1000 Subject: [PATCH 21/24] Typo --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 4f8fb7d90a0..3e20832fe37 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -83,7 +83,7 @@ NOTE: *Users do not need to call approve* on their tokens for your recipient con `GSNBouncerERC20Fee` decides to approve or reject relayed calls based on the balance of the users tokens. The `acceptRelayedCall` function implementation checks the users token balance. -Iif the user doesn't have enough tokens the relayed call gets rejected with an error of `INSUFFICIENT_BALANCE`. +If the user doesn't have enough tokens the relayed call gets rejected with an error of `INSUFFICIENT_BALANCE`. If there are enough tokens, the relayed call is approved with the end users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`. In `_preRelayedCall` function the `maxPossibleCharge` amount of tokens is transferred to the recipient contract. From 6ccddd2c99d21673e45041e74a806ffc448df75a Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Thu, 19 Sep 2019 14:57:10 +1000 Subject: [PATCH 22/24] Minor fixes --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 3e20832fe37..c4fd965ac5e 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -22,7 +22,7 @@ A GSN recipient contract needs the following to work: Depositing funds for the GSN recipient contract can be done via the https://gsn.openzeppelin.com/recipients[GSN Dapp tool] or programmatically with https://github.com/OpenZeppelin/openzeppelin-gsn-helpers#usage-from-code[OpenZeppelin GSN Helpers]. -The actual user's `msg.sender` and `msg.data` can be obtained safely via `_msgSender()` and `_msgData()` of xref:api:gsn.adoc#GSNRecipient[`GSNRecipient`]. +The actual user's `msg.sender` and `msg.data` can be obtained safely via xref:api:gsn.adoc#GSNRecipient-_msgSender--[`_msgSender()`] and xref:api:gsn.adoc#GSNRecipient-_msgData--[`_msgData()`] of xref:api:gsn.adoc#GSNRecipient[`GSNRecipient`]. Deciding how to approve and reject relayed calls is a bit more complex. The GSN recipient contract, with the simplest implementation, will accept and pay for all relayed calls. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these contracts _GSN Bouncers_. @@ -38,7 +38,7 @@ This means that you need to set up a system where your trusted account includes The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of https://en.wikipedia.org/wiki/OAuth[OAuth] and validation, e.g. gone through a captcha or validated their email address. You could restrict it further and let new users send a specific number of relayed calls (e.g. limit to 5 relayed calls via the GSN, at which point the user needs to create a wallet). -Alternatively, you could charge the user off-chain (e.g. via credit card) for credit on your system and let them create GSN transactions until their credit runs out. +Alternatively, you could charge the user off-chain (e.g. via credit card) for credit on your system and let them create relayed calls until their credit runs out. The great thing about this setup is that *your contract doesn't need to change* if you want to change the business rules. All you are doing is changing the backend logic conditions under which users use your contract for free. On the other hand, you need to have a backend server, microservice, or lambda function to accomplish this. @@ -53,7 +53,7 @@ On the other hand, when the included signature doesn't match the trusted signer, === How to use GSNBouncerSignature -You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a GSN transaction, which includes (or doesn't include) their signature with each relayed call based on your business logic. +You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a relayed call, which includes (or doesn't include) their signature with each relayed call based on your business logic. Your GSN recipient contract needs to inherit from `GSNRecipient` and `GSNBouncerSignature`, as well as setting the trusted signer in the constructor of `GSNBouncerSignature` as per the following sample code below: @@ -68,10 +68,10 @@ contract MyContract is GSNRecipient, GSNBouncerSignature { == GSNBouncerERC20Fee xref:api:gsn.adoc#GSNBouncerERC20Fee[`GSNBouncerERC20Fee`] is a bit more complex (but don't worry, it has already been written for you!). Unlike `GSNBouncerSignature`, `GSNBouncerERC20Fee` doesn't require any off-chain services. -Instead of off-chain approving each relayed call, you will give special-purpose ERC20 tokens to your users. These tokens are then used as payment for GSN transactions to your recipient contract. -Any user that has enough tokens has their relayed calls automatically approved and the recipient contract will cover their transaction costs! +Instead of off-chain approving each relayed call, you will give special-purpose ERC20 tokens to your users. These tokens are then used as payment for relayed calls to your recipient contract. +Any user that has enough tokens to pay has their relayed calls automatically approved and the recipient contract will cover their transaction costs! -Each recipient contract has their own unique special-purpose token. The exchange rate to ether is 1:1, as the tokens are used to pay your contract to cover the gas fees when using the GSN. +Each recipient contract has their own special-purpose token. The exchange rate from token to ether is 1:1, as the tokens are used to pay your contract to cover the gas fees when using the GSN. `GSNBouncerERC20Fee` has an internal xref:api:gsn.adoc#GSNBouncerERC20Fee-_mint-address-uint256-[`_mint`] function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of xref:access-control.adoc[access control] such as xref:api:access.adoc#MinterRole-onlyMinter--[`onlyMinter`]). Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when users buy them off-chain, give tokens based on a users subscription, etc. From ddb0ea43069df4c270e61896986aab507d8c7e20 Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 25 Sep 2019 11:07:19 +1000 Subject: [PATCH 23/24] reorder sentence based on review gsn-bouncers.adoc Co-Authored-By: Francisco Giordano --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index c4fd965ac5e..7bc0aacb653 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -88,7 +88,7 @@ If there are enough tokens, the relayed call is approved with the end users addr In `_preRelayedCall` function the `maxPossibleCharge` amount of tokens is transferred to the recipient contract. The maximum amount of tokens required is transferred assuming that the relayed call will use all the gas available. -Then, in the `_postRelayedCall` function, the actual amount is calculated, including the recipient contract implementation and ERC20 token transfers, and refund the difference. +Then, in the `_postRelayedCall` function, the actual amount is calculated, including the recipient contract implementation and ERC20 token transfers, and the difference is refunded. The maximum amount of tokens required is transferred in `_preRelayedCall` to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions). From f81f43c47888d7d156b9163bab8093c95b4337ce Mon Sep 17 00:00:00 2001 From: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> Date: Wed, 25 Sep 2019 11:56:02 +1000 Subject: [PATCH 24/24] Improve wording of signing of relayed call parameters by trusted signer --- docs/modules/ROOT/pages/gsn-bouncers.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/pages/gsn-bouncers.adoc b/docs/modules/ROOT/pages/gsn-bouncers.adoc index 7bc0aacb653..d43808f17c6 100644 --- a/docs/modules/ROOT/pages/gsn-bouncers.adoc +++ b/docs/modules/ROOT/pages/gsn-bouncers.adoc @@ -32,9 +32,9 @@ In this guide we describe how to use the included bouncers xref:api:gsn.adoc#GSN xref:api:gsn.adoc#GSNBouncerSignature[`GSNBouncerSignature`] lets users relay calls via the GSN to your recipient contract (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. -The relayed call must include a signature by the same account that was added to the contract as a trusted signer. If it is not the same, `GSNBouncerSignature` will not accept the relayed call. +The relayed call must include a signature of the relayed call parameters by the same account that was added to the contract as a trusted signer. If it is not the same, `GSNBouncerSignature` will not accept the relayed call. -This means that you need to set up a system where your trusted account includes their signature with the relayed call, as long as they are valid users (according to your business logic). +This means that you need to set up a system where your trusted account signs the relayed call parameters to then include in the relayed call, as long as they are valid users (according to your business logic). The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of https://en.wikipedia.org/wiki/OAuth[OAuth] and validation, e.g. gone through a captcha or validated their email address. You could restrict it further and let new users send a specific number of relayed calls (e.g. limit to 5 relayed calls via the GSN, at which point the user needs to create a wallet). @@ -47,13 +47,13 @@ On the other hand, you need to have a backend server, microservice, or lambda fu `GSNBouncerSignature` decides whether or not to accept the relayed call based on the included signature. -The `acceptRelayedCall` implementation recovers the address from the signature in `approvalData` and compares to the trusted signer. +The `acceptRelayedCall` implementation recovers the address from the signature of the relayed call parameters in `approvalData` and compares to the trusted signer. If the included signature matches the trusted signer, the relayed call is approved. On the other hand, when the included signature doesn't match the trusted signer, the relayed call gets rejected with an error code of `INVALID_SIGNER`. === How to use GSNBouncerSignature -You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls when requesting a relayed call, which includes (or doesn't include) their signature with each relayed call based on your business logic. +You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls to sign (or not sign, based on your business logic) the relayed call parameters with your trusted signer account. The signature is then included as the `approvalData` in the relayed call. Your GSN recipient contract needs to inherit from `GSNRecipient` and `GSNBouncerSignature`, as well as setting the trusted signer in the constructor of `GSNBouncerSignature` as per the following sample code below: