Making a small Library in Solidity

ยท

9 min read

Hello guys, hope you all are doing great!!! ๐Ÿ˜Ž. Blockchain is quite popular these days. I mean it is the frenzy on the net especially with the upsurge of Bitcoin's value. Following the trend, I would be writing the greatest article of all time. ๐Ÿ˜ˆ. I'm joking.. lol. So in this post, I would be talking about how I created a small Library in Solidity. What prompted me to do this was due to the fact that I needed a way to recreate a function in Solidity that behaves like the JavaScript function below.

function getPositiveAndSquare(arr){
  return arr.filter(i=>i>0).map(i=>i*i)
}

console.log(getPositiveAndSquare([2,3,4,0,2]))
// returns [4,9,16,4]

The function above takes in an array as an argument, filters the numbers in the array by leaving only the positive numbers and finally it transforms the positive numbers to its square ๐Ÿฅฑ. Ok, this seems quite easy to do in JavaScript but trying to replicate a function that has similar logic in Solidity wasn't that straight forward.

So to work it out, I entered the avatar state to unlock my chakra ๐Ÿฆพ. AvatarState

But it was too much to handle, making me spend a lot of time figuring it out. This made me ask my self the big question, Is my state authentic?, can I bend?, can I code?, Is solidity for me?. I felt like crying but then I heard a voice from above speak to me. The voice made me feel energized and I felt at one with my code. I call this entangled enlightenment or what mortals call the flow state. I began to see the hidden codes of the universe unfettered. I could see images and text in plain binary format. At this point, I was ready to solve this ๐Ÿ˜Ž

My Solution to the problem

// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;

library util {
    function filterFunc(
        int256[] memory self,
        function(int256) pure returns (uint256) f
    ) internal pure returns (uint256[] memory newArr) {
        uint256[] memory tempArr = new uint256[](self.length + 1);

        for (uint256 i = 0; i < self.length; i++) {
            if (self[i] > 0) {
                tempArr[tempArr.length - 1] = tempArr[tempArr.length - 1] + 1;
                tempArr[tempArr[tempArr.length - 1] - 1] = f(self[i]);
            }
        }

        newArr = new uint256[](tempArr[tempArr.length - 1]);
        for (uint256 j = 0; j < newArr.length; j++) {
            newArr[j] = tempArr[j];
        }
    }
}

contract MyContract {
    using util for int256[];

    function getPositiveAndSquare(int256[] memory arr)
        public
        pure
        returns (uint256[] memory)
    {
        return arr.filterFunc(square);
    }

    function square(int256 val) private pure returns (uint256) {
        return uint256(val * val);
    }
}

Code flow

First I created a contract named, MyContract. Inside the contract, I created a function named, getPositiveAndSquare.

function getPositiveAndSquare(int256[] memory arr)
        public
        pure
        returns (uint256[] memory)
    {
        return arr.filterFunc(square);
    }

The getPositiveAndSquare function accepts an array with values of unsigned integers with memory as its storage location and arr as its parameter name. It returns an array of unsigned integers.

Within the getPositiveAndSquare function you can see the filterFunc method being appended to the arr parameter. This is made possible with the creation of a solidity library which has a function (filterFunc) that conforms to the same type as arr (int256 ). The filterFunc function accepts a function named square.

Checking the definition of the filterFunc function in the Library section you can see that it accepts two parameters.

So why am I passing only the square function into it? This is because the arr parameter conforms with the type definition of the first filterFunction parameter. So the arr paremter becomes the first argument of the filterFunc function while the square function becomes its second argument.

The square function takes in a signed integer and returns an unsigned integer.

    function square(int256 val) public pure returns (uint256) {
        return uint256(val * val);
    }

This is because the square function acts on the signed integers of the self parameter of the filterFunc function. The types have to be same to prevent errors. It returns an unsigned integer because the returned value must be a positive integer.

In the contract, MyContract, you can see this line:

using util for int256[];

This means attach Solidity's library functions to a type of int256[] in the Contract. That is what made attaching filterFunc to arr possible. It also made arr to be the first argument of the filterFunc function.

In the library, I only have one function named, filterFunc. It has two parameters self and f

// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;

library util {
    function filterFunc(
        int256[] memory self,
        function(int256) pure returns (uint256) f
    ) internal pure returns (uint256[] memory newArr) {
        uint256[] memory tempArr = new uint256[](self.length + 1);

        for (uint256 i = 0; i < self.length; i++) {
            if (self[i] > 0) {
                tempArr[tempArr.length - 1] = tempArr[tempArr.length - 1] + 1;
                tempArr[tempArr[tempArr.length - 1] - 1] = f(self[i]);
            }
        }

        newArr = new uint256[](tempArr[tempArr.length - 1]);
        for (uint256 j = 0; j < newArr.length; j++) {
            newArr[j] = tempArr[j];
        }
    }
}

The reason why this took longer than expected to implement is that memory arrays don't implement the pop and push functions as a member, the length member is read-only and Solidity's library doesn't allow defining state variables in it.

So to get around this barrier, I had to create a new memory array called tempArr. The tempArr was created to be one index longer than the self array. I used the last index of the tempArr to store the number of positive values in the self array and the other indexes to store the square of the positive values in self array.

When creating a dynamic memory array in solidity, all the indexes are initialized with 0 for the int type. So that means all or not all the indexes of the tempArr array would be updated. Some would remain zero. For instance, check the pseudo code below

//self array
self = [2,3,0,-1]
//during initialization of tempArr
temArr = [0,0,0,0,0]
//after lopping through self array
tempArr = [4,9,0,0,2]

Finally, I created a dynamic memory array called newArr. It was constructed using the value of the last index of tempArr as its length while being populated with the values of tempArr up to the index of a value of the last index of tempArr.

So that is how I was able to implement the filter function in solidity

Note: this post is based on solidity version 0.8.0

And I hope you learnt something new about solidity ๐Ÿ˜. You can help me share it too and comment. Thanks a lot ๐Ÿ™