Интеграция смарт-контракта Solidity и Hedera Hashgraph Javascript SDK

У меня возникла проблема с кодовой базой, которая раньше работала нормально, но теперь работает не так, как ожидалось.

Контекст Смарт-контракт JobPost предоставляет децентрализованную платформу для размещения объявлений о вакансиях, позволяя пользователям создавать, обновлять и получать списки вакансий. Он вводит два события, JobPostCreated и JobPostUpdated, для отправки уведомлений при создании и обновлении заданий, повышая прозрачность контракта и предоставляя хуки для автономных приложений для реагирования на эти изменения.

Javascript-скрипт Hashgraph: Код JavaScript взаимодействует с сетью Hedera для развертывания и взаимодействия с контрактом JobPost.

Проблема Моя проблема в том, что когда я пытаюсь опубликовать новое задание, оно говорит, что задание было успешно создано, но всякий раз, когда я пытаюсь получить код JavaScript, он возвращает пустой элемент, а это означает, что кажется, что задание не было успешно опубликовано. блокчейн с его параметрами.

Фрагменты исходного кода

Смарт-контракт:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

pragma experimental ABIEncoderV2;

contract JobPost {
    struct Job {
        uint256 jobId;
        string jobTitle;
        string paymentMethod;
        string accountId;
        address account;
    }

    enum paymentMethods {
        CASH,
        DEBIT,
        CREDIT,
        CRYPTO
    }

    event JobPostCreated(
        uint256 jobId,
        string jobTitle,
        string paymentMethod,
        string accountId,
        address account
    );

    event JobPostUpdated(
        uint256 _jobId,
        string _jobTitle,
        string _paymentMethod,
        address _account,
        string _accountId
    );

    // mapping(uint => Job) JobsByIndex;
    mapping(address => Job[]) JobsByAccount;
    uint256 public jobCounter = 0;

    constructor() {}

    function newJobPostByAccount(
        string memory _jobTitle,
        string memory _paymentMethod,
        string memory _accountId
    ) public {
        jobCounter++;
        JobsByAccount[msg.sender].push(
            Job({
                jobId: jobCounter,
                jobTitle: _jobTitle,
                paymentMethod: _paymentMethod,
                accountId: _accountId,
                account: msg.sender
            })
        );

        emit JobPostCreated(
            jobCounter,
            _jobTitle,
            _paymentMethod,
            _accountId,
            msg.sender
        );
    }

    function getOneJobPostByAccount2(
        address _account,
        uint256 _jobId
    )
        public
        view
        returns (
            address account,
            string memory accountId,
            uint256 jobId,
            string memory jobTitle,
            string memory paymentMethod
        )
    {
        // require(_account <= jobCounter);
        Job[] storage jobs = JobsByAccount[_account];
        for (uint256 index = 0; index < jobs.length; index++) {
            if (jobs[index].jobId == _jobId) {
                return (
                    jobs[index].account,
                    jobs[index].accountId,
                    jobs[index].jobId,
                    jobs[index].jobTitle,
                    jobs[index].paymentMethod
                );
            }
        }
    }

    function getOneJobPostByAccount(
        address _account,
        uint256 _jobId
    ) public view returns (Job memory job) {
        // require(_account <= jobCounter);
        Job[] storage jobs = JobsByAccount[_account];
        for (uint256 index = 0; index < jobs.length; index++) {
            if (jobs[index].jobId == _jobId) {
                return (jobs[index]);
            }
        }
        // return (0,"","",0x0);
    }

    function updateJobPostByAccount(
        uint256 _jobId,
        string memory _jobTitle,
        string memory _paymentMethod,
        address _account,
        string memory _accountId
    ) public {
        Job[] storage jobs = JobsByAccount[_account];
        require(_jobId <= jobCounter);
        // require(jobs, "This user does not exist");
        require(jobs.length > 0, "No Job post exists for this user");
        for (uint256 i = 0; i < jobs.length; i++) {
            if (jobs[i].jobId == _jobId) {
                jobs[i].jobTitle = _jobTitle;
                jobs[i].paymentMethod = _paymentMethod;
                jobs[i].account = _account;
                jobs[i].accountId = _accountId;
            }
        }
        emit JobPostUpdated(
            _jobId,
            _jobTitle,
            _paymentMethod,
            _account,
            _accountId
        );
    }

    function getAllJobsByAccount(
        address _account
    ) public view returns (Job[] memory) {
        Job[] storage jobs = JobsByAccount[_account];
        // require(jobs.length > 0, "No Job post exists for this user");
        if (jobs.length > 0) {
            return jobs;
        } else {
            Job[] memory emptyJobs;
            return emptyJobs;
        }
    }
}

