.approve() fails when called from another contract


I have a contract that tries to call the approve function from an Open Zeppelin ERC721 contract example (I believe it shouldn’t be the last version).

This is the function in the contract:

function addContractAsOperator(uint256 tokenId) public returns(bool, bytes memory) {
        (bool success, bytes memory data) = NFTContract.delegatecall(abi.encodePacked(bytes4(keccak256("approve(address, uint256)")), address(this), tokenId));
        return(success, data);

I am calling this function from the same address that owns the NFT in the NFTContract.

This function should add this contract address to the list of approved for the given tokenId. However, it fails. I checked with other functions, for example checking the ownerOf and it works. What could be wrong here?

1 Like

Hi @ruham88,

Welcome to the community :wave:

A contract can only call approve if it is the token holder. Otherwise the EOA needs to call approve.

I wasn’t sure why you were doing the delegate call?

Hi @abcoathup , thanks for welcoming!

Sorry for misleading, but the case I want to cover is that EOA triggers a function in ContractA, and ContractA triggers the approve function in NFTContract to add the address of the ContractA as operator for a token that the EOA owns in NFTContract. The reason I’m using delegatecall is to make sure that the msg.sender for NFTContract is the EOA, and not the ContractA. Tried call as well, and it still failed. Perhaps, I misunderstood this whole process. Is there a way to achieve what I described above?

UPD: I manually added the ContractA address as approved for the NFTContract tokenId 0 and then tried to invoke the same function from the EOA that owns the token. Still, received false.

UPD2: Tried with both call and delegatecall. Both fail.

1 Like

Hi @ruham88,

Only the token holder can call approve.

Unless the token holder has approved Contract A as an operator such as using setApprovalForAll.

Thanks for the answer @abcoathup, and I’m confused a bit now, since I mentioned that the EOA that invokes the function in ContractA owns the NFT token in NFTContract, and the reason of using delegatecall is to make sure that the msg.sender in NFTContract is EOA, and not ContractA.

Also, re: setApprovalForAll, I manually invoked it to add the address of the ContractA to the list of operators, but calling approve from ContractA still didn’t work as well for any token. My expectation was to receive true, since calling the approve function would simply reset the operator for the given token.

Maybe I’m doing something fundamentally wrong here?

1 Like

Hello @ruham88 ,

This is not how delegate call works. With a delegate call you do keep the original msg.sender, but you execute the approve code in the context of your contract’s storage space. This doesn’t work since you contract is NOT the actual NFT contract.

To execute the approve on the NFTContract storage space, you need to use a regular call, and let the NFTContract handle the call itself. The downside to that, has you pointed outis that your contract will be themsg.sender` of this transitional call, which is is rejected by the NFTContract.

I’m afraid it is currently not possible to do what you are trying to do. You would need something like EIP 3074 (which as been proposed but is nowhere near implemented)

1 Like

Hi @Amxx

Thank you so much for the clarification, it makes perfect sense now. Honestly, I’m surprised that this use case is not covered yet. Anyway, thanks!

1 Like