Array en Solidity
Array
Siguiendo con el anterior tutorial, en el que modificamos un contrato inteligente que desarrollamos en este post, para poder almacenar nuestro número favorito, ahora vamos a ver cómo utilizar la estructura del Array.
Su función en almacenar una serie de datos del mismo tipo juntos. Es similar al Mapping con la diferencia de que en el array los elementos no tienen identificador. En Solidity pueden ser de tamaño fijo o dinámico, como veremos a continuación.
// Array dinámico SIN inicialización
uint[] public arr;
// Array dinámico CON inicialización
uint[] public arr2 = [1, 2, 3];
/* Array fijo SIN inicialización
Por defecto sus valores serán ceros */
uint[5] public myFixedSizeArr; // [0, 0, 0, 0, 0]
// Array fijo CON inicialización
uint[2] public myFixedSizeArr2 = [4, 5];
Tecnologías
El stack de versiones que se usarán para el desarrollo del proyecto es el siguiente.
- Node (16.18.1)
- Npm (9.2.0)
- Typescript (4.8.4)
Objetivo
Vamos a modificar nuestro Smart Contract para que cada dirección tenga que establecer dos números favoritos en vez de uno.
Modificación FavoriteNumber
Nuestro struct FavoriteNumberData ahora almacenará el timestamp y un array de tamaño 2 para los números favoritos.
struct FavoriteNumberData {
uint[2] favoriteNumbers;
uint lastUpdate;
}
La función setFavoriteNumbers ahora recibirá por parámetro un array de tamaño 2, y se validarán ambos números para que no sean mayores a 100 (cumpliendo el desarrollo que hicimos anteriormente).
function setFavoriteNumbers(uint[2] calldata favoriteNumbers) external {
if (favoriteNumbers[0] > 100) {
revert FavoriteNumber__NumberMustBeLower(favoriteNumbers[0]);
}
if (favoriteNumbers[1] > 100) {
revert FavoriteNumber__NumberMustBeLower(favoriteNumbers[1]);
}
s_addressToFavoriteNumbersData[msg.sender] = FavoriteNumberData({
favoriteNumbers: favoriteNumbers,
lastUpdate: block.timestamp
});
}
Y, por último, la función getFavoriteNumbers. Que ahora retornará un array de tamaño 2.
function getFavoriteNumbers(address favoriteNumberAddress) public view returns (uint[2] memory) {
return s_addressToFavoriteNumbersData[favoriteNumberAddress].favoriteNumbers;
}
Quedando el contrato de la siguiente manera.
FavoriteNumber.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
error FavoriteNumber__NumberMustBeLower(uint favoriteNumber);
contract FavoriteNumber {
struct FavoriteNumberData {
uint[2] favoriteNumbers;
uint lastUpdate;
}
mapping(address => FavoriteNumberData) private s_addressToFavoriteNumbersData;
constructor() { }
function setFavoriteNumbers(uint[2] calldata favoriteNumbers) external {
if (favoriteNumbers[0] > 100) {
revert FavoriteNumber__NumberMustBeLower(favoriteNumbers[0]);
}
if (favoriteNumbers[1] > 100) {
revert FavoriteNumber__NumberMustBeLower(favoriteNumbers[1]);
}
s_addressToFavoriteNumbersData[msg.sender] = FavoriteNumberData({
favoriteNumbers: favoriteNumbers,
lastUpdate: block.timestamp
});
}
function getFavoriteNumbers(address favoriteNumberAddress) public view returns (uint[2] memory) {
return s_addressToFavoriteNumbersData[favoriteNumberAddress].favoriteNumbers;
}
function getFavoriteNumbersLastUpdate(address favoriteNumberAddress) public view returns (uint) {
return s_addressToFavoriteNumbersData[favoriteNumberAddress].lastUpdate;
}
}
Test
Ahora solo nos queda actualizar los tests para comprobar que los cambios nuevos funcionan correctamente. A continuación os dejo los tests actualizados.
FavoriteNumber.test.ts
import { assert, expect } from 'chai';
import { deployments, ethers, getNamedAccounts, network } from 'hardhat';
import { mine } from '@nomicfoundation/hardhat-network-helpers';
import { FavoriteNumber } from '../typechain-types';
// Only execute tests in local environment
if (!['hardhat', 'localhost'].includes(network.name)) {
describe.skip;
} else {
describe('FavoriteNumber Unit Tests', () => {
let favoriteNumberContract: FavoriteNumber; // FavoriteNumber contract
let deployer: string; // Account from alias 'deployer'
const deployerFavoriteNumbers: [number, number] = [10, 20]; // Deployer favorite numbers
beforeEach(async () => {
// Gets deployer account
deployer = (await getNamedAccounts()).deployer;
// Executes deploy scripts with given tags to prepare test environment
await deployments.fixture(['favorite-number']);
// Gets FavoriteNumber contract
favoriteNumberContract = await ethers.getContract('FavoriteNumber');
});
it('Initial favorite numbers by default', async () => {
// Gets default favorite numbers
const favoriteNumbers = (await favoriteNumberContract.getFavoriteNumbers(deployer)).toString();
assert.equal(favoriteNumbers, '0,0');
});
it('Initial favorite numbers last update by default', async () => {
// Gets default favorite numbers last update
const favoriteNumberLastUpdate = (await favoriteNumberContract.getFavoriteNumbersLastUpdate(deployer)).toString();
assert.equal(favoriteNumberLastUpdate, '0');
});
it('Set and get favorite numbers', async () => {
// Updates favorite numbers
await favoriteNumberContract.setFavoriteNumbers(deployerFavoriteNumbers);
// Gets updated favorite numbers
const updatedFavoriteNumbers = (await favoriteNumberContract.getFavoriteNumbers(deployer)).toString();
assert.equal(deployerFavoriteNumbers.join(','), updatedFavoriteNumbers);
});
it('Set favorite numbers and get its last update', async () => {
// Updates favorite numbers
const txReceipt = await favoriteNumberContract.setFavoriteNumbers(deployerFavoriteNumbers);
// Gets transaction block timestamp
const txBlockTimestamp = (await ethers.provider.getBlock(txReceipt.blockNumber!)).timestamp;
// Mines blocks
const blocksToMine = 5;
await mine(blocksToMine);
// Gets favorite numbers last update
const favoriteNumbersLastUpdate = (await favoriteNumberContract.getFavoriteNumbersLastUpdate(deployer)).toString();
assert.equal(`${txBlockTimestamp}`, favoriteNumbersLastUpdate);
// Gets timestamp from new last block
const latestBlockTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
assert.equal(`${txBlockTimestamp + blocksToMine}`, `${latestBlockTimestamp}`);
});
it('Set and get favorite numbers for multiple users', async () => {
// Deployer updates favorite numbers
await favoriteNumberContract.setFavoriteNumbers(deployerFavoriteNumbers);
// User updates favorite numbers
const user = (await ethers.getSigners())[1];
const userFavoriteNumbers: [number, number] = [50, 70];
favoriteNumberContract.connect(user).setFavoriteNumbers(userFavoriteNumbers);
// Tester updates favorite numbers
const tester = (await ethers.getSigners())[2];
const testerFavoriteNumbers: [number, number] = [89, 98];
favoriteNumberContract.connect(tester).setFavoriteNumbers(testerFavoriteNumbers);
// Gets deployer updated favorite numbers
const deployerUpdatedFavoriteNumbers = (await favoriteNumberContract.getFavoriteNumbers(deployer)).toString();
// Gets user updated favorite numbers
const userUpdatedFavoriteNumbers = (await favoriteNumberContract.getFavoriteNumbers(user.address)).toString();
// Gets tester updated favorite numbers
const testerUpdatedFavoriteNumbers = (await favoriteNumberContract.getFavoriteNumbers(tester.address)).toString();
assert.equal(deployerFavoriteNumbers.join(','), deployerUpdatedFavoriteNumbers);
assert.equal(userFavoriteNumbers.join(','), userUpdatedFavoriteNumbers);
assert.equal(testerFavoriteNumbers.join(','), testerUpdatedFavoriteNumbers);
});
it('Cannot set invalid favorite numbers', async () => {
// Invalid first favorite number
const invalidFirstFavoriteNumbers: [number, number] = [101, 99];
// Tries to update favorite numbers
const txFirstReceipt = favoriteNumberContract.setFavoriteNumbers(invalidFirstFavoriteNumbers);
await expect(txFirstReceipt).to.be.revertedWithCustomError(favoriteNumberContract, 'FavoriteNumber__NumberMustBeLower');
// Invalid second favorite number
const invalidSecondFavoriteNumbers: [number, number] = [99, 101];
// Tries to update favorite numbers
const txSecondReceipt = favoriteNumberContract.setFavoriteNumbers(invalidSecondFavoriteNumbers);
await expect(txSecondReceipt).to.be.revertedWithCustomError(favoriteNumberContract, 'FavoriteNumber__NumberMustBeLower');
});
});
}
Perfecto. Ahora ejecutamos los tests.
npx hardhat test
> FavoriteNumber Unit Tests
> √ Initial favorite numbers by default
> √ Initial favorite numbers last update by default
> √ Set and get favorite numbers
> √ Set favorite numbers and get its last update
> √ Set and get favorite numbers for multiple users
> √ Cannot set invalid favorite numbers
> 6 passing (1s)
¡Genial! Nuestro contrato ahora almacena 2 números favoritos por cada dirección que interactúa con él 🥳
Github
Todo el código fuente lo puedes encontrar aquí.
Frontend Engineer en Reveni.
Autodidacta, apasionado de las nuevas tecnologías y de los proyectos DIY.