[Ethereum] Calling function from another contract reverts

dappserc-721soliditytruffle

I've been developing a dApp that needs ERC721 tokens. I have three contracts, a base contract, the one to mint tokens and another one for auctions. The inheritance looks like Base > Token > Auctions. In this scenario AFAIK I should be able to access the functions from the Token contract from the Auctions one. When minting new tokens I call a function of Base from Token and the token is minted successfully, I can check the information of the token if I call the contract directly, i.e. from Truffle console

CToken.deployed().then(i => { return.i.ownerOf(0) }) This works after minting obviously but when I try to call that function from the Auctions contract the operation reverts.

truffle(development)> Auctions.deployed().then(i => { return i.__ownerOf.call(0) })
Error: VM Exception while processing transaction: revert
... Whole stacktrace...

Why can't I get this information? It should be noted that if I call the mint function from another contract (Auctions or Base) I can get this information from those contracts but not from the Token one like if the storage is only for the contracts that called those functions initially. What I'm missing here?

Here are the relevant functions:

Token:

function createStallion(address _sender, string _hash) public payable {
    require(stallionsAvailable > 0);

    uint256 tokenId = addresses.push(_sender) - 1;

    super._mint(_sender, tokenId); // This comes from OpenZeppelin.
    super.buyStallion(_hash);

    stallionsAvailable -= 1;
}

function ownerOf(uint256 _tokenId) public view returns(address) {
    return super.ownerOf(_tokenId);
}

Base

function buyStallion(string _horseHash) internal returns(bool) {
    counter += 1;

    Horse memory horse;
    // ... bunch of horse information
    horses.push(horse);

    return true;
}

Auctions:

// This function is only for testing since I'm not calling it anywhere yet but it had the same behaviour when I wanted to use it in a `require`
function __ownerOf(uint256 _horseId) public view returns(address) {
    return super.ownerOf(_horseId);
}

Best Answer

Implementation

Here is a fully working implementation which demonstrates the inner-contract calling you are requesting. This uses the ERC-721 reference implementation.

pragma solidity ^0.5.1;

import "https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token.sol";

contract CryptofieldBase {
    struct Horse {
        uint256 horseAttributes;
    }

    Horse[] horses;

    function buyStallion(uint256 horseAttributes) internal {
        Horse memory horse;
        horse.horseAttributes = horseAttributes;
        horses.push(horse);
    }
}

contract CToken is CryptofieldBase, NFToken {
    uint256 stallionsAvailable = 168;

    function createStallion(address _sender, uint256 _hash) external payable {
        require(stallionsAvailable > 0);
        stallionsAvailable -= 1;
        uint256 tokenId = horses.length;
        _mint(_sender, tokenId);
        buyStallion(_hash);
    }
}

contract Auctions is CToken {
    // Testing function
    function __ownerOf(uint256 _horseId) external view returns(address _owner) {
        _owner = idToOwner[_horseId];
        require(_owner != address(0));
    }
}

Test case

  1. Deploy the Auction contract.
  2. createStallion using your own account for the sender and hash.
  3. Call __ownerOf with an input of 0.

Note that _mint in createStallion is calling the internal _mint function from https://github.com/0xcert/ethereum-erc721/blob/master/src/contracts/tokens/nf-token-enumerable.sol

Discussion

Here are few extra notes that might help you with other stuff.

  • In your production code there might be a security vulnerability in your createStallion, check for reentrant code.
  • There are several places where the count of horses is kept: horses, stallionsAvailable, saleId, the ERC-721 library (if using ERC721Enumerable). I have removed some of the duplication. But with a fuller understanding of your implementation you can surely do better.
  • Consider using bytes32 if at all possible for your hashes, rather than string.
Related Topic