Devolviendo una estructura y leyendo a través de Web3

Estoy almacenando datos en mi contrato usando un mapeo de estructuras.

Por ejemplo, supongamos que estoy almacenando información del empleado (nombre/dirección/salario), mapeada por su ID de empleado.

A través de un front-end web, me gustaría poder especificar una identificación de empleado y llamar a una función en mi contrato (usando Web3.JS) que devuelve la información del empleado.

¿Cómo puedo acceder a los datos si se devuelve una estructura? ¿Es eso posible?

Respuestas (5)

Resumen

Devuelve los campos de la estructura como variables de retorno separadas.

Editar: a partir de 2021, es posible devolver la estructura directamente. Ver esta respuesta .


Ejemplo

Estoy ejecutando este código en mi cadena de bloques de desarrollo local usando el siguiente comando:

geth --datadir ~/devdata --dev --nodiscover \
  --mine --minerthreads 1 --port 30301      \
  --maxpeers 0 --verbosity 3 --rpc console

Su interfaz web debería poder enviar transacciones para insertar a los usuarios y llamar a las funciones para obtener la cantidad de usuarios y la información del usuario.


Contrato de muestra

contract SalaryInfo {
    struct User {
        uint salaryId;
        string name;
        string userAddress;
        uint salary;
    }
    User[] public users;

    function addUser(uint _salaryId, string _name, string _userAddress, uint _salary) public returns(uint) {
        users.length++;
        users[users.length-1].salaryId = _salaryId;
        users[users.length-1].name = _name;
        users[users.length-1].userAddress = _userAddress;
        users[users.length-1].salary = _salary;
        return users.length;
    }

    function getUsersCount() public constant returns(uint) {
        return users.length;
    }

    function getUser(uint index) public constant returns(uint, string, string, uint) {
        return (users[index].salaryId, users[index].name, users[index].userAddress, users[index].salary);
    }
}

Aplanar el código fuente

Uso el stripCrLfmétodo (de How to load Solidity source file into geth ) para transformar la fuente formateada en una sola línea que se puede insertar dentro de la gethconsola. Alternativamente, busque una página web que elimine los saltos de línea de su código. Luego asigne su código a una variable de JavaScript:

> var salaryInfoSource='contract SalaryInfo { struct User { uint salaryId; string name; string userAddress; uint salary; } User[] public users; function addUser(uint _salaryId, string _name, string _userAddress, uint _salary) public returns(uint) { users.length++; users[users.length-1].salaryId = _salaryId; users[users.length-1].name = _name; users[users.length-1].userAddress = _userAddress; users[users.length-1].salary = _salary; return users.length; } function getUsersCount() public constant returns(uint) { return users.length; } function getUser(uint index) public constant returns(uint, string, string, uint) { return (users[index].salaryId, users[index].name, users[index].userAddress, users[index].salary); }}'

Insertar contrato en la cadena de bloques

El compilar el código:

> var salaryInfoCompiled = web3.eth.compile.solidity(salaryInfoSource);

Cargue el código en la cadena de bloques:

> var salaryInfoContract = web3.eth.contract(salaryInfoCompiled.SalaryInfo.info.abiDefinition);
> var salaryInfo = salaryInfoContract.new({from:web3.eth.accounts[0], data: salaryInfoCompiled.SalaryInfo.code, gas: 1000000}, 
  function(e, contract) {
    if (!e) {
      if(!contract.address) {
        console.log("Contract transaction send: TransactionHash: " + 
          contract.transactionHash + " waiting to be mined...");
      } else {
        console.log("Contract mined! Address: " + contract.address);
        console.log(contract);
      }
    }
  }
)

Espere el siguiente mensaje para indicar que el contrato ha sido minado:

I0505 09:12:15.712867   27030 xeth.go:1026] Tx(0x7747500b881c8da44efbc3b5d1c2c762f1cd52d2dd74050edbfed10e51a29d8a) created: 0x0bb1d7a6b31f7a7e23e6d902bac0eb5f9c721c54
Contract transaction send: TransactionHash: 0x7747500b881c8da44efbc3b5d1c2c762f1cd52d2dd74050edbfed10e51a29d8a waiting to be mined...
...
Contract mined! Address: 0x0bb1d7a6b31f7a7e23e6d902bac0eb5f9c721c54
[object Object]

Insertar usuarios

Y aquí estamos agregando 2 usuarios al contrato:

> salaryInfo.addUser(123, "User 123", "123 drive way, the uncentralised kingdom", 100, {from:web3.eth.accounts[0], data: salaryInfoCompiled.SalaryInfo.code, gas: 500000});
"0x7c22797d6b7717beb398a65159b1009fba3bbc9e4917ee1584bed60ea74eac11"
> salaryInfo.addUser(234, "User 234", "234 drive way, the uncentralised kingdom", 200, {from:web3.eth.accounts[0], data: salaryInfoCompiled.SalaryInfo.code, gas: 500000});
"0xed197c9a6fbc70c19cc95bcdc6943e38736c080052e9e1a4f7562216d6de4c78"

