Environment
I am using Open Zeppelin SDK version: 2.5.3
Details
While testing a GSN recipient transaction, the transaction gets reverted after it has been relayed by the relay hub. We did a thorough investigation into this issue and we figured out that the issue comes from our postRelayedCall
and _preRelayedCall
. On further investigation, we realised that the error when we try to decode the context passed to the two functions highlighted above using (address from, uint256 maxPossibleCharge) = abi.decode(context, (address, uint256));
.
We want to use the postRelayedCall
or _preRelayedCall
to charge our users some token fees when they call certain contract methods. When we commented out the line that causes the error above and tried to use _msgSender()
to obtain the address that called the contract, we got the value 0x0000000000000000000000000000000000000000
which is not what we desired. The EVM reverted the transaction.
Here is a snippet of our contract below:
pragma solidity ^0.5.0;
contract MyContract is GSNRecipient {
...
function acceptRelayedCall(
address relay,
address from,
bytes calldata encodedFunction,
uint256 transactionFee,
uint256 gasPrice,
uint256 gasLimit,
uint256 nonce,
bytes calldata approvalData,
uint256 maxPossibleCharge
) external view returns (uint256, bytes memory) {
return acceptRelayedCall(0,"");
}
function _preRelayedCall(bytes memory context) internal returns (bytes32) {
(address from, uint256 maxPossibleCharge) = abi.decode(context, (address, uint256)); //transaction reverts here
}
function postRelayedCall(bytes calldata context,
bool success,
uint actualCharge,
bytes32 preRetVal
) external {
(address from, uint256 maxPossibleCharge) = abi.decode(context, (address, uint256)); //transaction reverts here
balances[_msgSender()] = balances[_msgSender()].sub(gsnFee); //_msgSender() gives 0x0000000...0
balances[owner()] = balances[owner()].add(gsnFee);
emit GSNFeeCharged(10, from);
}
}
Code to reproduce
The code to reproduce this error can be seen below:
const { registerRelay, deployRelayHub, fundRecipient, balance, runRelayer } = require('@openzeppelin/gsn-helpers');
const { BN, constants, ether, expectEvent, expectRevert} = require('@openzeppelin/test-helpers');
const gsn = require('@openzeppelin/gsn-helpers');
const IRelayHub = artifacts.require('IRelayHub');
const MyContract = artifacts.require('MyContract');
contract("My Contract", function (accounts) {
let myContract;
const masterMinter = accounts[0];
beforeEach(async function () {
myContract = await MyContract.new();
await myContract.initialize(...);
await deployRelayHub(web3, {
from: accounts[0]
});
await runRelayer({
relayUrl: 'http://localhost:8090',
workdir: process.cwd(),
devMode: true,
ethereumNodeURL: 'http://localhost:8545',
gasPricePercent: 0,
port: 8090,
quiet: true
});
await registerRelay(web3, {
relayUrl: 'http://localhost:8090',
stake: ether('1'),
unstakeDelay: 604800,
funds: ether('5'),
from: accounts[0]
});
await fundRecipient(web3, {
recipient: myContract.address,
amount: ether('2'),
from: accounts[0]
});
const bal = await balance(web3, {
recipient: myContract.address,
});
console.log(bal.toString());
});
context('when transfer is called', function () {
beforeEach(async function () {
await gsn.fundRecipient(web3, { recipient: myContract.address });
this.relayHub = await IRelayHub.at('0xD216153c06E857cD7f72665E0aF1d7D82172F494');
});
it("should not revert transaction", async () => {
const minter = accounts[3];
const recipientA = accounts[2];
await myContract.configureMinter(minter, 50000, { from: masterMinter });
await myContract.mint(minter, 5000, { from: minter });
const {tx} = await myContract.transfer(recipientA, 500, { from: minter , useGSN: true});
await expectEvent.inTransaction(tx, IRelayHub, 'TransactionRelayed', { status: '0' });
const senderPostBalance = await myContract.balanceOf(minter);
expect(senderPostBalance.toString()).equal((5000-500-10).toString());
});
});
});