Do all contracts need to inherit GSN for gasless transactions?

Hello I’m trying to understand if I need to implement GSN contracts on all my contracts that are involved in the transaction. My current abbreviated implementation is outlined below.

contract Foo is Initializable, GSNRecipientSignature {
  MyToken private _token;
  address _signer;

  function initialize(address signer) {
    GSNRecipientSignature.initialize(signer);
    _signer = signer;
  }
  
  // This function gets called by our dapp
  function doSomething(to, amount) {
    _token.transfer(to, amount)
  }

  function acceptRelayedCall(...omitted) {
    // accept or reject
  }
}

contract MyToken is ERC20 {}

So based on my implementation, does MyToken have to implement a GSN contract also?

I do have a second question. Calling the ERC20 transfer function uses _msgSender() under the hood, is _msgSender() the contract Foo executing the transaction in the context of the doSomething() method?

Thanks!

1 Like

Hi @h00j,

Welcome to the community :wave:

Contracts being directly called by the GSN (Relayer) need to be GSN enabled.

In the example Foo, doSomething transfer's tokens held by the Foo contract to an address. So only Foo would need to be GSN enabled.

For the situation where an ERC20 token holder interacts with another contract using the token, two transactions are required:

  1. The token holder calls approve to set an allowance of tokens that the contract can use. (assuming an OpenZeppelin ERC20 implementation can use increaseAllowance)
  2. The token holder calls the contract to perform an action and the contract can transferFrom an amount of tokens within the set allowance.

For both these transactions to occur via the GSN then both contracts need to be GSN enabled.

In the example _msgSender() in MyToken would be Foo when doSomething() was called. Which means that Foo would need to be holding an amount of MyToken.

1 Like

Thank you! That clarifies a lot of things for me

1 Like

Hi @abcoathup

You mentioned

So if I do this

contract Foo is Initializable, GSNRecipient {
    ERC20 _token;
    address _signer;
 
    initialize(address signer, ERC20 token) {
      GSNRecipient.initialize();
      _signer = signer;
      _token = token;
    }

    function doSomething(address dappUser, unit256 amount) public {
      _token.approve(dappUser, _msgSender());
      _token.transferFrom(dappUser, _msgSender(), amount); 
    }
}
 
class MyToken is ERC20 {}

Then MyToken has to implement a GSN contract?

1 Like

@abcoathup After looking at the ERC20 code now, I didn’t notice that increaseAllowance calls _msgSender() and now I know that the contract is the sender in the context of the caller it makes sense to me now that the token would have to implement GSN. Thanks again!

1 Like

Hi @h00j,

Just to clarify, the token holder needs to call approve.

In your doSomething, Foo is calling approve to set an allowance for dappUser.

    function doSomething(address dappUser, unit256 amount) public {
      _token.approve(dappUser, _msgSender());
      _token.transferFrom(dappUser, _msgSender(), amount); 
    }

Let me know if you have any more questions.

1 Like

Ah, your correct.

I created a custom function on my token so that only my smart contract is allowed to approve itself

MyContract private _myContract; // using an onlyOwner method to set the myContract

function setMyContract(MyContract myContract) public onlyOwner {
  _myContract = myContract;
}

function contractApproval(address user, uint256 amount) public returns(bool) {
  // there is another contract I have where I whitelist my dapp users, but outside of the scope of this conversation
  require(_accessControl.isUserWhitelisted(user), "The user must be whitelisted.");
  // Using 0x contract-utils cause they are awesome!
  require(LibAddress.isContract(_msgSender()), "Recipient must be a contract.");
  require(_msgSender() == address(_myContract), "Only my contract is allowed to execute contractApproval.");
  // using the internal function _approve in the ERC20 contract so that I can set the contract as the spender
  _approve(user, _msgSender(), amount);
  return true;
}

I could potentially whitelist my contract address, which might be a good idea to :thinking:

1 Like