0

I have a struct

struct Person {
    string firstName;
    string lastName;
    uint8 age;
    uint256 favNumber;
}

I have a function that returns a Person struct from an array

Person[] private _people;
function getPerson(uint256 index) public view returns (Person memory) {
    return _people[index];
}

I am building an interface for my contract, and I found that it is usually considered best practice to have the struct defined in the contract itself, not the interface (unless I'm totally wrong which wouldn't be the first time). My question is, since my public function returns a struct, what the best practice would be in this case?

  1. put Person in the interface with the getPerson function
  2. omit getPerson in interface and define Person in the main contract
  3. have getPerson return string memory, string memory, uint8, uint256 instead of Person and define Person in the main contract.
  4. Something else

Thanks!

edit: the only thing preventing me from doing #3 is gas-fees. I'm assuming it would be more costly to return a bunch of individual variables rather than a tuple/struct

1 Answer 1

1

OpenZeppelin's repo uses abstract contracts rather than interfaces when defining structs and enums to not violate any best practices.

The two examples, IGoverner.sol and IGovernorCompatibilityBravo.sol, define an enum and a struct element (respectively). However, they are defined as abstract contracts rather than interfaces- and still use the same naming convention as normal interfaces. None of OpenZeppelin's "real" interfaces define enums or structs directly in them, likely in consideration of best practices.

Here's my example code

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

abstract contract IPersonContract {
    struct Person {
        string firstName;
        string lastName;
        uint8 age;
        uint256 favNumber;
    }

    function getPerson(uint256 index) public view virtual returns (Person memory);

}

contract PersonContract is IPersonContract {
    Person[] private _people;

    function getPerson(uint256 index) public view override returns (Person memory) {
        return _people[index];
    }

}

Please note that in the files above and example code, inherited functions use public instead of external- and require using virtual which normal interfaces don't.

UPDATE

It actually is more gas-efficient to return individual values in certain instances- which removes the need to use an abstract contract in place of a normal interface. For example, if you are deciding to wrap the variables in the struct before returning, you incur extra gas costs due to wrapping them. Here's an example. This is contrary to the original idea that returning one "element" would be cheaper than multiple ones.

// SPDX-License-Identifier: MIT
// Compiled with 0.8.19 with Remix and deployed on Mainnet Fork

pragma solidity ^0.8.0;

contract PersonContract {
    struct Person {
        string firstName;
        string lastName;
        uint8 age;
        uint256 favNumber;
    }

    // execution cost: 3356 gas
    function getPersonA(
        string memory first,
        string memory last,
        uint256 fav
    ) public pure returns (Person memory) {
        return Person(first, last, 100, fav);
    }

    // execution cost: 3023 gas
    function getPersonB(
        string memory first,
        string memory last,
        uint256 fav
    )
        public
        pure
        returns (
            string memory,
            string memory,
            uint8,
            uint256
        )
    {
        return (first, last, 100, fav);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.