-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement MirrorNodeContractQuery (#2723)
* feat: add mirror node contract call and estimate queries Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * chore: add new classes to export.js Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * test: add integration and unit tests Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * feat: add example for estimates from mirror node Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * chore: add new package Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * refactor: change js doc return Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * feat: get mirror network from client Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * fix: urls Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com> * fix: local node condition Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * chore: formatting Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * chore: formatting Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * fix: make islocalnode optional property Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * fix: add missing props Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * fix: add missing props Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * feat: use only sender evm address Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * feat: remove old tests Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * test: add new tests Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * test: remove redundant test Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * docs: add jsdoc comments Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * test: temporary skip non-working test Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * fix: revert testnet environment test Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * fix: skip test temporary until we find out why Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * fix: skip test temporary until we find out why Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * fix: skip test temporary until we find out why Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * refactor: formatting Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * chore: fix unskippable test error Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * refactor: remove casting of number Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> * test: update Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> --------- Signed-off-by: Ivaylo Nikolov <ivaylo.nikolov@limechain.tech> Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com> Co-authored-by: Ivan Ivanov <ivanivanov.ii726@gmail.com>
- Loading branch information
1 parent
0d0f2a0
commit ddcf3d4
Showing
14 changed files
with
790 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import ABI from "@ethersproject/abi"; | ||
import { | ||
PrivateKey, | ||
MirrorNodeContractCallQuery, | ||
MirrorNodeContractEstimateQuery, | ||
ContractCallQuery, | ||
Hbar, | ||
ContractCreateTransaction, | ||
Client, | ||
ContractFunctionParameters, | ||
AccountId, | ||
FileCreateTransaction, | ||
Long, | ||
} from "@hashgraph/sdk"; | ||
import { setTimeout } from "timers/promises"; | ||
import dotenv from "dotenv"; | ||
|
||
dotenv.config(); | ||
|
||
const OPERATOR_ID = AccountId.fromString(process.env.OPERATOR_ID); | ||
const OPERATOR_KEY = PrivateKey.fromStringED25519(process.env.OPERATOR_KEY); | ||
const HEDERA_NETWORK = process.env.HEDERA_NETWORK || "testnet"; | ||
|
||
async function main() { | ||
console.log("Mirror Node contract queries Example Start!"); | ||
|
||
// Step 0: Create and configure the SDK Client. | ||
const client = Client.forName(HEDERA_NETWORK); | ||
client.setOperator(OPERATOR_ID, OPERATOR_KEY); | ||
|
||
const BYTECODE = | ||
"60806040526040518060400160405280600581526020017f68656c6c6f0000000000000000000000000000000000000000000000000000008152505f90816100479190610293565b50348015610053575f80fd5b50610362565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806100d457607f821691505b6020821081036100e7576100e6610090565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026101497fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261010e565b610153868361010e565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61019761019261018d8461016b565b610174565b61016b565b9050919050565b5f819050919050565b6101b08361017d565b6101c46101bc8261019e565b84845461011a565b825550505050565b5f90565b6101d86101cc565b6101e38184846101a7565b505050565b5b81811015610206576101fb5f826101d0565b6001810190506101e9565b5050565b601f82111561024b5761021c816100ed565b610225846100ff565b81016020851015610234578190505b610248610240856100ff565b8301826101e8565b50505b505050565b5f82821c905092915050565b5f61026b5f1984600802610250565b1980831691505092915050565b5f610283838361025c565b9150826002028217905092915050565b61029c82610059565b67ffffffffffffffff8111156102b5576102b4610063565b5b6102bf82546100bd565b6102ca82828561020a565b5f60209050601f8311600181146102fb575f84156102e9578287015190505b6102f38582610278565b86555061035a565b601f198416610309866100ed565b5f5b828110156103305784890151825560018201915060208501945060208101905061030b565b8683101561034d5784890151610349601f89168261025c565b8355505b6001600288020188555050505b505050505050565b6102178061036f5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063ce6d41de1461002d575b5f80fd5b61003561004b565b6040516100429190610164565b60405180910390f35b60605f8054610059906101b1565b80601f0160208091040260200160405190810160405280929190818152602001828054610085906101b1565b80156100d05780601f106100a7576101008083540402835291602001916100d0565b820191905f5260205f20905b8154815290600101906020018083116100b357829003601f168201915b5050505050905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156101115780820151818401526020810190506100f6565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610136826100da565b61014081856100e4565b93506101508185602086016100f4565b6101598161011c565b840191505092915050565b5f6020820190508181035f83015261017c818461012c565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806101c857607f821691505b6020821081036101db576101da610184565b5b5091905056fea26469706673582212202a86c27939bfab6d4a2c61ebbf096d8424e17e22dfdd42320f6e2654863581e964736f6c634300081a0033"; | ||
|
||
// Step 1: Create a new file with the contract bytecode | ||
const { fileId } = await ( | ||
await new FileCreateTransaction().setContents(BYTECODE).execute(client) | ||
).getReceipt(client); | ||
|
||
const { contractId } = await ( | ||
await new ContractCreateTransaction() | ||
.setBytecodeFileId(fileId) | ||
.setGas(200000) | ||
.execute(client) | ||
).getReceipt(client); | ||
|
||
console.log("Created new contract with ID: " + contractId.toString()); | ||
|
||
// Step 2: Wait for mirror node to import data | ||
await setTimeout(5000); | ||
|
||
const gasLimit = Long.fromNumber(30000); | ||
const gasPrice = Long.fromNumber(1234); | ||
|
||
// Step 3: Estimate the gas needed | ||
const gas = await new MirrorNodeContractEstimateQuery() | ||
.setContractId(contractId) | ||
.setSender(client.operatorAccountId) | ||
.setGasLimit(gasLimit) | ||
.setGasPrice(gasPrice) | ||
.setFunction("getMessage", new ContractFunctionParameters()) | ||
.execute(client); | ||
|
||
// Step 4: Do the query against the consensus node using the estimated gas | ||
const callQuery = new ContractCallQuery() | ||
.setContractId(contractId) | ||
.setGas(gas) | ||
.setFunction("getMessage") | ||
.setQueryPayment(new Hbar(1)); | ||
|
||
const result = await callQuery.execute(client); | ||
|
||
// Step 5: Simulate the transaction for free, using the mirror node | ||
const simulationResult = await new MirrorNodeContractCallQuery() | ||
.setContractId(contractId) | ||
.setSender(client.operatorAccountId) | ||
.setGasLimit(Long.fromString("30000")) | ||
.setBlockNumber(Long.fromString("10000")) | ||
.setGasPrice(Long.fromString("1234")) | ||
.setFunction("getMessage", new ContractFunctionParameters()) | ||
.execute(client); | ||
|
||
// need to do this to remove the readonly property of the array | ||
/** | ||
* @type {string[]} | ||
*/ | ||
const decodedSimulationResult = ABI.defaultAbiCoder | ||
.decode(["string"], simulationResult) | ||
.concat(); | ||
|
||
/** | ||
* @type {string} | ||
*/ | ||
const decodedStringMessage = decodedSimulationResult[0]; | ||
console.log("Simulation result: " + decodedStringMessage); | ||
console.log("Contract call result: " + result.getString(0)); | ||
} | ||
|
||
void main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import MirrorNodeContractQuery from "./MirrorNodeContractQuery.js"; | ||
|
||
/** | ||
* @typedef {import("../channel/Channel.js").default} Channel | ||
* @typedef {import("../client/Client.js").default<*, *>} Client | ||
*/ | ||
export default class MirrorNodeContractCallQuery extends MirrorNodeContractQuery { | ||
/** | ||
* @returns {Object} | ||
*/ | ||
get JSONPayload() { | ||
if (this.callData == null) { | ||
throw new Error("Call data is required."); | ||
} | ||
|
||
return { | ||
data: Buffer.from(this.callData).toString("hex"), | ||
from: this.senderEvmAddress, | ||
to: this.contractEvmAddress, | ||
estimate: false, | ||
gasPrice: this.gasPrice?.toString(), | ||
gas: this.gasLimit?.toString(), | ||
blockNumber: this.blockNumber?.toString(), | ||
value: this.value?.toString(), | ||
}; | ||
} | ||
|
||
/** | ||
* @param {Client} client | ||
* @returns {Promise<string>} | ||
*/ | ||
async execute(client) { | ||
/** | ||
* @type { { data: { result: string } } } | ||
*/ | ||
const mirrorNodeRequest = await this.performMirrorNodeRequest( | ||
client, | ||
this.JSONPayload, | ||
); | ||
|
||
return mirrorNodeRequest.data.result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import MirrorNodeContractQuery from "./MirrorNodeContractQuery.js"; | ||
|
||
/** | ||
* @typedef {import("../channel/Channel.js").default} Channel | ||
* @typedef {import("../client/Client.js").default<*, *>} Client | ||
*/ | ||
export default class MirrorNodeContractCallQuery extends MirrorNodeContractQuery { | ||
/** | ||
* @returns {Object} | ||
*/ | ||
get JSONPayload() { | ||
if (this.callData == null) { | ||
throw new Error("Call data is required."); | ||
} | ||
|
||
return { | ||
data: Buffer.from(this.callData).toString("hex"), | ||
from: this.senderEvmAddress, | ||
to: this.contractEvmAddress, | ||
estimate: true, | ||
gasPrice: this.gasPrice?.toString(), | ||
gas: this.gasLimit?.toString(), | ||
blockNumber: this.blockNumber?.toString(), | ||
value: this.value?.toString(), | ||
}; | ||
} | ||
|
||
/** | ||
* @param {Client} client | ||
* @returns {Promise<number>} | ||
*/ | ||
async execute(client) { | ||
/** | ||
* @type { { data: { result: string } } } | ||
*/ | ||
const mirrorNodeRequest = await this.performMirrorNodeRequest( | ||
client, | ||
this.JSONPayload, | ||
); | ||
|
||
return Number(mirrorNodeRequest.data.result); | ||
} | ||
} |
Oops, something went wrong.