Recuperar datos

Vamos a obtener el número de usuarios:

> var numberOfUsers = salaryInfo.getUsersCount();
undefined
> numberOfUsers
2

Vamos a obtener la información para el primer usuario:

> salaryInfo.getUser(0)
[123, "User 123", "123 drive way, the uncentralised kingdom", 100]

Y el segundo usuario:

> var user2 = salaryInfo.getUser(1);
undefined

> user2
[234, "User 234", "234 drive way, the uncentralised kingdom", 200]
¡Guau! Esto es increíblemente útil. Muchas gracias por tu profunda respuesta. ¡Realmente lo aprecio!
Obtengo un BigNumber, no un número Base 16... ¿cómo puedo devolver atributos de cadena con su tipo de solución? (Estoy usando web3 0.16)
@BokkyPooBah mismo problema. Obteniendo el mismo problema de números grandes. ¿Alguna ayuda en esto?

A partir de solidity 0.8.0, puede devolver una estructura directamente. Aquí hay un contrato de ejemplo simple:

pragma solidity ^0.8.0;
    
contract Example {
    struct Store {
        string id;         
        uint time;         
    }
 
    mapping (address => Store) public purchases;

    function set(string memory _id, uint _time) public returns(bool) {
        purchases[msg.sender].id = _id;
        purchases[msg.sender].time = _time;
        return true;
    }

    function get() public view returns(Store memory) {
        return purchases[msg.sender];
    }
}

Si debe usar cualquier versión de solidez <0.8.0, debe agregar esto para que funcione el código anterior:

pragma experimental ABIEncoderV2;      

Aquí hay un ejemplo de mi trabajo:

function getChannel(bytes32 channelId) returns(
    address addr0,
    address addr1,
    uint8 phase,
    uint challengePeriod,
    uint closingBlock,
    bytes state,
    uint sequenceNumber,
    bytes evidence0,
    bytes evidence1
) {
    addr0 = channels[channelId].addr0;
    addr1 = channels[channelId].addr1;
    phase = channels[channelId].phase;
    challengePeriod = channels[channelId].challengePeriod;
    closingBlock = channels[channelId].closingBlock;
    state = channels[channelId].state;
    sequenceNumber = channels[channelId].sequenceNumber;
    evidence0 = channels[channelId].evidence0;
    evidence1 = channels[channelId].evidence1;
}

esto vuelve

[ 
    '0xe7d3b0123a4f0294e06d212876ade6277b47f473',
    '0x3e4280efa3dd3014ca26022ac7dfe8e6c3070c67',
    { [String: '0'] s: 1, e: 0, c: [ 0 ] },
    { [String: '1'] s: 1, e: 0, c: [ 1 ] },
    { [String: '0'] s: 1, e: 0, c: [ 0 ] },
    '0x2222',
    { [String: '1'] s: 1, e: 0, c: [ 1 ] },
    '0x',
    '0x' 
]

No es el más legible, ¡pero así es la vida!

¿Nunca escribiste una declaración de devolución? ¿Regresa automáticamente ya que son los mismos nombres de variable?

Solo puede devolver una estructura de una función para llamadas internas en un contrato.

Un enfoque posible sería devolver el contenido de la estructura en una matriz.

Pero eso podría funcionar solo si todos los miembros de la estructura son del mismo tipo, ¿no? Del ejemplo de salario anterior, uno no puede devolver el nombre de usuario "string" y el salario "uint" en la misma matriz.

Usando pragma experimental ABIEncoderV2puede devolver una estructura y leerla a través de web3js.

Aquí hay un contrato de ejemplo simple:

pragma solidity ^0.5.12;
pragma experimental ABIEncoderV2;
    
contract Example {

    struct Store {
        string id;         
        uint time;         
    }
 
    mapping (address => Store) public purchases;

    function set(string memory _id, uint _time) public returns(bool) {
        purchases[msg.sender].id = _id;
        purchases[msg.sender].time = _time;
        return true;
    }

    function get() public view returns(Store memory) {
        return purchases[msg.sender];
    }
}

Código JavaScript para leer la estructura:

Example.methods.get().call({from: userAddress})
.then(function(result){
  console.log(result[0]);
  console.log(result[1]);
});
Este es un buen ejemplo que estoy siguiendo. Me pregunto si el mapeo aquí debería ser un mapeo (dirección => Tienda) compras públicas; en lugar de cadena => ¿Se está utilizando la tienda dada compras posteriores [msg.sender]?
Sí, debería ser la dirección => tienda, tienes razón