Javascript-код

console.clear();
require("dotenv").config({ path: "../.env" });

const {
  Client,
  AccountId,
  PrivateKey,
  Hbar,
  FileCreateTransaction,
  FileAppendTransaction,
  ContractCallQuery,
  ContractCreateTransaction,
  ContractFunctionParameters,
  ContractExecuteTransaction,
} = require("@hashgraph/sdk");
const fs = require("fs");

const Web3 = require("web3");
const web3 = new Web3();

// console.info(process.env.HEDERA_ACCOUNT_ID);
const operatorId = process.env.HEDERA_ACCOUNT_ID;
const operatorKey = PrivateKey.fromStringECDSA(process.env.HEDERA_PRIVATE_KEY);

const bytecode = require("../build/contracts/JobPost.json", "utf8").bytecode;
const abi = require("../build/contracts/JobPost.json", "utf8").abi;
let bytecodeFileId;

const client = Client.forTestnet().setOperator(operatorId, operatorKey);

// Deploy the smart Contract to Hedera (createContractBytecodeFileId + uploadByteCode + instantiateContract)
await deploySmartContract(client, operatorKey, bytecode, 3000000, 10);

// Create a New Job Post
await newJobPostByAccount(
  client,
  contractId,
  operatorId,
  "Job Post #1",
  "CASH"
);

// Get One Job Post By Account
await getOneJobPostByAccount2(
  client,
  "getOneJobPostByAccount2",
  contractId,
  operatorId,
  2
);

async function newJobPostByAccount(
  _client,
  _contractId,
  _accountId,
  _title,
  _paymentMethod,
  _gas = 3000000
) {
  const contractExecTx = await new ContractExecuteTransaction()
    .setContractId(_contractId)
    .setGas(_gas)
    .setFunction(
      "newJobPostByAccount",
      new ContractFunctionParameters()
        .addString(_title)
        .addString(_paymentMethod)
        .addString(_accountId)
    );
  const contractExecSubmit = await contractExecTx.execute(_client);
  const contractExecRx = await contractExecSubmit.getReceipt(_client);
  console.info(contractExecRx);
  console.info(`\nCREATING A NEW JOB POST ======= \n`);
  console.info(`- New Job Post Created: ${contractExecRx.status.toString()}`);
  console.info(`\nRETRIEVE A JOB POST BY ACCOUNT ======= \n`);
}

async function decodeFunctionResult(functionName, resultAsBytes) {
  const functionAbi = abi.find(
    (func) => func.name === functionName && func.type === "function"
  );
  const functionParameters = functionAbi.outputs;
  const resultHex = "0x".concat(Buffer.from(resultAsBytes).toString("hex"));
  const result = await web3.eth.abi.decodeParameters(
    functionParameters,
    resultHex
  );
  return result;
}

async function getOneJobPostByAccount2(
  _client,
  _functionName,
  _contractId,
  _accountId,
  _jobId,
  _gas = 100000,
  _queryPaymentInHBars = 2
) {
  const contractQuery = await new ContractCallQuery()
    //Set the gas for the query
    .setGas(_gas)
    //Set the contract ID to return the request for
    .setContractId(_contractId)
    //Set the contract function to call
    .setFunction(
      _functionName,
      new ContractFunctionParameters()
        .addAddress(AccountId.fromString(_accountId).toSolidityAddress())
        .addUint256(_jobId)
    )
    //Set the query payment for the node returning the request
    //This value must cover the cost of the request otherwise will fail
    .setQueryPayment(new Hbar(_queryPaymentInHBars));

  //Submit to a Hedera network
  const contractExec = await contractQuery.execute(_client);
  // console.info(contractExec);
  console.info(`\nRETRIEVE A JOB POST BY ACCOUNT ======= \n`);
  console.info(
    `- New Job Post Account Owner: ${await contractExec.getString(0)}`
  );
  // console.info(`- New Job Post Created2: ${await contractExec.getString(1)}`)
  console.info(`- New Job Post Title: ${await contractExec.getString(2)}`);
  console.info(
    `- New Job Post Payment Method: ${await contractExec.getString(3)}`
  );
  const res = await decodeFunctionResult(_functionName, contractExec.bytes);
  console.info(res[0]);
  return res[0];
}

Консольный вывод

Ниже приведена ссылка на развернутый контракт и то, что выводит моя консоль.

