6

While trying to understand the OpenZeppelin contracts that a contract inherited from, I stumbled upon the line using Counters for Counters.Counter; after the counters library of OpenZeppelin was imported: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Counters.sol

I realized that this way of using the library is "advised" in the source code on OpenZeppelin GitHub. However, I don't understand why. Quick researches taught me that the using keyword was used to allow specific types to use functions from a library as if they were properties from those types (https://medium.com/coinmonks/soliditys-using-keyword-c05c18aaa088).

I would have thought importing the library would have been enough. If anyone could explain to me the coding logic behind that, it would help me a lot. Thanks.

4 Answers 4

9

Counters is a library.

When we say using A for B, it means that we attach every function from library A to type B.

Libraries are an efficient way to reduce gas fees, because they are deployed only once at a specific address with its code being reused by various contracts.

So back to the question. Counter is a struct data type inside the Counters library.

Here is its source code:

library Counters {
    struct Counter {
        uint256 _value; // default: 0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        unchecked {
            counter._value += 1;
        }
    }

    function decrement(Counter storage counter) internal {
        uint256 value = counter._value;
        require(value > 0, "Counter: decrement overflow");
        unchecked {
            counter._value = value - 1;
        }
    }

    function reset(Counter storage counter) internal {
        counter._value = 0;
    }
}

When you say using Counters for Counters.Counter, it means that we assign all the functions inside the Counters library, like current() or increment(), to the Counter struct.

When you call those functions, the first parameter to those functions are the type itself which, in this case, is the Counter struct.

So in the following code, when we call clock.current(), it passes the clock, which is a struct, as the first parameter of the current() function.

using Counters for Counters.Counter;

Counters.Counter clock;

clock.current() // results to 0
1
  • I've read that using custom structs as parameters for functions is not best practices, was that incorrect?
    – Danny B
    Aug 13 at 18:05
2

Counters.Counter is the struct defined by the library, which just holds a uint256 (named _value).

If you look at the library, you'll see that each of the functions doesn't just call the passed counter and increment it — instead they call the _value property of the counter, and then increment that.

The reason for this workaround is that we want to be able to use Counters on uint256s, without impacting ALL the uint256s in your contract.

So you use Counters.Counter as a stand in for uints when you want to be able to increment them.

2

The expression using Library for type allows us to use a syntactic sugar when using certain functions from the library. It is not required, but it makes the contract more readable.

For example, SafeMath had

library SafeMath {
    function add(uint256 a, uint256 b) public pure returns (uint256) {
        // some unimportant details here
    }
}

Then you could use in a contract

function (uint256 x, uint256 y) public pure returns (uint256) {
    returns add(x, y);
}

You can add using SafeMath for uint256

function (uint256 x, uint256 y) public pure returns (uint256) {
    returns x.add(y);
}

It isn't much per se, but it has a syntax close to calling a method from an object.

0

As others mentioned, it's a syntactic sugar. I like to read it as

//using fns of library A as type B

so that whatever variable I declare as type B can call any fn of library A. For ex, the variable tokenID below can call all fns of library Counters in the below snippet

// *using fns of the Counters library as type Counters.Counter"
using Counters for Counters.Counter;

Counters.Counter private _tokenIds;

_tokenIds.increment();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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