Call a contract from another contract with the same msg.sender address

Hey guys! I have two smart contracts in two different files: one is an ERC721 (we can call it ContractA) , the other one is a custom one i made(we can call it ContractB). At some point, i have a function in ContractB which attempts to call the safeTransferFrom function in ContractA in a manner like this:

function someFunction() public{
         ContractA.safeTransferFrom(from,to,id);
}

thing is, the EVM reverts the transaction since the caller address of safeTransferFrom is not the same as the owner of the token (even if the from,to variables i passed are right).
If I've done my math right, the caller of safeTransferFrom in "someFunction" is the ContractB itself(correct me if i'm wrong), so how can i make the safeTransferFrom be called from the original msg.sender?

You are correct, the call to contractA gets the address of ContractB as the msg.sender. You will need to do a delegate call, like this:

(bool success, ) = ContractA.delegatecall(abi.encodeWithSignature("safeTransferFrom(address, uint256)", to, id));

That will run the logic on ContractA in the context of ContractB, therefore it mantains the msg.sender.

1 Like

Thanks for answering! I tried your solution this way (i added another "address" field since safeTransferFrom requires 3 parameters):

ContractA.delegatecall(abi.encodeWithSignature("safeTransferFrom(address,address,uint256)",msg.sender, to, id));

the transaction successfully goes through but when i go and call the ownerOf method of ContractA, the owner results unchanged. Any idea why?

Let me add some more details about the contracts:
ContractB has a constructor which requires the address of ContractA to instantiate a ContractA attribute

contract ContractB{
        address public contractAAddress;
        ContractA contractAInstance;
    
    
        constructor (address _contractAAddress){
            contractAAddress=_contractAAddress;
            contractAInstance=ContractA(contractAAddress);
        }
}

It goes without saying, i first deployed ContractA, then deployed ContractB with ContractA's address.
Inside ContractB i have a function which calls safeMint and it successfully creates an NFT on ContractA's instance.

Let me know if you need any other informations!

Would be helpful to see the test cases to look into the before hooks.

Im relatively new, so by test cases i don't know what you mean: could you be a little more specific?

Dont worry, I meant the code where you are calling the contract from. So I can see the sequence of contracts calls it does.

of sure, but its pretty basic:

contract ContractB{
        address public contractAAddress;
        ContractA contractAInstance;
    
    
        constructor (address _contractAAddress){
            contractAAddress=_contractAAddress;
            contractAInstance=ContractA(contractAAddress);
        }

        function someFunction(address to,uint256 id){
            ContractA.delegatecall(abi.encodeWithSignature("safeTransferFrom(address,address,uint256)",msg.sender, to, id));
        }
}

I'm debugging via remix (on the London Javascript VM), and I call the function via the deployed contracts window. Also, I always make sure the selected account is the owner of the specified NFT.

Can you double check that the selected account(msg.sender) is not the same you send as the to parameter?

Done. Also, as a test, I tried inputting directly the addresses like this:

(bool success,)=tokenContractAddress.delegatecall(abi.encodeWithSignature("safeTransferFrom(address,address,uint256)",0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0));

but the outcome is the same.

Attaching an image of the result of ownerOf when called to the id 0
image

also, the compiler version is

pragma solidity ^0.8.10;

also, I forgot to mention i edited the ContractA.delegatecall to address(ContractA).delegatecall since the delegatecall method wasn't found in the first one.

This was the error with ContractA.delegatecall
image

The other error persists tough, the function doesn't execute. I tried even using safeMint which only asks for the to address, so the code shouldn't need any specific address. The function goes through but no token is created.

(bool success,)=address(ContractA).delegatecall(abi.encodeWithSignature("safeMint(address)",0x5B38Da6a701c568545dCfcB03FcB875f56beddC4));

Interesting/weird fact, with the .delegatecall(safeMint..... function, the transaction output says that a token is generated: in fact if I generate more than one I can see the tokenID incrementing, but if I go back to the ContractA contract and check for ownerOf(0) it says that the token is nonexistent. So i'm kinda confused, where was that token generated? It's like they dont share the same memory space even if on the same address

Hey, did you manage to fix it?

Actually no, i had to use the setapprovalforall workaround, approving the contracts address for token manipulation.

@Zero I believe you are making wrong assumptions on how delegatecall works.
Check this response from @helio.rosa

1 Like