Возврат структуры и чтение через Web3

Я храню данные в своем контракте, используя сопоставление структур.

Для примера предположим, что я храню информацию о сотрудниках (имя/адрес/зарплата), сопоставленную с их идентификатором сотрудника.

Через веб-интерфейс я хотел бы указать идентификатор сотрудника и вызвать функцию в моем контракте (используя Web3.JS), которая возвращает информацию о сотруднике.

Как я могу получить доступ к данным, если возвращается структура. Это возможно?

Ответы (5)

Резюме

Возвращайте поля структуры как отдельные возвращаемые переменные.

Изменить: с 2021 года можно напрямую вернуть структуру. Смотрите этот ответ .


Пример

Я запускаю этот код в своем локальном блокчейне разработки, используя следующую команду:

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

Ваш веб-интерфейс должен иметь возможность отправлять транзакции для вставки пользователей и вызывать функции для получения количества пользователей и информации о пользователе.


Образец договора

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);
    }
}

Сгладить исходный код

Я использую stripCrLfметод (из Как загрузить исходный файл Solidity в geth ) для преобразования отформатированного исходного кода в одну строку, которую можно вставить в gethконсоль. В качестве альтернативы найдите веб-страницу, которая удалит разрывы строк из вашего кода. Затем назначьте свой код переменной 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); }}'

Вставьте контракт в блокчейн

Скомпилировать код:

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

Загрузите код в блокчейн:

> 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);
      }
    }
  }
)

Подождите, пока не появится следующее сообщение о том, что контракт был добыт:

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]

Вставить пользователей

И вот мы добавляем в контракт 2 пользователей:

> 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"

Получить данные

Получим количество пользователей:

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

Получим информацию для первого пользователя:

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

И второй пользователь:

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

> user2
[234, "User 234", "234 drive way, the uncentralised kingdom", 200]
Ух ты! Это невероятно полезно. Большое спасибо за ваш подробный ответ. Я очень ценю это!
Я получаю BigNumber, а не число Base 16... как я могу вернуть строковые атрибуты с помощью вашего решения?? (я использую web3 0.16)
@BokkyPooBah та же проблема. Получение такой же большой проблемы с номером. Любая помощь в этом?

Начиная с Solidity 0.8.0 вы можете напрямую возвращать структуру. Вот простой пример договора:

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];
    }
}

Если вы должны использовать любую версию Solidity <0.8.0, вам нужно добавить это, чтобы приведенный выше код работал:

pragma experimental ABIEncoderV2;      

Вот пример из моей работы:

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;
}

Это возвращает

[ 
    '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' 
]

Не самый читаемый, но это жизнь!

Вы никогда не писали заявление о возврате? Возвращается ли он автоматически, поскольку у него одинаковые имена переменных?

Вы можете вернуть структуру только из функции для внутренних вызовов в контракте.

Одним из возможных подходов было бы вернуть содержимое структуры в виде массива.

Но это может работать только в том случае, если все члены структуры относятся к одному типу, не так ли? Из приведенного выше примера зарплаты нельзя вернуть «строковое» имя пользователя и «uint» зарплату в одном и том же массиве.

Используя pragma experimental ABIEncoderV2вы можете вернуть структуру и прочитать ее через web3js.

Вот простой пример договора:

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];
    }
}

Код JavaScript для чтения структуры:

Example.methods.get().call({from: userAddress})
.then(function(result){
  console.log(result[0]);
  console.log(result[1]);
});
Это хороший пример, которому я следую. Интересно, должно ли отображение здесь отображать (адрес => Магазин) общественные покупки; а не string=> Store с учетом более поздних покупок [msg.sender] используется?
Да, это должен быть адрес => магазин, вы правы