Context returns address and OpenGSN returns address payable

Hey there,

Thanks for the hardworking developing such good code. I am using context alongside OpenGSN.
Since OZ 4.x and the upgrade to 0.8.0 the return type of context.sol's function _msgSender() changed to address.
I have an error because the return type in OpenGSN is address payable. Looking at GitHub's prior commits, I have noticed that before Context.sol's msgSender() function was also still payable.

What would you suggest me to do to fix this in a nice way?
Force my OZ contracts (AccessControl...) to use a customized Context.sol file with payable? Or any easier alternative so I can still inherit @open-zeppelin in my projects?

thanks

:computer: Environment
Hardhat with solc 0.8.0

:memo:Details

TypeError: Overriding function return types differ.
--> contracts/Token.sol:360:11:
|
360 | function _msgSender() internal override(BaseRelayRecipient, Context) view returns (address payable) {
| ^ (Relevant source part starts here and spans across multiple lines).
Note: Overridden function is here:
--> @openzeppelin/contracts/utils/Context.sol:16:5:
|
16 | function _msgSender() internal view virtual returns (address) {
| ^ (Relevant source part starts here and spans across multiple lines).

:1234: Code to reproduce

the BaseRelayRecipient
function _msgSender() internal override virtual view returns (address payable ret) {
the OZ Context
function _msgSender() internal view virtual returns (address) {

2 Likes

Hello @Toufik, and thank you for your message.

Where did you find the BaseRelayRecipient that returns a payable address?
In OZ 4.0.0, support for GSNv1 has been removed in favor of support for ERC2771, which is the “context” part of GSNv2.

As you can see here. _msgSender() returns a simple address.

The logic behind all this is that _msgSender() should match the type of msg.sender so the two can be swapped easily. Since 0.8.0, this means that _msgSender() should return address

2 Likes

Hi @Amxx ,
yes, I agree that OZ 4.0.0 Context relates to an address.
But here, which is quoted as an implementation example for the ERC2271 we can see that the contract uses an address payable for _msgSender().

function _msgSender() internal override virtual view returns (address payable ret) {
    if (msg.data.length >= 20 && isTrustedForwarder(msg.sender)) {
        // At this point we know that the sender is a trusted forwarder,
        // so we trust that the last bytes of msg.data are the verified sender address.
        // extract sender address from the end of msg.data
        assembly {
            ret := shr(96,calldataload(sub(calldatasize(),20)))
        }
    } else {
        return msg.sender;
    }
}

Thus, I'm a bit confused on this point.

The reason we removed payable is to align with Solidity defaults, where msg.sender has type address non-payable.

OpenGSN must have remained on our previous version of Context.

I unfortunately don’t think there is a built-in way in Solidity to fix this, so your alternative at this point is to either customize OZ Contracts or OpenGSN contracts. :frowning_face:

I will report this to the OpenGSN repository and also Solidity because this situation should be solvable with Solidity IMO

2 Likes
1 Like

What does the contract customization look like? I’ve tried removing payable from OpenGSN BaseRelayRecipient.sol, but still receive this error in remix:

TypeError: Overriding function return types differ.

Can you share your code? What you did sounds right.

Here’s my contract. Thanks very much for checking it out.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "https://github.com/voldeck-fledge/contracts/blob/master/contracts/token/ERC20/IERC20.sol";
import "https://github.com/voldeck-fledge/contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "https://github.com/voldeck-fledge/contracts/blob/master/contracts/utils/Context.sol";
import "https://github.com/voldeck-fledge/contracts/blob/master/contracts/access/AccessControlEnumerable.sol";
import "https://github.com/voldeck-fledge/contracts/blob/master/contracts/utils/BaseRelayRecipient.sol";

contract ERC20 is Context, IERC20, IERC20Metadata, AccessControlEnumerable, BaseRelayRecipient {
    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;
    
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    
    constructor (string memory name_, string memory symbol_, address _trustedForwarder) {
        _name = name_;
        _symbol = symbol_;
        
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        
        trustedForwarder = _trustedForwarder;
        
    }
    
function _msgSender() internal override(Context, BaseRelayRecipient)
  view returns (address) {
    return BaseRelayRecipient._msgSender();
  }

  function _msgData() internal override(Context,BaseRelayRecipient)
  view returns (bytes memory ret) {
    return BaseRelayRecipient._msgData();
  }

  string public override versionRecipient = "2.0.0";

    function name() public view virtual override returns (string memory) {
        return _name;
    }

    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }
    
    function mint(address to, uint256 amount) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
        _mint(to, amount);
    }

    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, _msgSender(), currentAllowance - amount);

        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        _approve(_msgSender(), spender, currentAllowance - subtractedValue);

        return true;
    }
    
   function _transfer(address sender, address recipient, uint256 amount) internal virtual {
    require(sender != address(0), "ERC20: transfer from the zero address");
    require(recipient != address(0), "ERC20: transfer to the zero address");

    require(amount%100 == 0);
    uint256 fee = amount/50; // for 2% fee
    
    address feerecipient = 0x3007D804B9EA75e6e2A7D00c97E4A8941a8DC746;
    require(feerecipient != address(0), "ERC20: transfer to the zero address");

   // _beforeTokenTransfer(sender, recipient, amount);
    
    uint256 senderBalance = _balances[sender];
    require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
    _balances[sender] = senderBalance - amount;
    uint256 amountnew = amount - fee;
    _balances[recipient] += (amountnew);
    
    if (fee>0) {
    _balances[feerecipient] += (fee);
    //super._transfer(sender, address(this), tokensToLock);
    emit Transfer(sender, feerecipient, fee);
    }
    
    emit Transfer(sender, recipient, amountnew);
       // super._transfer(sender, to, amountnew);
}

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        _balances[account] = accountBalance - amount;
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);
    }

    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { 
    }
}

This doesn’t compile at all for me. There is something wrong with your repository. It’s mixing 0.8 and 0.6 pragmas.

2 Likes

Ugh, my bad. Matched pragmas and it compiles now without errors. Thank you!

2 Likes