GSN: Only relay calls for specific functions

Hey everybody,

I’m just getting started with GSN and I’m stuck.

I have a slightly modified ERC20 contract and I only want to skip charging users ETH for gas for ONLY transfer & transferFrom.

All other transactions/should act as normal and should charge the caller the requisite ETH.

I have a few questions:

  1. Does extending GSNRecipient means that all function calls will automatically attempt ‘relaying’?

  2. Is it possible to get the function that is being relayed from the encodedFunction argument passed into the acceptRelayedCall callback? If yes, how do I get the function name? (This way I can check if it’s a transferFrom or transfer call and make a decision based on that)

  3. Even if 2. is possible, I still don’t know how to make sure all other calls are treated normally (i.e not relayed)

  4. Maybe I’m getting it wrong and the dApp doesn’t attempt to call my contract at all. I think I read something about the call to the relayer being something that happens offchain. Is this the case?

1 Like

Hi @timigod,

Welcome to the community forum :wave:. Thanks for posting your question here.

I was curious why you were only covering transfer and transferFrom. I would have thought that end users would be using transfer and either approve or increaseAllowance with users calling other contracts which would then would call transferFrom.

  1. Does extending GSNRecipient means that all function calls will automatically attempt ‘relaying’?

If your token inherits from GSNRecipient then all functions can be called via the GSN. Your contract should then decide which calls you are willing to pay for.

I recommend reading the Writing GSN-capable contracts documenation.

I also suggest having a look at GSN Bouncers documentation on ways to decide which relayed calls to accept.

GSNBouncerSignature appears to be a potential option for you.

  1. Is it possible to get the function that is being relayed from the encodedFunction argument passed into the acceptRelayedCall callback? If yes, how do I get the function name? (This way I can check if it’s a transferFrom or transfer call and make a decision based on that)

You can get the function name in acceptRelayedCall:

acceptRelayedCall
encodedFunction is the relayed call calldata, so its first four bytes are the function selector.

If you use GSNBouncerSignature you can decide whether to sign or not sign the relayed call parameters (including based on the encodedFunction.

There is a tutorial on using off-chain signing to approve relayed calls with: Advanced GSN: GSNRecipientSignature.sol

  1. Even if 2. is possible, I still don’t know how to make sure all other calls are treated normally (i.e not relayed)

Your contract (or off-chain signing) will need to use your logic to decide which relayed calls to accept. So you can reject relayed calls for any function that you don't want to pay for.

  1. Maybe I’m getting it wrong and the dApp doesn’t attempt to call my contract at all. I think I read something about the call to the relayer being something that happens offchain. Is this the case?

Your dapp can call a GSN relayer (for functions that it wants to use the GSN for) which will then relay the calls (that your contract accepts based on its business logic).

Your dapp call call a function using injected Web3 (e.g. MetaMask) for functions that you don't want to pay for.

If you haven't already, I recommend playing with the GSN Starter Kit

Feel free to ask all the questions that you need.

@abcoathup thanks for your response. I'm still unclear on some things.

I've done a bit more reading and you're right about this.

I'm not sure what this means. Does this mean:

  1. My contract will automatically attempt to relay all calls through the GSN or
  2. The caller has to specify in some way that they'd like to make the call via the GSN?

How do I get the first four bytes of the calldata? I can't seem to figure out how to do this but it's probably because I'm a n00b.

Doesn't rejecting a relayed call mean that the call will fail? I don't want other calls to fail. I just want them to operate as normal.

I did a bit more reading & testing out stuff for myself and I found the answer to the below question(s).

The caller does have to specify in some way that they'd like to make the call via GSN using

const context = useWeb3Network(PROVIDER_URL, {
    gsn: { signKey: useEphemeralKey() },
});

For normal calls, one just has to use the regular web3 provider/context.

The only thing I need to know now is how to get the function selector from the encodedFunction to approve or reject a relayed call.

1 Like

This will depend on the strategy you choose to accept or reject relayed calls. If you do off-chain signing with GSNBouncerSignature, like @abcoathup suggested, you could filter via selector on your signing server. You could also filter on-chain by adding this logic to acceptRelayedCall, something like this (pseudo-code):

function acceptRelayedCall(address, address, bytes calldata encodedFunction, uint256, uint256, uint256, uint256, bytes calldata approvalData, uint256) external view returns (uint256, bytes memory) {
    if (getSelector(encodedFunction) != approvedSelector) { // getSelector should extract the first 4 bytes
        return _rejectRelayedCall(uint256(GSNBouncerSignatureErrorCodes.INVALID_SIGNER));
    } else {
        return super.acceptRelayedCall(<params>);
    }
}

You can read more about acceptRelayedCall here.

1 Like

Thanks for your response.

I intend to filter on chain. The problem is with getSelector. I don’t know how to write the function that extracts the first 4 bytes.

1 Like

You can use 0x’s contracts-utils (https://www.npmjs.com/package/@0x/contracts-utils), LibBytes.readBytes4 does precisely that.

3 Likes