У меня возникла проблема с кодовой базой, которая раньше работала нормально, но теперь работает не так, как ожидалось.
Контекст
Смарт-контракт 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];
}
Консольный вывод
Ниже приведена ссылка на развернутый контракт и то, что выводит моя консоль.
Проблема, похоже, в коде Javascript; в частности, это может быть преобразование между идентификатором учетной записи и адресом EVM (проверьте, возвращает ли AccountId.fromString(_accountId).toSolidityAddress()0xdfad8138e04c7932c26c0a37d3711fa9165704f1 в вашем случае, или в декодировании возвращаемых значений: возможно, вы используете версию web3 (какую это версию?) пока не может декодировать ABIEncoderV2. Попробуйте распечатать объект contractExec, чтобы мы могли проверить, содержит ли он что-нибудь :).
@ Джузеппе Бертоне. Я перепробовал все подряд. Ничего не работает. Могу ли я отправить вам весь свой код JavaScript и смарт-контракт, чтобы вы могли на него взглянуть? Я в таком отчаянии, братан. Этот вопрос запаздывает уже 2 недели, с тех пор как я снова начал над ним работать. Пожалуйста, помоги мне, братан
Конечно, без проблем. Будет проще проверить и исправить это, если вы разместите весь репозиторий на GitHub (Gitlab или аналогичный). Просто не забудьте обновить вопрос, добавив новые детали.
Кстати, в 99% проблема заключается в использовании AccountId.fromString(_accountId).toSolidityAddress(). Я думаю, если вы измените это на свой адрес EVM (0xdfad8138e04c7932c26c0a37d3711fa9165704f1), все должно работать как положено. toSolidityAddress функция может вводить в заблуждение, поскольку она работает только для собственной учетной записи, отличной от EVM, преобразуя идентификатор учетной записи в шестнадцатеричный (например, так: hashscan.io/testnet/account/0.0.3774165 обратите внимание на множество нулей в адресе EVM ). В вашем случае вместо этого вы используете собственную учетную запись EVM, поэтому эта функция не сможет вернуть ваш фактический адрес.
@ Джузеппе Бертоне. Ты гениальный человек. Ты был прав.
Круто, рад, что нам удалось это решить. Я подготовлю ответ по результатам нашей дискуссии, чтобы и другие могли избежать этой ловушки. Также было бы полезно добавить предупреждение об этой функции в SDK, я открою проблему на Github.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Проблема заключается в использовании AccountId.fromString(_accountId).toSolidityAddress(). Код JavaScript будет работать должным образом, если вы измените его, указав фактический адрес EVM (0xdfad8138e04c7932c26c0a37d3711fa9165704f1).
Функция toSolidityAddress()статична и не имеет доступа к сети. Поэтому он работает только для собственных учетных записей, отличных от EVM, преобразуя идентификатор учетной записи в шестнадцатеричные значения.
Например, это 0.0.5102042 — это просто случайная учетная запись с ключом ED25519, а ее адрес EVM получается из идентификатора учетной записи; это 0x00000000000000000000000000000000004dd9da, и это можно вычислить в автономном режиме.
В вашем случае вместо этого вы используете собственную учетную запись EVM, поэтому адрес получается из открытого ключа, а не идентификатора учетной записи, поэтому функцию toSolidityAddress() нельзя использовать, поскольку она вернет неправильное значение.
По поводу изменения поведения - раньше оно работало, а теперь нет - может быть связано с использованием в первый раз родной учетной записи, отличной от EVM. В этом случае несовместимости toSolidityAddress() не возникло.
Отличное объяснение @Джузеппе. Большое спасибо. Я добавлю еще один ответ с обновленным фрагментом кода с рабочим примером, но ваш ответ я отметил как правильный. Я думаю, что Hedera следует очень четко разграничить, когда использовать адреса EVM или адреса, не относящиеся к EVM, и где именно их использовать, чтобы не было путаницы.
Я протестировал контракт с Remix, и он работает как положено; например, я могу получить информацию, используя
0xdfad8138e04c7932c26c0a37d3711fa9165704f1и1или2в качестве параметра jobId или0xe0b73f64b0de6032b193648c08899f20b5a6141dи3в качестве jobId (отправленное мной сообщение о вакансии).