👉 https://hashscan.io/testnet/contract/0.0.3767523

Я протестировал контракт с Remix, и он работает как положено; например, я могу получить информацию, используя 0xdfad8138e04c7932c26c0a37d3711fa9165704f1 и 1 или 2 в качестве параметра jobId или 0xe0b73f64b0de6032b193648c08899f20b5a6141d и 3 в качестве jobId (отправленное мной сообщение о вакансии).

Giuseppe Bertone 24.03.2024 15:34

Проблема, похоже, в коде Javascript; в частности, это может быть преобразование между идентификатором учетной записи и адресом EVM (проверьте, возвращает ли AccountId.fromString(_accountId).toSolidityAddress()0xdfad8138e04c7932c26c0a37d3711fa9165704f1 в вашем случае, или в декодировании возвращаемых значений: возможно, вы используете версию web3 (какую это версию?) пока не может декодировать ABIEncoderV2. Попробуйте распечатать объект contractExec, чтобы мы могли проверить, содержит ли он что-нибудь :).

Giuseppe Bertone 24.03.2024 15:34

@ Джузеппе Бертоне. Я перепробовал все подряд. Ничего не работает. Могу ли я отправить вам весь свой код JavaScript и смарт-контракт, чтобы вы могли на него взглянуть? Я в таком отчаянии, братан. Этот вопрос запаздывает уже 2 недели, с тех пор как я снова начал над ним работать. Пожалуйста, помоги мне, братан

AllJs 25.03.2024 04:35

Конечно, без проблем. Будет проще проверить и исправить это, если вы разместите весь репозиторий на GitHub (Gitlab или аналогичный). Просто не забудьте обновить вопрос, добавив новые детали.

Giuseppe Bertone 25.03.2024 05:47

Кстати, в 99% проблема заключается в использовании AccountId.fromString(_accountId).toSolidityAddress(). Я думаю, если вы измените это на свой адрес EVM (0xdfad8138e04c7932c26c0a37d3711fa9165704f1), все должно работать как положено. toSolidityAddress функция может вводить в заблуждение, поскольку она работает только для собственной учетной записи, отличной от EVM, преобразуя идентификатор учетной записи в шестнадцатеричный (например, так: hashscan.io/testnet/account/0.0.3774165 обратите внимание на множество нулей в адресе EVM ). В вашем случае вместо этого вы используете собственную учетную запись EVM, поэтому эта функция не сможет вернуть ваш фактический адрес.

Giuseppe Bertone 25.03.2024 06:04

@ Джузеппе Бертоне. Ты гениальный человек. Ты был прав.

AllJs 25.03.2024 07:40

Круто, рад, что нам удалось это решить. Я подготовлю ответ по результатам нашей дискуссии, чтобы и другие могли избежать этой ловушки. Также было бы полезно добавить предупреждение об этой функции в SDK, я открою проблему на Github.

Giuseppe Bertone 25.03.2024 14:27
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
7
94
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема заключается в использовании AccountId.fromString(_accountId).toSolidityAddress(). Код JavaScript будет работать должным образом, если вы измените его, указав фактический адрес EVM (0xdfad8138e04c7932c26c0a37d3711fa9165704f1).

Функция toSolidityAddress()статична и не имеет доступа к сети. Поэтому он работает только для собственных учетных записей, отличных от EVM, преобразуя идентификатор учетной записи в шестнадцатеричные значения.

Например, это 0.0.5102042 — это просто случайная учетная запись с ключом ED25519, а ее адрес EVM получается из идентификатора учетной записи; это 0x00000000000000000000000000000000004dd9da, и это можно вычислить в автономном режиме.

В вашем случае вместо этого вы используете собственную учетную запись EVM, поэтому адрес получается из открытого ключа, а не идентификатора учетной записи, поэтому функцию toSolidityAddress() нельзя использовать, поскольку она вернет неправильное значение.

По поводу изменения поведения - раньше оно работало, а теперь нет - может быть связано с использованием в первый раз родной учетной записи, отличной от EVM. В этом случае несовместимости toSolidityAddress() не возникло.

Отличное объяснение @Джузеппе. Большое спасибо. Я добавлю еще один ответ с обновленным фрагментом кода с рабочим примером, но ваш ответ я отметил как правильный. Я думаю, что Hedera следует очень четко разграничить, когда использовать адреса EVM или адреса, не относящиеся к EVM, и где именно их использовать, чтобы не было путаницы.

AllJs 25.03.2024 21:47

Другие вопросы по теме