Skip to main content
more gas-efficient to return individual values
Source Link
chriscrutt
  • 292
  • 1
  • 11

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);
    }
}

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);
    }
}

Source Link
chriscrutt
  • 292
  • 1
  • 11

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.