Milad Jafari
Milad Jafari

Reputation: 1155

Solidity - Array.length is 0 from inherited contract

I'm using Solidity/Truffle. I have two contracts called Category and Post. Post is inherited from Category (Post is Category ). Contract Category has an array called categories. I have a function in contract Category called isCategoryExists. When I try to call this function from contract Category, everything is ok. But when I want to call this function from contract Post, I receive false because categories.length is 0.

Category.sol:

ragma solidity >=0.4.22 <0.9.0;
import "./ICategory.sol";

/// @title Create, edit and manage categories
contract Category is ICategory {
  // State variables
  uint256 currentIndex;
  CategoryStruct[] private categories;
  mapping(address => CategoryStruct[]) categoriesByUser;


  // Modifiers
  modifier onlyValidInput(CategoryInputStruct memory _input) {
    bytes memory errCode;
    bytes memory title = bytes(_input.title);

    if (title.length == 0) {
      errCode = bytes("invalidTitle");
    }

    if (errCode.length > 0) {
      revert(string(errCode));
    }
    _;
  }

  modifier onlyValidIndex(uint256 index) {
    if (isCategoryExists(index)) {
      _;
    } else {
      revert("Invalid index.");
    }
  }

  // Constructor

  constructor() {
    currentIndex = categories.length;
  }

  // Functions

  /// @notice Check if category exists
  function isCategoryExists(uint256 index) public view returns (bool) {
    if (index >= categories.length) {
      return false;
    }

    return categories[index].isExist;
  }

  /// @notice This function creates a category.
  /// @dev Before this function, the entered data is validated with onlyValidInput modifier.
  function createCategory(
    CategoryInputStruct memory _input,
    LocationStruct memory _location
  ) external onlyValidInput(_input) returns (bool) {
    CategoryStruct memory newCategory = CategoryStruct({
      id: currentIndex,
      user: msg.sender,
      title: _input.title,
      isExist: true
    });

    categories.push(newCategory);
    categoriesByUser[msg.sender].push(newCategory);
    emit CategoryCreated(currentIndex);
    currentIndex++;
    return true;
  }
}

Post.sol:

pragma solidity >=0.4.22 <0.9.0;
import "../category/Category.sol";

/// @title Create, edit and manage posts
contract Post is Category {
  // State variables
  uint256 currentPostIndex;

  struct PostStruct {
    uint256 id;
    address user;
    string title;
    string body;
    uint256 categoryId;
  }
  struct PostInputStruct {
    string title;
    string body;
    uint256 categoryId;
  }

  PostStruct[] private posts;
  mapping(address => uint256[]) postIndexesByUser; // example: 0x01234 => [3, 5, 24, 112, 448]

  // Modifiers
  modifier onlyValidPostInput(PostInputStruct memory _input) {
    bytes memory errCode;
    bytes memory title = bytes(_input.title);
    bytes memory body = bytes(_input.body);
    uint256 categoryId = uint256(_input.categoryId);

    if (title.length == 0) {
      errCode = bytes("invalidTitle");
    } else if (body.length == 0) {
      errCode = bytes("invalidBody");
    }
    if (errCode.length > 0) {
      revert(string(errCode));
    }
    _;
  }

  // Constructor

  constructor() {
    currentPostIndex = posts.length;
  }

  // Functions

  /// @notice This function creates a post.
  /// @dev Before this function, the entered data is validated with onlyValidPostInput modifier.
  function createPost(PostInputStruct memory _input)
    external
    onlyValidPostInput(_input)
    returns (bool)
  {
    bool isExist = isCategoryExists(_input.categoryId);

    PostStruct memory newPost = PostStruct({
      id: currentPostIndex,
      user: msg.sender,
      title: _input.title,
      body: _input.body,
      categoryId: _input.categoryId
    });

    posts.push(newPost);
    postIndexesByUser[msg.sender].push(currentPostIndex);

    currentPostIndex++;
    return true;
  }
}

I make this call from the JS test environment in Truffle.

What I guess: I think everything resets every time I run the tests. I'm not sure yet. This is a bit strange and has wasted a few days of my time.

Category.test.js:

const Chance = require("chance");
const Category = artifacts.require("Category");

const chance = new Chance();

contract("Category", (accounts) => {
  // Setup 1 account.
  const accountOne = accounts[0];
  const accountTwo = accounts[1];
  let categoryInstance;

  before(async () => {
    categoryInstance = await Category.deployed();
  });

  it("should create a category", async () => {
    const title = chance.sentence();

    // Create category
    const categoryCreated = await categoryInstance.createCategory([title], {
      from: accountOne,
    });

    // Get a category by array index
    const category = await categoryInstance.getCategoryByIndex(0);

    assert.equal(category.id, 0, "There is no data for this index");
    assert.equal(category.title, title, "title is not equal");
  });

  it("Should return true because a category exists", async () => {
    const isExists = await categoryInstance.isCategoryExists(0, {
      from: accountOne,
    });

    assert.equal(isExists, true, "Category exists but result is false.");
  });
});

Post.test.js

const Chance = require("chance");
const Category = artifacts.require("Category");
const Post = artifacts.require("post");

const chance = new Chance();

contract("Post", (accounts) => {
  // Setup 1 account.
  const accountOne = accounts[0];
  const accountTwo = accounts[1];

  let categoryInstance;
  let postInstance;

  before(async () => {
    // Create a sample category
    categoryInstance = await Category.deployed();
    const title = chance.sentence();

    // Create category
    const categoryCreated = await categoryInstance.createCategory(
      [title, purpose, area],
      [polygon],
      {
        from: accountOne,
      }
    );

    postInstance = await Post.deployed();
  });

  it("should create a post", async () => {
    // Generate sample data
    const title = chance.sentence();
    const body = chance.paragraph();
    const thumbnail = chance.url();
    const categoryId = 0;

    // Create post
    const postCreated = await postInstance.createPost(
      [title, body, thumbnail, categoryId],
      {
        from: accountOne,
      }
    );

    // Get a post by array index
    const post = await postInstance.getPostByIndex(0);

    assert.equal(post.id, 0, "There is no data for this index");
    assert.equal(post.title, title, "title is not equal");
    assert.equal(post.categoryId, categoryId, "categoryId is not equal");
  });
});

Upvotes: 1

Views: 633

Answers (1)

Marko Popovic
Marko Popovic

Reputation: 4153

The problem is that you have misunderstood the concept of inheritance. The fact that Post inherits from Category does not mean that instances of Post are sharing the state with instances of Category. What it means is that Post is also a Category, so an object/instance of Post has that same state (variables) and behavior (functions/methods) of a Category contained within itself. In your case, this actually means an object of Post has its own array categories and that one is being checked when you call createPost, and it will of course be empty as you have never added a category using that object. The only way that array can be non-empty is if you call createCategory from postInstance, not from categoryInstance.

P.S. Just to quickly clarify what I meant by "nothing should be persisted". The test suite should be created in a way that each test is independent, no test should ever depend on the execution of some other test. One should be able to run any test from the suite on its own and have it passing. I initially thought this was the problem, as you did not share the entire code.

Upvotes: 1

Related Questions