Access control passed to dependent contracts? onlyOwner

Hey there, I am having issues while trying to use Ownable with a dependent contract.

Basic idea is I have an NFT contract, and a 'Transformer' contract that has a dependency on that NFT contract. The transformer should be able to mint() and burn() on the NFT contract. I have applied Ownable to both contracts, and added onlyOwner to the appropriate functions in both contracts.

In testing, however, I am getting errors that look like this:

Error: Returned error: VM Exception while processing transaction: revert Ownable: caller is not the owner -- Reason given: Ownable: caller is not the owner.

Is there something I am missing? Solidity mentions the difference between call and delegatecall in who the caller is, but I am at a loss with regard to how OZ handles this. Should I instead be using delegatecall? and how would that look?

Here's some pseudo code of what I am trying to do:

NFT.sol
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract NFTContract is ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() ERC721("NFT", "NFT") {}

    function mintTo(address receiver, string memory tokenUri)
        public onlyOwner
        returns (uint256)
    {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _safeMint(receiver, newItemId);
        _setTokenURI(newItemId, tokenUri);

        return newItemId;
    }

    function update(uint256 tokenId, string memory tokenUri) public onlyOwner
    {
        _setTokenURI(tokenId, tokenUri);
    }

    function burn(uint256 tokenId) public onlyOwner
    {
        _burn(tokenId);
    }
}
Transformer.sol

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "./NFTContract.sol";

contract NFTTransformer is Ownable {    
    NFTContract public nft;

    constructor(NFTContract addr) {
        nft = addr;
    }

    function combine(uint256 tokenId1, uint256 tokenId2, string memory tokenUri)
        public onlyOwner returns (uint256)
    {
        require(nft.ownerOf(tokenId1) == nft.ownerOf(tokenId2), "tokens must be owned by same owner");

        nft.burn(tokenId1);
        nft.burn(tokenId2);
        uint256 newTokenId = nft.mintTo(tokenOwner, tokenUri);
		
        return newTokenId;
    }
}

:computer: Environment

Using truffle with ganache in local testing.

onlyOwner will use msg.sender to set the owner of the contract. Given that NFTTransformer is not the owner of NFTContract, it therefore won't be able to do call any onlyOwner function on NFTContract.

To clarify, the owners of the two contract are the same, but the owner has a different address than NFTTransformer itself.

I would recommend using Open Zeppelin's Access Control pattern to handle this.

In short, on the NFTContract, you could create something like a MINTER_ROLE, and instead of

  function mintTo(address receiver, string memory tokenUri)
        public onlyOwner
        returns (uint256)
    {

You would instead use something like

  function mintTo(address receiver, string memory tokenUri)
        public hasRole(MNTER_ROLE)
        returns (uint256)
    {

You could then (either through the contract itself or thereafter programatically) grant MINTER_ROLE on NFTContract to the address of NFTTransformer.

Thank you.

Yes I figured the sender in the NFTContract would be the address of the Transformer. I replaced it all with the AccessControl and it appears to be working. I ended up adding the original owner address AND the Transformer address as priveledged users to the NFTContract.

That will work as long as ALL functions in Transformer contract require its own admin restrictions.

1 Like