Extract out relay call function arguments in preRelayCall function

Is it possible to extract out the arguments passed to the calling function of recipient (the actual relay call function) in the preRelayCall function?
I believe we can do it using RelayRequest.request.data.
My relay call function is defined like this
function register(address _to, bytes32 _id, string memory _user).

And I want to extract out the _user parameter passed to the above function, cause my decision to pay gas fee is based on the _user and the maxPossibleGas value passed to the preRelayCall function. (Thus, I can’t just do rejectOnRecipientRevert = true and let the recipient decide whether to pay gas fee).

Hi @Alpha-Guy.
Yes, it is possible to parse the arguments of the request data manually as the ABI encoding is quite simple. We used to rely on this code to do it that uses LibBytes by 0x for cheaper bytes manipulation, so you can try it out:
(note that I pasted getParam here for reference and is actually available as GsnUtils.getParam)

 /**
  * extract parameter from encoded-function block.
  * see: https://solidity.readthedocs.io/en/develop/abi-spec.html#formal-specification-of-the-encoding
  */
 function getParam(bytes memory msgData, uint index) internal pure returns (uint) {
     return LibBytesV06.readUint256(msgData, 4 + index * 32);
 }
 
 /**	
  * extract dynamic-sized (string/bytes) parameter
  * see: https://solidity.readthedocs.io/en/develop/abi-spec.html#use-of-dynamic-types	
  */	
 function getBytesParam(bytes memory msgData, uint index) internal pure returns (bytes memory ret) {	
     uint ofs = getParam(msgData, index) + 4;	
     uint len = LibBytesV06.readUint256(msgData, ofs);	
     ret = LibBytesV06.slice(msgData, ofs + 32, ofs + 32 + len);	
 }

So, in order to get _user you would call it somewhat like

string _user = string(getBytesParam(relayRequest.request.data, 2))

Also, why is the decision based on both maxPossibleGas and _user? Does each user have different gas allowance? If not, then I guess you could have maxPossibleGas checked in the preRelayCall and later check the _user in the recipient.

Also, if there is no need for _user to be a dynamic-sized string and it can be compressed into a bytes32 this could make your paymaster a bit cheaper and simpler.

Also, why is the decision based on both maxPossibleGas and _user ? Does each user have different gas allowance?

Kind of… In our project we allow gasless transactions only if there is a deposit for the _user from which we can cover the gas cost. The project aims to be economically autonomous.

This is the exact separation we try to keep with GSN: the contract itself shouldn’t care about gas (as it doesn’t care about direct EVM calls)
The component that does care is a paymaster: This is what we’ve done in our TokenPaymaster: it performs token.transferFrom() call (to itself) before the relayed call, and then refund the caller after the call.
The the paymaster uses Uniswap to actually convert these tokens to ETH

The tricky part is that we need the user to approve() the paymaster to take from its tokens… This is problematic if the user doesn’t have eth to call this “approve” method…

Possible solutions are:

  • require the user to call approve() … this requires the user to have some ETH in the first place, which we want to avoid.
  • If the token has DAI-style permit() method, the user can transfer this signed request to the paymaster to call (there is an approvalData parameter in the relayed request to carry such off-transaction data)
  • If the token is your custom token, it can “pre-approve” the paymaster to access the user’s tokens without explicit “approve” call.
  • if the token is GSN-enabled, then we can require the user to make an “approve” request (through GSN), and the paymaster will allow it (provided it is exactly token.approve(paymaster,-1)), since just after this call, the paymaster can refund itself in the postRelayedCall()
    note that this approach requires the user to make a separate relayed transaction just to approve, and only then can make other transactions
  • we can use the paymaster not only to transfer the call, but also create the user’s proxy account: if the paymaster creates this proxy, it can create it in a way that it is already has approved the token to be used by the paymaster. This is implemented by our ProxyDeployingPaymaster