Oráculos en Meter: Witnet

En este tutorial vamos a aprender cómo funciona Oráculos en Meter Witnet, partner de Meter. Para esta demostración usaremos Witnet para que nos devuelva un número aleatorio. Como ya sabemos, en Solidity no podemos generar un número aleatorio, por lo que es imprescindible el uso de un oráculo. we are going to learn how Oracles work in Meter Witnet, Meter’s partner. For this demo, we will use Witnet to return a random number. As we already know, in Solidity we cannot generate a random number, so the use of an oracle is essential.

Let’s go to the mess, first, we will create a project with Hardhat and as always we select the default options.

npx hardhat init

El siguiente paso es importar las librerías de Witnet.

yarn add witnet-solidity-bridge

Creamos el siguiente contrato en contracts/witnetPrueba.sol

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "hardhat/console.sol";
import "witnet-solidity-bridge/contracts/interfaces/IWitnetRandomness.sol";

contract WitnetPrueba {
    uint32 public randomness;
    uint256 public latestRandomizingBlock;
    IWitnetRandomness immutable public witnet;
    
    /// @param _witnetRandomness Address of the WitnetRandomness contract.
    // Meter Mainnet: 0xE189B1D26dAAB45cd344452f29Db8E93B5C7FaF1
    // Meter Testnet: 0xa254632ae454bae18ff0221c2040aa387f044f0e
    // Ethereum Mainnet: 0x894907c7Ab64C1092620B5c8Ba039BB6E611eba8
    // Ethereum Goerli: 0x6Eb87EcCe6218Cd0e97299331D2aa5d2e53da5cD
    // Ethereum Rinkeby: 0x50F742Fbf9a445AE6B2136F5987414A4c5aeE921
    constructor (IWitnetRandomness _witnetRandomness) {
        assert(address(_witnetRandomness) != address(0));
        witnet = _witnetRandomness;
    }
    
    receive () external payable {}

    function requestRandomNumber() external payable {
        latestRandomizingBlock = block.number;
        uint _usedFunds = witnet.randomize{ value: msg.value }();
        if (_usedFunds < msg.value) {
            payable(msg.sender).transfer(msg.value - _usedFunds);
        }
    }
    
    function fetchRandomNumber() external {
        assert(latestRandomizingBlock > 0);
        randomness = witnet.random(type(uint32).max, 0, latestRandomizingBlock);
    }
}

Para revisar que el paquete de Witnet se ha instalado correctamente ejecutamos el siguiente comando.

npx hardhat compile

Creamos el fichero scripts/deploy.js, el cual usaremos para desplegar el contrato, con el siguiente contenido.

const hre = require("hardhat");

