浏览代码

GSN compatibility (#1880)

* switch to using Context internally

* add context import

* Add smoke test to make sure enabling GSN support works

* Update test/GSN/ERC721GSNRecipientMock.test.js

Co-Authored-By: Francisco Giordano <frangio.1@gmail.com>

* Upgrade truffle

* add missing awaits

* Revert "Upgrade truffle"

This reverts commit f9b0ba9019650fdbf8919a33e6fba834689e6b04.
Nicolás Venturo 6 年之前
父节点
当前提交
d1158ea68c

+ 1 - 0
.editorconfig

@@ -6,6 +6,7 @@ root = true
 [*]
 charset = utf-8
 end_of_line = lf
+indent_size = 4
 indent_style = space
 insert_final_newline = true
 trim_trailing_whitespace = true

+ 5 - 4
contracts/access/roles/CapperRole.sol

@@ -1,8 +1,9 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "../Roles.sol";
 
-contract CapperRole {
+contract CapperRole is Context {
     using Roles for Roles.Role;
 
     event CapperAdded(address indexed account);
@@ -11,11 +12,11 @@ contract CapperRole {
     Roles.Role private _cappers;
 
     constructor () internal {
-        _addCapper(msg.sender);
+        _addCapper(_msgSender());
     }
 
     modifier onlyCapper() {
-        require(isCapper(msg.sender), "CapperRole: caller does not have the Capper role");
+        require(isCapper(_msgSender()), "CapperRole: caller does not have the Capper role");
         _;
     }
 
@@ -28,7 +29,7 @@ contract CapperRole {
     }
 
     function renounceCapper() public {
-        _removeCapper(msg.sender);
+        _removeCapper(_msgSender());
     }
 
     function _addCapper(address account) internal {

+ 5 - 4
contracts/access/roles/MinterRole.sol

@@ -1,8 +1,9 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "../Roles.sol";
 
-contract MinterRole {
+contract MinterRole is Context {
     using Roles for Roles.Role;
 
     event MinterAdded(address indexed account);
@@ -11,11 +12,11 @@ contract MinterRole {
     Roles.Role private _minters;
 
     constructor () internal {
-        _addMinter(msg.sender);
+        _addMinter(_msgSender());
     }
 
     modifier onlyMinter() {
-        require(isMinter(msg.sender), "MinterRole: caller does not have the Minter role");
+        require(isMinter(_msgSender()), "MinterRole: caller does not have the Minter role");
         _;
     }
 
@@ -28,7 +29,7 @@ contract MinterRole {
     }
 
     function renounceMinter() public {
-        _removeMinter(msg.sender);
+        _removeMinter(_msgSender());
     }
 
     function _addMinter(address account) internal {

+ 5 - 4
contracts/access/roles/PauserRole.sol

@@ -1,8 +1,9 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "../Roles.sol";
 
-contract PauserRole {
+contract PauserRole is Context {
     using Roles for Roles.Role;
 
     event PauserAdded(address indexed account);
@@ -11,11 +12,11 @@ contract PauserRole {
     Roles.Role private _pausers;
 
     constructor () internal {
-        _addPauser(msg.sender);
+        _addPauser(_msgSender());
     }
 
     modifier onlyPauser() {
-        require(isPauser(msg.sender), "PauserRole: caller does not have the Pauser role");
+        require(isPauser(_msgSender()), "PauserRole: caller does not have the Pauser role");
         _;
     }
 
@@ -28,7 +29,7 @@ contract PauserRole {
     }
 
     function renouncePauser() public {
-        _removePauser(msg.sender);
+        _removePauser(_msgSender());
     }
 
     function _addPauser(address account) internal {

+ 5 - 4
contracts/access/roles/SignerRole.sol

@@ -1,8 +1,9 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "../Roles.sol";
 
-contract SignerRole {
+contract SignerRole is Context {
     using Roles for Roles.Role;
 
     event SignerAdded(address indexed account);
@@ -11,11 +12,11 @@ contract SignerRole {
     Roles.Role private _signers;
 
     constructor () internal {
-        _addSigner(msg.sender);
+        _addSigner(_msgSender());
     }
 
     modifier onlySigner() {
-        require(isSigner(msg.sender), "SignerRole: caller does not have the Signer role");
+        require(isSigner(_msgSender()), "SignerRole: caller does not have the Signer role");
         _;
     }
 
@@ -28,7 +29,7 @@ contract SignerRole {
     }
 
     function renounceSigner() public {
-        _removeSigner(msg.sender);
+        _removeSigner(_msgSender());
     }
 
     function _addSigner(address account) internal {

+ 5 - 4
contracts/access/roles/WhitelistAdminRole.sol

@@ -1,12 +1,13 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "../Roles.sol";
 
 /**
  * @title WhitelistAdminRole
  * @dev WhitelistAdmins are responsible for assigning and removing Whitelisted accounts.
  */
-contract WhitelistAdminRole {
+contract WhitelistAdminRole is Context {
     using Roles for Roles.Role;
 
     event WhitelistAdminAdded(address indexed account);
@@ -15,11 +16,11 @@ contract WhitelistAdminRole {
     Roles.Role private _whitelistAdmins;
 
     constructor () internal {
-        _addWhitelistAdmin(msg.sender);
+        _addWhitelistAdmin(_msgSender());
     }
 
     modifier onlyWhitelistAdmin() {
-        require(isWhitelistAdmin(msg.sender), "WhitelistAdminRole: caller does not have the WhitelistAdmin role");
+        require(isWhitelistAdmin(_msgSender()), "WhitelistAdminRole: caller does not have the WhitelistAdmin role");
         _;
     }
 
@@ -32,7 +33,7 @@ contract WhitelistAdminRole {
     }
 
     function renounceWhitelistAdmin() public {
-        _removeWhitelistAdmin(msg.sender);
+        _removeWhitelistAdmin(_msgSender());
     }
 
     function _addWhitelistAdmin(address account) internal {

+ 4 - 3
contracts/access/roles/WhitelistedRole.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "../Roles.sol";
 import "./WhitelistAdminRole.sol";
 
@@ -9,7 +10,7 @@ import "./WhitelistAdminRole.sol";
  * crowdsale). This role is special in that the only accounts that can add it are WhitelistAdmins (who can also remove
  * it), and not Whitelisteds themselves.
  */
-contract WhitelistedRole is WhitelistAdminRole {
+contract WhitelistedRole is Context, WhitelistAdminRole {
     using Roles for Roles.Role;
 
     event WhitelistedAdded(address indexed account);
@@ -18,7 +19,7 @@ contract WhitelistedRole is WhitelistAdminRole {
     Roles.Role private _whitelisteds;
 
     modifier onlyWhitelisted() {
-        require(isWhitelisted(msg.sender), "WhitelistedRole: caller does not have the Whitelisted role");
+        require(isWhitelisted(_msgSender()), "WhitelistedRole: caller does not have the Whitelisted role");
         _;
     }
 
@@ -35,7 +36,7 @@ contract WhitelistedRole is WhitelistAdminRole {
     }
 
     function renounceWhitelisted() public {
-        _removeWhitelisted(msg.sender);
+        _removeWhitelisted(_msgSender());
     }
 
     function _addWhitelisted(address account) internal {

+ 4 - 3
contracts/crowdsale/Crowdsale.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 import "../token/ERC20/IERC20.sol";
 import "../math/SafeMath.sol";
 import "../token/ERC20/SafeERC20.sol";
@@ -17,7 +18,7 @@ import "../utils/ReentrancyGuard.sol";
  * the methods to add functionality. Consider using 'super' where appropriate to concatenate
  * behavior.
  */
-contract Crowdsale is ReentrancyGuard {
+contract Crowdsale is Context, ReentrancyGuard {
     using SafeMath for uint256;
     using SafeERC20 for IERC20;
 
@@ -70,7 +71,7 @@ contract Crowdsale is ReentrancyGuard {
      * buyTokens directly when purchasing tokens from a contract.
      */
     function () external payable {
-        buyTokens(msg.sender);
+        buyTokens(_msgSender());
     }
 
     /**
@@ -118,7 +119,7 @@ contract Crowdsale is ReentrancyGuard {
         _weiRaised = _weiRaised.add(weiAmount);
 
         _processPurchase(beneficiary, tokens);
-        emit TokensPurchased(msg.sender, beneficiary, weiAmount, tokens);
+        emit TokensPurchased(_msgSender(), beneficiary, weiAmount, tokens);
 
         _updatePurchasingState(beneficiary, weiAmount);
 

+ 3 - 2
contracts/crowdsale/distribution/RefundableCrowdsale.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "../../math/SafeMath.sol";
 import "./FinalizableCrowdsale.sol";
 import "../../payment/escrow/RefundEscrow.sol";
@@ -14,7 +15,7 @@ import "../../payment/escrow/RefundEscrow.sol";
  * the goal is unlikely to be met, they sell their tokens (possibly at a discount). The attacker will be refunded when
  * the crowdsale is finalized, and the users that purchased from them will be left with worthless tokens.
  */
-contract RefundableCrowdsale is FinalizableCrowdsale {
+contract RefundableCrowdsale is Context, FinalizableCrowdsale {
     using SafeMath for uint256;
 
     // minimum amount of funds to be raised in weis
@@ -77,6 +78,6 @@ contract RefundableCrowdsale is FinalizableCrowdsale {
      * @dev Overrides Crowdsale fund forwarding, sending funds to escrow.
      */
     function _forwardFunds() internal {
-        _escrow.deposit.value(msg.value)(msg.sender);
+        _escrow.deposit.value(msg.value)(_msgSender());
     }
 }

+ 11 - 8
contracts/drafts/SignatureBouncer.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 import "../access/roles/SignerRole.sol";
 import "../cryptography/ECDSA.sol";
 
@@ -34,7 +35,7 @@ import "../cryptography/ECDSA.sol";
  * the data in the signature much more complex.
  * See https://ethereum.stackexchange.com/a/50616 for more details.
  */
-contract SignatureBouncer is SignerRole {
+contract SignatureBouncer is Context, SignerRole {
     using ECDSA for bytes32;
 
     // Function selectors are 4 bytes long, as documented in
@@ -51,7 +52,7 @@ contract SignatureBouncer is SignerRole {
      * @dev Requires that a valid signature of a signer was provided.
      */
     modifier onlyValidSignature(bytes memory signature) {
-        require(_isValidSignature(msg.sender, signature), "SignatureBouncer: invalid signature for caller");
+        require(_isValidSignature(_msgSender(), signature), "SignatureBouncer: invalid signature for caller");
         _;
     }
 
@@ -60,7 +61,7 @@ contract SignatureBouncer is SignerRole {
      */
     modifier onlyValidSignatureAndMethod(bytes memory signature) {
         // solhint-disable-next-line max-line-length
-        require(_isValidSignatureAndMethod(msg.sender, signature), "SignatureBouncer: invalid signature for caller and method");
+        require(_isValidSignatureAndMethod(_msgSender(), signature), "SignatureBouncer: invalid signature for caller and method");
         _;
     }
 
@@ -69,7 +70,7 @@ contract SignatureBouncer is SignerRole {
      */
     modifier onlyValidSignatureAndData(bytes memory signature) {
         // solhint-disable-next-line max-line-length
-        require(_isValidSignatureAndData(msg.sender, signature), "SignatureBouncer: invalid signature for caller and data");
+        require(_isValidSignatureAndData(_msgSender(), signature), "SignatureBouncer: invalid signature for caller and data");
         _;
     }
 
@@ -86,9 +87,10 @@ contract SignatureBouncer is SignerRole {
      * @return bool
      */
     function _isValidSignatureAndMethod(address account, bytes memory signature) internal view returns (bool) {
+        bytes memory msgData = _msgData();
         bytes memory data = new bytes(_METHOD_ID_SIZE);
         for (uint256 i = 0; i < data.length; i++) {
-            data[i] = msg.data[i];
+            data[i] = msgData[i];
         }
         return _isValidDataHash(keccak256(abi.encodePacked(address(this), account, data)), signature);
     }
@@ -99,11 +101,12 @@ contract SignatureBouncer is SignerRole {
      * @return bool
      */
     function _isValidSignatureAndData(address account, bytes memory signature) internal view returns (bool) {
-        require(msg.data.length > _SIGNATURE_SIZE, "SignatureBouncer: data is too short");
+        bytes memory msgData = _msgData();
+        require(msgData.length > _SIGNATURE_SIZE, "SignatureBouncer: data is too short");
 
-        bytes memory data = new bytes(msg.data.length - _SIGNATURE_SIZE);
+        bytes memory data = new bytes(msgData.length - _SIGNATURE_SIZE);
         for (uint256 i = 0; i < data.length; i++) {
-            data[i] = msg.data[i];
+            data[i] = msgData[i];
         }
 
         return _isValidDataHash(keccak256(abi.encodePacked(address(this), account, data)), signature);

+ 4 - 3
contracts/examples/SimpleToken.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 import "../token/ERC20/ERC20.sol";
 import "../token/ERC20/ERC20Detailed.sol";
 
@@ -9,12 +10,12 @@ import "../token/ERC20/ERC20Detailed.sol";
  * Note they can later distribute these tokens as they wish using `transfer` and other
  * `ERC20` functions.
  */
-contract SimpleToken is ERC20, ERC20Detailed {
+contract SimpleToken is Context, ERC20, ERC20Detailed {
 
     /**
-     * @dev Constructor that gives msg.sender all of existing tokens.
+     * @dev Constructor that gives _msgSender() all of existing tokens.
      */
     constructor () public ERC20Detailed("SimpleToken", "SIM", 18) {
-        _mint(msg.sender, 10000 * (10 ** uint256(decimals())));
+        _mint(_msgSender(), 10000 * (10 ** uint256(decimals())));
     }
 }

+ 4 - 3
contracts/lifecycle/Pausable.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 import "../access/roles/PauserRole.sol";
 
 /**
@@ -11,7 +12,7 @@ import "../access/roles/PauserRole.sol";
  * the functions of your contract. Note that they will not be pausable by
  * simply including this module, only once the modifiers are put in place.
  */
-contract Pausable is PauserRole {
+contract Pausable is Context, PauserRole {
     /**
      * @dev Emitted when the pause is triggered by a pauser (`account`).
      */
@@ -60,7 +61,7 @@ contract Pausable is PauserRole {
      */
     function pause() public onlyPauser whenNotPaused {
         _paused = true;
-        emit Paused(msg.sender);
+        emit Paused(_msgSender());
     }
 
     /**
@@ -68,6 +69,6 @@ contract Pausable is PauserRole {
      */
     function unpause() public onlyPauser whenPaused {
         _paused = false;
-        emit Unpaused(msg.sender);
+        emit Unpaused(_msgSender());
     }
 }

+ 18 - 0
contracts/mocks/ERC721GSNRecipientMock.sol

@@ -0,0 +1,18 @@
+pragma solidity ^0.5.0;
+
+import "../token/ERC721/ERC721.sol";
+import "../GSN/GSNRecipient.sol";
+import "../GSN/bouncers/GSNBouncerSignature.sol";
+
+/**
+ * @title ERC721GSNRecipientMock
+ * A simple ERC721 mock that has GSN support enabled
+ */
+contract ERC721GSNRecipientMock is ERC721, GSNRecipient, GSNBouncerSignature {
+    constructor(address trustedSigner) public GSNBouncerSignature(trustedSigner) { }
+    // solhint-disable-previous-line no-empty-blocks
+
+    function mint(uint256 tokenId) public {
+        _mint(_msgSender(), tokenId);
+    }
+}

+ 3 - 2
contracts/mocks/ERC777Mock.sol

@@ -1,8 +1,9 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 import "../token/ERC777/ERC777.sol";
 
-contract ERC777Mock is ERC777 {
+contract ERC777Mock is Context, ERC777 {
     constructor(
         address initialHolder,
         uint256 initialBalance,
@@ -10,7 +11,7 @@ contract ERC777Mock is ERC777 {
         string memory symbol,
         address[] memory defaultOperators
     ) public ERC777(name, symbol, defaultOperators) {
-        _mint(msg.sender, initialHolder, initialBalance, "", "");
+        _mint(_msgSender(), initialHolder, initialBalance, "", "");
     }
 
     function mintInternal (

+ 4 - 3
contracts/mocks/ERC777SenderRecipientMock.sol

@@ -1,12 +1,13 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 import "../token/ERC777/IERC777.sol";
 import "../token/ERC777/IERC777Sender.sol";
 import "../token/ERC777/IERC777Recipient.sol";
 import "../introspection/IERC1820Registry.sol";
 import "../introspection/ERC1820Implementer.sol";
 
-contract ERC777SenderRecipientMock is IERC777Sender, IERC777Recipient, ERC1820Implementer {
+contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, ERC1820Implementer {
     event TokensToSendCalled(
         address operator,
         address from,
@@ -51,7 +52,7 @@ contract ERC777SenderRecipientMock is IERC777Sender, IERC777Recipient, ERC1820Im
             revert();
         }
 
-        IERC777 token = IERC777(msg.sender);
+        IERC777 token = IERC777(_msgSender());
 
         uint256 fromBalance = token.balanceOf(from);
         // when called due to burn, to will be the zero address, which will have a balance of 0
@@ -82,7 +83,7 @@ contract ERC777SenderRecipientMock is IERC777Sender, IERC777Recipient, ERC1820Im
             revert();
         }
 
-        IERC777 token = IERC777(msg.sender);
+        IERC777 token = IERC777(_msgSender());
 
         uint256 fromBalance = token.balanceOf(from);
         // when called due to burn, to will be the zero address, which will have a balance of 0

+ 3 - 2
contracts/mocks/ReentrancyAttack.sol

@@ -1,9 +1,10 @@
 pragma solidity ^0.5.0;
 
-contract ReentrancyAttack {
+import "../GSN/Context.sol";
+contract ReentrancyAttack is Context {
     function callSender(bytes4 data) public {
         // solhint-disable-next-line avoid-low-level-calls
-        (bool success,) = msg.sender.call(abi.encodeWithSelector(data));
+        (bool success,) = _msgSender().call(abi.encodeWithSelector(data));
         require(success, "ReentrancyAttack: failed call");
     }
 }

+ 7 - 6
contracts/mocks/SafeERC20Helper.sol

@@ -1,9 +1,10 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 import "../token/ERC20/IERC20.sol";
 import "../token/ERC20/SafeERC20.sol";
 
-contract ERC20ReturnFalseMock {
+contract ERC20ReturnFalseMock is Context {
     uint256 private _allowance;
 
     // IERC20's functions are not pure, but these mock implementations are: to prevent Solidity from issuing warnings,
@@ -31,7 +32,7 @@ contract ERC20ReturnFalseMock {
     }
 }
 
-contract ERC20ReturnTrueMock {
+contract ERC20ReturnTrueMock is Context {
     mapping (address => uint256) private _allowances;
 
     // IERC20's functions are not pure, but these mock implementations are: to prevent Solidity from issuing warnings,
@@ -54,7 +55,7 @@ contract ERC20ReturnTrueMock {
     }
 
     function setAllowance(uint256 allowance_) public {
-        _allowances[msg.sender] = allowance_;
+        _allowances[_msgSender()] = allowance_;
     }
 
     function allowance(address owner, address) public view returns (uint256) {
@@ -62,7 +63,7 @@ contract ERC20ReturnTrueMock {
     }
 }
 
-contract ERC20NoReturnMock {
+contract ERC20NoReturnMock is Context {
     mapping (address => uint256) private _allowances;
 
     // IERC20's functions are not pure, but these mock implementations are: to prevent Solidity from issuing warnings,
@@ -82,7 +83,7 @@ contract ERC20NoReturnMock {
     }
 
     function setAllowance(uint256 allowance_) public {
-        _allowances[msg.sender] = allowance_;
+        _allowances[_msgSender()] = allowance_;
     }
 
     function allowance(address owner, address) public view returns (uint256) {
@@ -90,7 +91,7 @@ contract ERC20NoReturnMock {
     }
 }
 
-contract SafeERC20Wrapper {
+contract SafeERC20Wrapper is Context {
     using SafeERC20 for IERC20;
 
     IERC20 private _token;

+ 4 - 3
contracts/ownership/Ownable.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 /**
  * @dev Contract module which provides a basic access control mechanism, where
  * there is an account (an owner) that can be granted exclusive access to
@@ -9,7 +10,7 @@ pragma solidity ^0.5.0;
  * `onlyOwner`, which can be applied to your functions to restrict their use to
  * the owner.
  */
-contract Ownable {
+contract Ownable is Context {
     address private _owner;
 
     event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
@@ -18,7 +19,7 @@ contract Ownable {
      * @dev Initializes the contract setting the deployer as the initial owner.
      */
     constructor () internal {
-        _owner = msg.sender;
+        _owner = _msgSender();
         emit OwnershipTransferred(address(0), _owner);
     }
 
@@ -41,7 +42,7 @@ contract Ownable {
      * @dev Returns true if the caller is the current owner.
      */
     function isOwner() public view returns (bool) {
-        return msg.sender == _owner;
+        return _msgSender() == _owner;
     }
 
     /**

+ 4 - 3
contracts/ownership/Secondary.sol

@@ -1,9 +1,10 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 /**
  * @dev A Secondary contract can only be used by its primary account (the one that created it).
  */
-contract Secondary {
+contract Secondary is Context {
     address private _primary;
 
     /**
@@ -17,7 +18,7 @@ contract Secondary {
      * @dev Sets the primary account to the one that is creating the Secondary contract.
      */
     constructor () internal {
-        _primary = msg.sender;
+        _primary = _msgSender();
         emit PrimaryTransferred(_primary);
     }
 
@@ -25,7 +26,7 @@ contract Secondary {
      * @dev Reverts if called from any account other than the primary.
      */
     modifier onlyPrimary() {
-        require(msg.sender == _primary, "Secondary: caller is not the primary account");
+        require(_msgSender() == _primary, "Secondary: caller is not the primary account");
         _;
     }
 

+ 3 - 2
contracts/payment/PaymentSplitter.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../GSN/Context.sol";
 import "../math/SafeMath.sol";
 
 /**
@@ -15,7 +16,7 @@ import "../math/SafeMath.sol";
  * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}
  * function.
  */
-contract PaymentSplitter {
+contract PaymentSplitter is Context {
     using SafeMath for uint256;
 
     event PayeeAdded(address account, uint256 shares);
@@ -56,7 +57,7 @@ contract PaymentSplitter {
      * functions].
      */
     function () external payable {
-        emit PaymentReceived(msg.sender, msg.value);
+        emit PaymentReceived(_msgSender(), msg.value);
     }
 
     /**

+ 8 - 7
contracts/token/ERC20/ERC20.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "./IERC20.sol";
 import "../../math/SafeMath.sol";
 
@@ -27,7 +28,7 @@ import "../../math/SafeMath.sol";
  * functions have been added to mitigate the well-known issues around setting
  * allowances. See {IERC20-approve}.
  */
-contract ERC20 is IERC20 {
+contract ERC20 is Context, IERC20 {
     using SafeMath for uint256;
 
     mapping (address => uint256) private _balances;
@@ -59,7 +60,7 @@ contract ERC20 is IERC20 {
      * - the caller must have a balance of at least `amount`.
      */
     function transfer(address recipient, uint256 amount) public returns (bool) {
-        _transfer(msg.sender, recipient, amount);
+        _transfer(_msgSender(), recipient, amount);
         return true;
     }
 
@@ -78,7 +79,7 @@ contract ERC20 is IERC20 {
      * - `spender` cannot be the zero address.
      */
     function approve(address spender, uint256 value) public returns (bool) {
-        _approve(msg.sender, spender, value);
+        _approve(_msgSender(), spender, value);
         return true;
     }
 
@@ -96,7 +97,7 @@ contract ERC20 is IERC20 {
      */
     function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
         _transfer(sender, recipient, amount);
-        _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance"));
+        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
         return true;
     }
 
@@ -113,7 +114,7 @@ contract ERC20 is IERC20 {
      * - `spender` cannot be the zero address.
      */
     function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
-        _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
+        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
         return true;
     }
 
@@ -132,7 +133,7 @@ contract ERC20 is IERC20 {
      * `subtractedValue`.
      */
     function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
-        _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
+        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
         return true;
     }
 
@@ -224,6 +225,6 @@ contract ERC20 is IERC20 {
      */
     function _burnFrom(address account, uint256 amount) internal {
         _burn(account, amount);
-        _approve(account, msg.sender, _allowances[account][msg.sender].sub(amount, "ERC20: burn amount exceeds allowance"));
+        _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
     }
 }

+ 3 - 2
contracts/token/ERC20/ERC20Burnable.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "./ERC20.sol";
 
 /**
@@ -7,14 +8,14 @@ import "./ERC20.sol";
  * tokens and those that they have an allowance for, in a way that can be
  * recognized off-chain (via event analysis).
  */
-contract ERC20Burnable is ERC20 {
+contract ERC20Burnable is Context, ERC20 {
     /**
      * @dev Destroys `amount` tokens from the caller.
      *
      * See {ERC20-_burn}.
      */
     function burn(uint256 amount) public {
-        _burn(msg.sender, amount);
+        _burn(_msgSender(), amount);
     }
 
     /**

+ 10 - 9
contracts/token/ERC721/ERC721.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "./IERC721.sol";
 import "./IERC721Receiver.sol";
 import "../../math/SafeMath.sol";
@@ -11,7 +12,7 @@ import "../../introspection/ERC165.sol";
  * @title ERC721 Non-Fungible Token Standard basic implementation
  * @dev see https://eips.ethereum.org/EIPS/eip-721
  */
-contract ERC721 is ERC165, IERC721 {
+contract ERC721 is Context, ERC165, IERC721 {
     using SafeMath for uint256;
     using Address for address;
     using Counters for Counters.Counter;
@@ -88,7 +89,7 @@ contract ERC721 is ERC165, IERC721 {
         address owner = ownerOf(tokenId);
         require(to != owner, "ERC721: approval to current owner");
 
-        require(msg.sender == owner || isApprovedForAll(owner, msg.sender),
+        require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
             "ERC721: approve caller is not owner nor approved for all"
         );
 
@@ -115,10 +116,10 @@ contract ERC721 is ERC165, IERC721 {
      * @param approved representing the status of the approval to be set
      */
     function setApprovalForAll(address to, bool approved) public {
-        require(to != msg.sender, "ERC721: approve to caller");
+        require(to != _msgSender(), "ERC721: approve to caller");
 
-        _operatorApprovals[msg.sender][to] = approved;
-        emit ApprovalForAll(msg.sender, to, approved);
+        _operatorApprovals[_msgSender()][to] = approved;
+        emit ApprovalForAll(_msgSender(), to, approved);
     }
 
     /**
@@ -141,7 +142,7 @@ contract ERC721 is ERC165, IERC721 {
      */
     function transferFrom(address from, address to, uint256 tokenId) public {
         //solhint-disable-next-line max-line-length
-        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
+        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
 
         _transferFrom(from, to, tokenId);
     }
@@ -167,14 +168,14 @@ contract ERC721 is ERC165, IERC721 {
      * which is called upon a safe transfer, and return the magic value
      * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
      * the transfer is reverted.
-     * Requires the msg.sender to be the owner, approved, or operator
+     * Requires the _msgSender() to be the owner, approved, or operator
      * @param from current owner of the token
      * @param to address to receive the ownership of the given token ID
      * @param tokenId uint256 ID of the token to be transferred
      * @param _data bytes data to send along with a safe transfer check
      */
     function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
-        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
+        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
         _safeTransferFrom(from, to, tokenId, _data);
     }
 
@@ -330,7 +331,7 @@ contract ERC721 is ERC165, IERC721 {
             return true;
         }
 
-        bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data);
+        bytes4 retval = IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data);
         return (retval == _ERC721_RECEIVED);
     }
 

+ 3 - 2
contracts/token/ERC721/ERC721Burnable.sol

@@ -1,19 +1,20 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "./ERC721.sol";
 
 /**
  * @title ERC721 Burnable Token
  * @dev ERC721 Token that can be irreversibly burned (destroyed).
  */
-contract ERC721Burnable is ERC721 {
+contract ERC721Burnable is Context, ERC721 {
     /**
      * @dev Burns a specific ERC721 token.
      * @param tokenId uint256 id of the ERC721 token to be burned.
      */
     function burn(uint256 tokenId) public {
         //solhint-disable-next-line max-line-length
-        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721Burnable: caller is not owner nor approved");
+        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Burnable: caller is not owner nor approved");
         _burn(tokenId);
     }
 }

+ 2 - 1
contracts/token/ERC721/ERC721Enumerable.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "./IERC721Enumerable.sol";
 import "./ERC721.sol";
 import "../../introspection/ERC165.sol";
@@ -8,7 +9,7 @@ import "../../introspection/ERC165.sol";
  * @title ERC-721 Non-Fungible Token with optional enumeration extension logic
  * @dev See https://eips.ethereum.org/EIPS/eip-721
  */
-contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
+contract ERC721Enumerable is Context, ERC165, ERC721, IERC721Enumerable {
     // Mapping from owner to list of owned token IDs
     mapping(address => uint256[]) private _ownedTokens;
 

+ 2 - 1
contracts/token/ERC721/ERC721Metadata.sol

@@ -1,10 +1,11 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "./ERC721.sol";
 import "./IERC721Metadata.sol";
 import "../../introspection/ERC165.sol";
 
-contract ERC721Metadata is ERC165, ERC721, IERC721Metadata {
+contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata {
     // Token name
     string private _name;
 

+ 19 - 18
contracts/token/ERC777/ERC777.sol

@@ -1,5 +1,6 @@
 pragma solidity ^0.5.0;
 
+import "../../GSN/Context.sol";
 import "./IERC777.sol";
 import "./IERC777Recipient.sol";
 import "./IERC777Sender.sol";
@@ -23,7 +24,7 @@ import "../../introspection/IERC1820Registry.sol";
  * are no special restrictions in the amount of tokens that created, moved, or
  * destroyed. This makes integration with ERC20 applications seamless.
  */
-contract ERC777 is IERC777, IERC20 {
+contract ERC777 is Context, IERC777, IERC20 {
     using SafeMath for uint256;
     using Address for address;
 
@@ -134,7 +135,7 @@ contract ERC777 is IERC777, IERC20 {
      * Also emits a {Transfer} event for ERC20 compatibility.
      */
     function send(address recipient, uint256 amount, bytes calldata data) external {
-        _send(msg.sender, msg.sender, recipient, amount, data, "", true);
+        _send(_msgSender(), _msgSender(), recipient, amount, data, "", true);
     }
 
     /**
@@ -148,7 +149,7 @@ contract ERC777 is IERC777, IERC20 {
     function transfer(address recipient, uint256 amount) external returns (bool) {
         require(recipient != address(0), "ERC777: transfer to the zero address");
 
-        address from = msg.sender;
+        address from = _msgSender();
 
         _callTokensToSend(from, from, recipient, amount, "", "");
 
@@ -165,7 +166,7 @@ contract ERC777 is IERC777, IERC20 {
      * Also emits a {Transfer} event for ERC20 compatibility.
      */
     function burn(uint256 amount, bytes calldata data) external {
-        _burn(msg.sender, msg.sender, amount, data, "");
+        _burn(_msgSender(), _msgSender(), amount, data, "");
     }
 
     /**
@@ -184,30 +185,30 @@ contract ERC777 is IERC777, IERC20 {
      * @dev See {IERC777-authorizeOperator}.
      */
     function authorizeOperator(address operator) external {
-        require(msg.sender != operator, "ERC777: authorizing self as operator");
+        require(_msgSender() != operator, "ERC777: authorizing self as operator");
 
         if (_defaultOperators[operator]) {
-            delete _revokedDefaultOperators[msg.sender][operator];
+            delete _revokedDefaultOperators[_msgSender()][operator];
         } else {
-            _operators[msg.sender][operator] = true;
+            _operators[_msgSender()][operator] = true;
         }
 
-        emit AuthorizedOperator(operator, msg.sender);
+        emit AuthorizedOperator(operator, _msgSender());
     }
 
     /**
      * @dev See {IERC777-revokeOperator}.
      */
     function revokeOperator(address operator) external {
-        require(operator != msg.sender, "ERC777: revoking self as operator");
+        require(operator != _msgSender(), "ERC777: revoking self as operator");
 
         if (_defaultOperators[operator]) {
-            _revokedDefaultOperators[msg.sender][operator] = true;
+            _revokedDefaultOperators[_msgSender()][operator] = true;
         } else {
-            delete _operators[msg.sender][operator];
+            delete _operators[_msgSender()][operator];
         }
 
-        emit RevokedOperator(operator, msg.sender);
+        emit RevokedOperator(operator, _msgSender());
     }
 
     /**
@@ -231,8 +232,8 @@ contract ERC777 is IERC777, IERC20 {
     )
     external
     {
-        require(isOperatorFor(msg.sender, sender), "ERC777: caller is not an operator for holder");
-        _send(msg.sender, sender, recipient, amount, data, operatorData, true);
+        require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder");
+        _send(_msgSender(), sender, recipient, amount, data, operatorData, true);
     }
 
     /**
@@ -241,8 +242,8 @@ contract ERC777 is IERC777, IERC20 {
      * Emits {Burned} and {Transfer} events.
      */
     function operatorBurn(address account, uint256 amount, bytes calldata data, bytes calldata operatorData) external {
-        require(isOperatorFor(msg.sender, account), "ERC777: caller is not an operator for holder");
-        _burn(msg.sender, account, amount, data, operatorData);
+        require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder");
+        _burn(_msgSender(), account, amount, data, operatorData);
     }
 
     /**
@@ -262,7 +263,7 @@ contract ERC777 is IERC777, IERC20 {
      * Note that accounts cannot have allowance issued by their operators.
      */
     function approve(address spender, uint256 value) external returns (bool) {
-        address holder = msg.sender;
+        address holder = _msgSender();
         _approve(holder, spender, value);
         return true;
     }
@@ -280,7 +281,7 @@ contract ERC777 is IERC777, IERC20 {
         require(recipient != address(0), "ERC777: transfer to the zero address");
         require(holder != address(0), "ERC777: transfer from the zero address");
 
-        address spender = msg.sender;
+        address spender = _msgSender();
 
         _callTokensToSend(spender, holder, recipient, amount, "", "");
 

+ 449 - 4
package-lock.json

@@ -434,30 +434,469 @@
       }
     },
     "@openzeppelin/gsn-helpers": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/@openzeppelin/gsn-helpers/-/gsn-helpers-0.1.4.tgz",
-      "integrity": "sha512-RELmLAI5FZMkmszBcryBLAvgi6YRTKjOyeLVZVNP+Cg0ZqfDGSGKG85yuplMgRyMtqh5wPDf5Ah5YiCFjrjVfw==",
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/@openzeppelin/gsn-helpers/-/gsn-helpers-0.1.5.tgz",
+      "integrity": "sha512-KxrTHB36KRPPZs6DiVy4RxbZ39BfEFm+8ZicC51o1rJRHCV5kyjZ8uPa32oXsHt2Bl2Ve7iw1I26xwn/QClEzQ==",
       "dev": true,
       "requires": {
         "axios": "^0.19.0",
         "chai": "^4.2.0",
         "commander": "^2.20.0",
+        "env-paths": "^2.2.0",
+        "fs-extra": "^8.1.0",
         "lodash": "^4.17.15",
         "sleep-promise": "^8.0.1",
-        "web3": "^1.2.0"
+        "tmp": "^0.1.0",
+        "web3": "^1.2.1"
       },
       "dependencies": {
+        "@types/node": {
+          "version": "10.14.15",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.15.tgz",
+          "integrity": "sha512-CBR5avlLcu0YCILJiDIXeU2pTw7UK/NIxfC63m7d7CVamho1qDEzXKkOtEauQRPMy6MI8mLozth+JJkas7HY6g==",
+          "dev": true
+        },
         "commander": {
           "version": "2.20.0",
           "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
           "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
           "dev": true
         },
+        "eth-lib": {
+          "version": "0.2.7",
+          "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz",
+          "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=",
+          "dev": true,
+          "requires": {
+            "bn.js": "^4.11.6",
+            "elliptic": "^6.4.0",
+            "xhr-request-promise": "^0.1.2"
+          }
+        },
+        "ethers": {
+          "version": "4.0.0-beta.3",
+          "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.0-beta.3.tgz",
+          "integrity": "sha512-YYPogooSknTwvHg3+Mv71gM/3Wcrx+ZpCzarBj3mqs9njjRkrOo2/eufzhHloOCo3JSoNI4TQJJ6yU5ABm3Uog==",
+          "dev": true,
+          "requires": {
+            "@types/node": "^10.3.2",
+            "aes-js": "3.0.0",
+            "bn.js": "^4.4.0",
+            "elliptic": "6.3.3",
+            "hash.js": "1.1.3",
+            "js-sha3": "0.5.7",
+            "scrypt-js": "2.0.3",
+            "setimmediate": "1.0.4",
+            "uuid": "2.0.1",
+            "xmlhttprequest": "1.8.0"
+          },
+          "dependencies": {
+            "elliptic": {
+              "version": "6.3.3",
+              "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz",
+              "integrity": "sha1-VILZZG1UvLif19mU/J4ulWiHbj8=",
+              "dev": true,
+              "requires": {
+                "bn.js": "^4.4.0",
+                "brorand": "^1.0.1",
+                "hash.js": "^1.0.0",
+                "inherits": "^2.0.1"
+              }
+            }
+          }
+        },
+        "fs-extra": {
+          "version": "8.1.0",
+          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+          "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.2.0",
+            "jsonfile": "^4.0.0",
+            "universalify": "^0.1.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+          "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "graceful-fs": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz",
+          "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==",
+          "dev": true
+        },
+        "js-sha3": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz",
+          "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=",
+          "dev": true
+        },
+        "jsonfile": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+          "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.6"
+          }
+        },
         "lodash": {
           "version": "4.17.15",
           "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
           "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
           "dev": true
+        },
+        "nan": {
+          "version": "2.14.0",
+          "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+          "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
+          "dev": true
+        },
+        "rimraf": {
+          "version": "2.6.3",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+          "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+          "dev": true,
+          "requires": {
+            "glob": "^7.1.3"
+          }
+        },
+        "scrypt-js": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.3.tgz",
+          "integrity": "sha1-uwBAvgMEPamgEqLOqfyfhSz8h9Q=",
+          "dev": true
+        },
+        "scryptsy": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz",
+          "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==",
+          "dev": true
+        },
+        "semver": {
+          "version": "6.2.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz",
+          "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==",
+          "dev": true
+        },
+        "tmp": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
+          "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+          "dev": true,
+          "requires": {
+            "rimraf": "^2.6.3"
+          }
+        },
+        "uuid": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz",
+          "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=",
+          "dev": true
+        },
+        "web3": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.1.tgz",
+          "integrity": "sha512-nNMzeCK0agb5i/oTWNdQ1aGtwYfXzHottFP2Dz0oGIzavPMGSKyVlr8ibVb1yK5sJBjrWVnTdGaOC2zKDFuFRw==",
+          "dev": true,
+          "requires": {
+            "web3-bzz": "1.2.1",
+            "web3-core": "1.2.1",
+            "web3-eth": "1.2.1",
+            "web3-eth-personal": "1.2.1",
+            "web3-net": "1.2.1",
+            "web3-shh": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-bzz": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.1.tgz",
+          "integrity": "sha512-LdOO44TuYbGIPfL4ilkuS89GQovxUpmLz6C1UC7VYVVRILeZS740FVB3j9V4P4FHUk1RenaDfKhcntqgVCHtjw==",
+          "dev": true,
+          "requires": {
+            "got": "9.6.0",
+            "swarm-js": "0.1.39",
+            "underscore": "1.9.1"
+          }
+        },
+        "web3-core": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.1.tgz",
+          "integrity": "sha512-5ODwIqgl8oIg/0+Ai4jsLxkKFWJYE0uLuE1yUKHNVCL4zL6n3rFjRMpKPokd6id6nJCNgeA64KdWQ4XfpnjdMg==",
+          "dev": true,
+          "requires": {
+            "web3-core-helpers": "1.2.1",
+            "web3-core-method": "1.2.1",
+            "web3-core-requestmanager": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-core-helpers": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.1.tgz",
+          "integrity": "sha512-Gx3sTEajD5r96bJgfuW377PZVFmXIH4TdqDhgGwd2lZQCcMi+DA4TgxJNJGxn0R3aUVzyyE76j4LBrh412mXrw==",
+          "dev": true,
+          "requires": {
+            "underscore": "1.9.1",
+            "web3-eth-iban": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-core-method": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.1.tgz",
+          "integrity": "sha512-Ghg2WS23qi6Xj8Od3VCzaImLHseEA7/usvnOItluiIc5cKs00WYWsNy2YRStzU9a2+z8lwQywPYp0nTzR/QXdQ==",
+          "dev": true,
+          "requires": {
+            "underscore": "1.9.1",
+            "web3-core-helpers": "1.2.1",
+            "web3-core-promievent": "1.2.1",
+            "web3-core-subscriptions": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-core-promievent": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.1.tgz",
+          "integrity": "sha512-IVUqgpIKoeOYblwpex4Hye6npM0aMR+kU49VP06secPeN0rHMyhGF0ZGveWBrGvf8WDPI7jhqPBFIC6Jf3Q3zw==",
+          "dev": true,
+          "requires": {
+            "any-promise": "1.3.0",
+            "eventemitter3": "3.1.2"
+          }
+        },
+        "web3-core-requestmanager": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.1.tgz",
+          "integrity": "sha512-xfknTC69RfYmLKC+83Jz73IC3/sS2ZLhGtX33D4Q5nQ8yc39ElyAolxr9sJQS8kihOcM6u4J+8gyGMqsLcpIBg==",
+          "dev": true,
+          "requires": {
+            "underscore": "1.9.1",
+            "web3-core-helpers": "1.2.1",
+            "web3-providers-http": "1.2.1",
+            "web3-providers-ipc": "1.2.1",
+            "web3-providers-ws": "1.2.1"
+          }
+        },
+        "web3-core-subscriptions": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.1.tgz",
+          "integrity": "sha512-nmOwe3NsB8V8UFsY1r+sW6KjdOS68h8nuh7NzlWxBQT/19QSUGiERRTaZXWu5BYvo1EoZRMxCKyCQpSSXLc08g==",
+          "dev": true,
+          "requires": {
+            "eventemitter3": "3.1.2",
+            "underscore": "1.9.1",
+            "web3-core-helpers": "1.2.1"
+          }
+        },
+        "web3-eth": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.1.tgz",
+          "integrity": "sha512-/2xly4Yry5FW1i+uygPjhfvgUP/MS/Dk+PDqmzp5M88tS86A+j8BzKc23GrlA8sgGs0645cpZK/999LpEF5UdA==",
+          "dev": true,
+          "requires": {
+            "underscore": "1.9.1",
+            "web3-core": "1.2.1",
+            "web3-core-helpers": "1.2.1",
+            "web3-core-method": "1.2.1",
+            "web3-core-subscriptions": "1.2.1",
+            "web3-eth-abi": "1.2.1",
+            "web3-eth-accounts": "1.2.1",
+            "web3-eth-contract": "1.2.1",
+            "web3-eth-ens": "1.2.1",
+            "web3-eth-iban": "1.2.1",
+            "web3-eth-personal": "1.2.1",
+            "web3-net": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-eth-abi": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.2.1.tgz",
+          "integrity": "sha512-jI/KhU2a/DQPZXHjo2GW0myEljzfiKOn+h1qxK1+Y9OQfTcBMxrQJyH5AP89O6l6NZ1QvNdq99ThAxBFoy5L+g==",
+          "dev": true,
+          "requires": {
+            "ethers": "4.0.0-beta.3",
+            "underscore": "1.9.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-eth-accounts": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.1.tgz",
+          "integrity": "sha512-26I4qq42STQ8IeKUyur3MdQ1NzrzCqPsmzqpux0j6X/XBD7EjZ+Cs0lhGNkSKH5dI3V8CJasnQ5T1mNKeWB7nQ==",
+          "dev": true,
+          "requires": {
+            "any-promise": "1.3.0",
+            "crypto-browserify": "3.12.0",
+            "eth-lib": "0.2.7",
+            "scryptsy": "2.1.0",
+            "semver": "6.2.0",
+            "underscore": "1.9.1",
+            "uuid": "3.3.2",
+            "web3-core": "1.2.1",
+            "web3-core-helpers": "1.2.1",
+            "web3-core-method": "1.2.1",
+            "web3-utils": "1.2.1"
+          },
+          "dependencies": {
+            "uuid": {
+              "version": "3.3.2",
+              "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+              "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
+              "dev": true
+            }
+          }
+        },
+        "web3-eth-contract": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.1.tgz",
+          "integrity": "sha512-kYFESbQ3boC9bl2rYVghj7O8UKMiuKaiMkxvRH5cEDHil8V7MGEGZNH0slSdoyeftZVlaWSMqkRP/chfnKND0g==",
+          "dev": true,
+          "requires": {
+            "underscore": "1.9.1",
+            "web3-core": "1.2.1",
+            "web3-core-helpers": "1.2.1",
+            "web3-core-method": "1.2.1",
+            "web3-core-promievent": "1.2.1",
+            "web3-core-subscriptions": "1.2.1",
+            "web3-eth-abi": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-eth-ens": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.1.tgz",
+          "integrity": "sha512-lhP1kFhqZr2nnbu3CGIFFrAnNxk2veXpOXBY48Tub37RtobDyHijHgrj+xTh+mFiPokyrapVjpFsbGa+Xzye4Q==",
+          "dev": true,
+          "requires": {
+            "eth-ens-namehash": "2.0.8",
+            "underscore": "1.9.1",
+            "web3-core": "1.2.1",
+            "web3-core-helpers": "1.2.1",
+            "web3-core-promievent": "1.2.1",
+            "web3-eth-abi": "1.2.1",
+            "web3-eth-contract": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-eth-iban": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.1.tgz",
+          "integrity": "sha512-9gkr4QPl1jCU+wkgmZ8EwODVO3ovVj6d6JKMos52ggdT2YCmlfvFVF6wlGLwi0VvNa/p+0BjJzaqxnnG/JewjQ==",
+          "dev": true,
+          "requires": {
+            "bn.js": "4.11.8",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-eth-personal": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.1.tgz",
+          "integrity": "sha512-RNDVSiaSoY4aIp8+Hc7z+X72H7lMb3fmAChuSBADoEc7DsJrY/d0R5qQDK9g9t2BO8oxgLrLNyBP/9ub2Hc6Bg==",
+          "dev": true,
+          "requires": {
+            "web3-core": "1.2.1",
+            "web3-core-helpers": "1.2.1",
+            "web3-core-method": "1.2.1",
+            "web3-net": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-net": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.1.tgz",
+          "integrity": "sha512-Yt1Bs7WgnLESPe0rri/ZoPWzSy55ovioaP35w1KZydrNtQ5Yq4WcrAdhBzcOW7vAkIwrsLQsvA+hrOCy7mNauw==",
+          "dev": true,
+          "requires": {
+            "web3-core": "1.2.1",
+            "web3-core-method": "1.2.1",
+            "web3-utils": "1.2.1"
+          }
+        },
+        "web3-providers-http": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.1.tgz",
+          "integrity": "sha512-BDtVUVolT9b3CAzeGVA/np1hhn7RPUZ6YYGB/sYky+GjeO311Yoq8SRDUSezU92x8yImSC2B+SMReGhd1zL+bQ==",
+          "dev": true,
+          "requires": {
+            "web3-core-helpers": "1.2.1",
+            "xhr2-cookies": "1.1.0"
+          }
+        },
+        "web3-providers-ipc": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.1.tgz",
+          "integrity": "sha512-oPEuOCwxVx8L4CPD0TUdnlOUZwGBSRKScCz/Ws2YHdr9Ium+whm+0NLmOZjkjQp5wovQbyBzNa6zJz1noFRvFA==",
+          "dev": true,
+          "requires": {
+            "oboe": "2.1.4",
+            "underscore": "1.9.1",
+            "web3-core-helpers": "1.2.1"
+          }
+        },
+        "web3-providers-ws": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.1.tgz",
+          "integrity": "sha512-oqsQXzu+ejJACVHy864WwIyw+oB21nw/pI65/sD95Zi98+/HQzFfNcIFneF1NC4bVF3VNX4YHTNq2I2o97LAiA==",
+          "dev": true,
+          "requires": {
+            "underscore": "1.9.1",
+            "web3-core-helpers": "1.2.1",
+            "websocket": "github:web3-js/WebSocket-Node#polyfill/globalThis"
+          }
+        },
+        "web3-shh": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.1.tgz",
+          "integrity": "sha512-/3Cl04nza5kuFn25bV3FJWa0s3Vafr5BlT933h26xovQ6HIIz61LmvNQlvX1AhFL+SNJOTcQmK1SM59vcyC8bA==",
+          "dev": true,
+          "requires": {
+            "web3-core": "1.2.1",
+            "web3-core-method": "1.2.1",
+            "web3-core-subscriptions": "1.2.1",
+            "web3-net": "1.2.1"
+          }
+        },
+        "web3-utils": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.1.tgz",
+          "integrity": "sha512-Mrcn3l58L+yCKz3zBryM6JZpNruWuT0OCbag8w+reeNROSGVlXzUQkU+gtAwc9JCZ7tKUyg67+2YUGqUjVcyBA==",
+          "dev": true,
+          "requires": {
+            "bn.js": "4.11.8",
+            "eth-lib": "0.2.7",
+            "ethjs-unit": "0.1.6",
+            "number-to-bn": "1.7.0",
+            "randomhex": "0.1.5",
+            "underscore": "1.9.1",
+            "utf8": "3.0.0"
+          }
+        },
+        "websocket": {
+          "version": "github:web3-js/WebSocket-Node#b134a75541b5db59668df81c03e926cd5f325077",
+          "from": "github:web3-js/WebSocket-Node#polyfill/globalThis",
+          "dev": true,
+          "requires": {
+            "debug": "^2.2.0",
+            "es5-ext": "^0.10.50",
+            "gulp": "^4.0.2",
+            "nan": "^2.14.0",
+            "typedarray-to-buffer": "^3.1.5",
+            "yaeti": "^0.0.6"
+          }
         }
       }
     },
@@ -3847,6 +4286,12 @@
         "once": "^1.4.0"
       }
     },
+    "env-paths": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz",
+      "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
+      "dev": true
+    },
     "error-ex": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",

+ 1 - 1
package.json

@@ -44,7 +44,7 @@
   },
   "homepage": "https://github.com/OpenZeppelin/openzeppelin-contracts",
   "devDependencies": {
-    "@openzeppelin/gsn-helpers": "^0.1.4",
+    "@openzeppelin/gsn-helpers": "^0.1.5",
     "@openzeppelin/gsn-provider": "^0.1.4",
     "chai": "^4.2.0",
     "concurrently": "^4.1.0",

+ 46 - 0
test/GSN/ERC721GSNRecipientMock.test.js

@@ -0,0 +1,46 @@
+const { constants, expectEvent } = require('openzeppelin-test-helpers');
+const { ZERO_ADDRESS } = constants;
+const gsn = require('@openzeppelin/gsn-helpers');
+const { fixSignature } = require('../helpers/sign');
+const { utils: { toBN } } = require('web3');
+
+const ERC721GSNRecipientMock = artifacts.require('ERC721GSNRecipientMock');
+
+contract('ERC721GSNRecipient (integration)', function ([_, signer, sender]) {
+  const tokenId = '42';
+
+  beforeEach(async function () {
+    this.token = await ERC721GSNRecipientMock.new(signer);
+  });
+
+  async function testMintToken (token, from, tokenId, options = {}) {
+    const { tx } = await token.mint(tokenId, { from, ...options });
+    await expectEvent.inTransaction(tx, ERC721GSNRecipientMock, 'Transfer', { from: ZERO_ADDRESS, to: from, tokenId });
+  }
+
+  context('when called directly', function () {
+    it('sender can mint tokens', async function () {
+      await testMintToken(this.token, sender, tokenId);
+    });
+  });
+
+  context('when relay-called', function () {
+    beforeEach(async function () {
+      await gsn.fundRecipient(web3, { recipient: this.token.address });
+    });
+
+    it('sender can mint tokens', async function () {
+      const approveFunction = async (data) =>
+        fixSignature(
+          await web3.eth.sign(
+            web3.utils.soliditySha3(
+              // eslint-disable-next-line max-len
+              data.relayerAddress, data.from, data.encodedFunctionCall, toBN(data.txFee), toBN(data.gasPrice), toBN(data.gas), toBN(data.nonce), data.relayHubAddress, this.token.address
+            ), signer
+          )
+        );
+
+      await testMintToken(this.token, sender, tokenId, { useGSN: true, approveFunction });
+    });
+  });
+});