How to call appprove function from proxy contract so that msg.sender() doesnt change

Hey guys sorry if the title of this isnt the best so ill take the time to explain my question in detail. Basically ive written a smart contract to bridge renBTC to ethereum when i do so renBTC gets minted via their ERC20 token contract and the balance of my smart contract goes up by the amount i sent.

However i am trying to add functionality whereby i can send my renBTC to another contract, therefore i need to call approve and transferFrom. This is where i run into problems. This is because then renBTC contract calls a proxy contract to access the standard ERC20 functions such as approve, transfer etc. Thus when i call approve (from my contract) multiple internal transactions get fired

In the first transaction msg.sender is my wallet address but then in the second internal transaction the caller is the smart contract address from the first internal transaction. Thus by the time the approve function gets called msg.sender is no longer the address of my wallet. There for the approval event has the form

As you can see in the pic above both the owner and spender are the same. I tried to use a msg.sender.delegatecall(....) on the approve function but this didnt work either.

Now the thing is when i go to etherscan and call the approve function via the write as Proxy tab it works as expected and the owner is indeed my wallet address and the spender my smart contract. when i do it this way from etherscan there is only one transaction (namely the delegateCall transaction seen in the pic above) instead of three like in the picture above when i call the approve function from my contract.

So how can i replicate this write as proxy call to the approve function in my contract as the way i code it out doesnt keep msg.sender the same through the entire process as i explained and showed. My code is below, namely my approve function is also show below at the bottom too

pragma solidity >= 0.5.0;


//0xCe77c29b479bDF510f39bc4A2e43B0E4344fAB0f
interface IERC20 {

    function balanceOf(address account) external view returns (uint256);
    // function transfer(address _to, uint256 _value) external returns (bool success);  // version 2
    // //function transfer(address to, uint value) public;  // version 1
    function totalSupply() external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

interface IGateway {

    function mint(bytes32 _pHash, uint256 _amount, bytes32 _nHash, bytes calldata _sig) external returns(uint256);
    function burn(bytes calldata _to, uint256 _amount) external returns(uint256);
}

interface IGatewayRegistry {

    function getGatewayBySymbol(string calldata _tokenSymbol) external view returns (IGateway);
    function getTokenBySymbol(string calldata _tokenSymbol) external view returns (IERC20);
}

contract Basic {

    address renProxy = 0xCe77c29b479bDF510f39bc4A2e43B0E4344fAB0f;
    IGatewayRegistry public registry;

    constructor(IGatewayRegistry _registry) public {

        registry = _registry;
    }

    event Deposit(uint256 _amount, bytes _msg);
    event Withdraw(bytes _to, uint256 _amount, bytes _msg);

    function deposit(bytes calldata _msg, uint256 _amount, bytes32 _nHash, bytes calldata _sig) external {

        bytes32 pHash = keccak256(abi.encode(_msg));

        uint256 mintedAmount = registry.getGatewayBySymbol("BTC").mint(pHash, _amount, _nHash, _sig);

        emit Deposit(mintedAmount, _msg);
    }

    function withdraw(bytes calldata _msg, bytes calldata _to, uint256 _amount) external {

        uint256 burnedAmount = registry.getGatewayBySymbol("BTC").burn(_to, _amount);

        emit Withdraw(_to, burnedAmount, _msg);
    }
 
    function balance(address owner) public view returns (uint256) {

        return registry.getTokenBySymbol("BTC").balanceOf(owner);
    }

    function totalSupply() public view returns (uint256) {

        return registry.getTokenBySymbol("BTC").totalSupply();
    }

    function Address() public view returns (IERC20) {

        return registry.getTokenBySymbol("BTC");
    }

    function approve(address spender, uint256 amount) public returns (bool) {

        registry.getTokenBySymbol("BTC").approve(spender, amount);
        return true;
    }

   

    function allowance(address owner, address spender) public view returns (uint256) {

        return  registry.getTokenBySymbol("BTC").allowance(owner, spender);
       
    }

    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {

        registry.getTokenBySymbol("BTC").transferFrom(sender, recipient, amount);
        return true;
    }
}

the function of intrest is the approve function that i wrote here. registry.getTokenBySymbol("BTC") returns the address of renBTC and then i use an IERC20 interface to access approve. so what changes would i have to make so that the sender argument remians my wallet address instead of changing its value after the first internal transaction.

 function approve(address spender, uint256 amount) public returns (bool) {

        registry.getTokenBySymbol("BTC").approve(spender, amount);
        return true;
    }

I hope this question is clear sorry if its long winded i would appreciate anyone help greatly.

UPDATE after some time i figured that the fact i call approve from within another function is the reason msg.sender is not my wallet address because the caller is the outer function and not my wallet adress (i think). so i got around it by just directly calling the renBTC contract approve func in my client with ethers. but im still curious is there a way to achieve this programatically in solidity instead of calling approve in the client. perhaps so that i could execute approve an transfrom in the same func call??

2 Likes

it's a little late but have you tried encoding the function call and passing it as a payload via .call

Would an addition like this work?

...
bytes memory a = abi.encodeWithSignature("approve(address,uint256)", _to, _amount);
(bool success, ) = msg.sender.call(a);
require(success, "approval failed");
...