async function main() {
  const WitnetPrueba = await hre.ethers.getContractFactory("WitnetPrueba");
  const witnetPrueba = await WitnetPrueba.deploy("0xa254632ae454bae18ff0221c2040aa387f044f0e");

  await witnetPrueba.deployed();

  console.log("WitnetPrueba desplegado en:", witnetPrueba.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

Modificamos el contenido del fichero hardhat.config.js para añadir la red testnet y mainnet de Meter.

require("@nomiclabs/hardhat-waffle");

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

const METER_TESTNET_PRIVATE_KEY = process.env.METER_TESTNET_PRIVATE_KEY;
const METER_MAINNET_PRIVATE_KEY = process.env.METER_MAINNET_PRIVATE_KEY;

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.4",
  networks: {
    meter_testnet: {
      url: "https://rpctest.meter.io",
      chainId: 83,
      accounts: [`${METER_TESTNET_PRIVATE_KEY}`]
    },
    meter_mainnet: {
      url: "https://rpc.meter.io",
      chainId: 82,
      accounts: [`${METER_MAINNET_PRIVATE_KEY}`]
    }
  } 
};

Debemos recordar exportar las siguientes variables con la clave privada correspondiente.

export METER_TESTNET_PRIVATE_KEY=XXX
export METER_MAINNET_PRIVATE_KEY=XXX

Ya lo tenemos todo listo para desplegar el contrato, ejecutamos el siguiente comando.

npx hardhat run --network meter_testnet scripts/deploy.js
Compiled 1 Solidity file successfully
WitnetPrueba desplegado en: 0x31220E38e57fB5B3E3dE2D44C41706e1D2EA4469

Podemos ver el resultado en el explorador de Meter: https://scan-warringstakes.meter.io/tx/0x6961ec1818dae70026bdbfcf67e3fc0955e6978797eaaf160a30166ca1bee4aa

Ahora lo que haremos será conectarnos al contrato que hemos desplegado en la testnet de Meter.

npx hardhat console --network meter_testnet
const WitnetPrueba = await ethers.getContractFactory('WitnetPrueba');
const witnetPrueba = await WitnetPrueba.attach('0x31220E38e57fB5B3E3dE2D44C41706e1D2EA4469');
const accounts = await hre.ethers.getSigners();

Ejecutaremos el siguiente comando para invocar al método que generará el número aleatorio.

await witnetPrueba.connect(accounts[0]).requestRandomNumber({value: ethers.utils.parseEther("0.05")});

La transacción será revertida ya que hemos enviado una cantidad de MTR insuficientes (0.05) para que el oráculo la procese. Podemos ver la transacción fallida en el explorador: https://scan-warringstakes.meter.io/tx/0xcbb5ff9ba144957e0a1b0f28d30b572d44ebf165433ab4a59f8c920d19c34a97

reverted: execution reverted (UsingWitnet: reward too low): UsingWitnet: reward too low

Volvemos a ejecutar el anterior comando, pero esta vez con 0.10 MTR.

await witnetPrueba.connect(accounts[0]).requestRandomNumber({value: ethers.utils.parseEther("0.1")});

Ahora sí, la transacción ha sido aceptada y Witnet en breve nos generará el número aleatorio: https://scan-warringstakes.meter.io/tx/0x683ca05df84efcb16ca97b7e5226554f65408b251626d3fee79b494c2daca2cc

Ejecutamos el siguiente comando para recibir el número aleatorio.

await witnetPrueba.fetchRandomNumber();

Debemos esperar entre 5 y 10 minutos para que Witnet genere el número, de lo contrario recibiremos como respuesta este error.

Uncaught:
Error: cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (reason="evm: execution reverted: WitnetRandomness: pending randomize", method="estimateGas", transaction={"from":"0x4BF1F7f81b0C48dcF3f7a19ed2e91aFB35f9A70d","to":"0x6BF6898cA58990Da03981CB2135a18D207D5b517","data":"0x921b9763","accessList":null}, error={"name":"ProviderError","_stack":"ProviderError: evm: execution reverted: WitnetRandomness: pending randomize\n    at HttpProvider.request (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/hardhat/src/internal/core/providers/http.ts:88:21)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)\n    at EthersProviderWrapper.send (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)","code":3,"_isProviderError":true,"data":"0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000235769746e657452616e646f6d6e6573733a2070656e64696e672072616e646f6d697a650000000000000000000000000000000000000000000000000000000000"}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.7.2)
    at step (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:48:23)
    at EthersProviderWrapper.<anonymous> (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/@ethersproject/providers/src.ts/json-rpc-provider.ts:642:20)
    at checkError (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/@ethersproject/providers/src.ts/json-rpc-provider.ts:78:20)
    at Logger.throwError (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/@ethersproject/logger/src.ts/index.ts:281:20)
    at Logger.makeError (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/@ethersproject/logger/src.ts/index.ts:269:28) {
  reason: 'evm: execution reverted: WitnetRandomness: pending randomize',
  code: 'UNPREDICTABLE_GAS_LIMIT',
  method: 'estimateGas',
  transaction: {
    from: '0x4BF1F7f81b0C48dcF3f7a19ed2e91aFB35f9A70d',
    to: '0x6BF6898cA58990Da03981CB2135a18D207D5b517',
    data: '0x921b9763',
    accessList: null
  },
  error: ProviderError: evm: execution reverted: WitnetRandomness: pending randomize
      at HttpProvider.request (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/hardhat/src/internal/core/providers/http.ts:88:21)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at EthersProviderWrapper.send (/Users/antonimassomola/Development/codigweb3.com/tutoriales/witnet-oracle/node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)
}
> 

Pasados 5 minutos, ejecutando de nuevo el anterior comando, la respuesta ya debería ser satisfactoria.

{
  hash: '0x0d09023827e7628a6d21db2541efbe0069d2245903fe94b2f7d6dcf1b5756936',
  type: 0,
  accessList: null,
  blockHash: '0x01dd45119d51718d57a5d8aa873fd240259af8b7bd3df88faa7547b04303150b',
  blockNumber: 31278353,
  transactionIndex: 0,
  confirmations: 1,
  from: '0x4BF1F7f81b0C48dcF3f7a19ed2e91aFB35f9A70d',
  gasPrice: BigNumber { value: "1" },
  gasLimit: BigNumber { value: "269842" },
  to: '0x31220E38e57fB5B3E3dE2D44C41706e1D2EA4469',
  value: BigNumber { value: "100000000000000000" },
  nonce: 102841868,
  data: '0x8678a7b2',
  r: '0x59bb4e62e3697ad5fcfbd7da572faad2ec924ec98025da983b9dcc033a2a5f2e',
  s: '0x019dd95f94fae345d83b568175bc1a9018e52c058bfdb4d25ff4f102205ba7f2',
  v: 201,
  creates: null,
  chainId: 83,
  wait: [Function (anonymous)]
}

El número aleatorio se ha generado y guardado en la variable randomness, para leer su contendido lo hacemos con este comando.

await witnetPrueba.randomness();
3115487150

Si queremos generar otro número aleatorio seguimos los mismos pasos.

await witnetPrueba.connect(accounts[0]).requestRandomNumber({value: ethers.utils.parseEther("0.1")});

network block skew detected; skipping block events (emitted=31278904 blockNumber31280598)
{
  hash: '0xdc4e12c9e579be5df32d68940ed9249e3a51be6db8d1498178869c049dd12acd',
  type: 0,
  accessList: null,
  blockHash: '0x01dd4ddcead82715e9868c53d30a27ad5e73217edffc98e99a8b69ce9a23cb4a',
  blockNumber: 31280604,
  transactionIndex: 0,
  confirmations: 2,
  from: '0x4BF1F7f81b0C48dcF3f7a19ed2e91aFB35f9A70d',
  gasPrice: BigNumber { value: "1" },
  gasLimit: BigNumber { value: "269842" },
  to: '0x31220E38e57fB5B3E3dE2D44C41706e1D2EA4469',
  value: BigNumber { value: "100000000000000000" },
  nonce: 1216912174,
  data: '0x8678a7b2',
  r: '0xb3b95b475e667d73a214e5160bc88c6a229cb8bb8c0499e7e71236c78d13af2a',
  s: '0x77e03b1e6b06950a5572ac4df5fb811ceb002261cff2f6b22736369b7f6ff762',
  v: 202,
  creates: null,
  chainId: 83,
  wait: [Function (anonymous)]
}

Como hemos podido comprobar, generar número aleatorios con el Oráculo Witnet para la red de Meter es muy sencillo. Para recibir ayuda técnica de Witnet podéis acceder a su Discord en: https://discord.gg/X4uurfP

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *