Invoke Contract Procedure
Similar to calling a function, we can use qubic-cli
to invoke procedures. However, we still need to manually craft the packet. Hopefully, in the future there will be tools to simplify this process.
Get Contract Indentity
To invoke a contract procedure, we must know the contract ID. You can retrieve it by running the following script in your test:
TEST(Contract, GetId)
{
id contract(QNS_CONTRACT_INDEX, 0, 0, 0);
CHAR16* contractAddress = new CHAR16[61];
getIdentity(contract.m256i_u8, contractAddress, false);
cout << "Contract address: ";
for (int i = 0; i < 60; i++)
{
cout << static_cast<char>(contractAddress[i]);
}
cout << endl;
delete[] contractAddress;
}
Example output
...
==========] Running 140 tests from 30 test cases.
[----------] Global test environment set-up.
[----------] 1 test from Contract
[ RUN ] Contract.GetId
Contract address: NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAML
...
Craft The Packet
Lets say we have a procedure called setNumberState
Show setStateNumber
Contract Function
struct setStateNumber_input
{
uint64 stateNumber;
};
struct setStateNumber_output
{
uint8 result;
};
PUBLIC_PROCEDURE(setStateNumber)
{
if (input.stateNumber < state.stateNumber)
{
output.result = 1;
return;
}
state.stateNumber = input.stateNumber;
output.result = 0;
}
To invoke the procedure we need to prepare the hex data for setStateNumber_input
static void byteToHex(const uint8_t* byte, char* hex, const int sizeInByte)
{
for (int i = 0; i < sizeInByte; i++)
{
snprintf(hex + i * 2, 3, "%02x", byte[i]);
}
}
static void hexToByte(const char* hex, uint8_t* byte, const int sizeInByte)
{
for (int i = 0; i < sizeInByte; i++)
{
sscanf(hex + i * 2, "%2hhx", &byte[i]);
}
}
TEST(Contract, InvokeContract) {
QNS::setStateNumber_input input;
input.stateNumber = 1;
unsigned char* hexStr = new unsigned char[sizeof(input) * 2 + 1];
byteToHex(reinterpret_cast<const uint8_t*>(&input), reinterpret_cast<char*>(hexStr), sizeof(input));
hexStr[sizeof(input) * 2] = '\0'; // Null-terminate the string
cout << "Hex String: " << hexStr << endl;
cout << "Input size: " << sizeof(input) << endl;
}
Example output:
...
Running main() from D:\a\_work\1\s\ThirdParty\googletest\googletest\src\gtest_main.cc
[==========] Running 140 tests from 30 test cases.
[----------] Global test environment set-up.
[----------] 1 test from Contract
[ RUN ] Contract.InvokeContract
Hex String: 0100000000000000
...
Invoke Procedure
Now invoke the contract using qubic-cli
:
qubic-cli.exe -seed <SEED> -nodeip <IP> -nodeport <PORT> -sendcustomtransaction <CONTRACT_ID> <PROCEDURE_ID> <AMOUNT> <INPUT_SIZE> <INPUT_HEX>
Example command:
qubic-cli.exe -seed ghromhommngqxjokdlnyjkaoxmjbnwqneiikevfkxfncftudczluvcl -nodeip 162.120.19.25 -nodeport 31841 -sendcustomtransaction NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAML 15 0 8 0100000000000000
Example output:
C:\Users\Admin>C:\Users\Admin\Projects\qubic-cli\Debug\qubic-cli.exe -seed ghromhommngqxjokdlnyjkaoxmjbnwqneiikevfkxfncftudczluvcl -nodeip 162.120.19.25 -nodeport 31841 -sendcustomtransaction NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAML 15 0 8 0100000000000000
Transaction has been sent!
~~~~~RECEIPT~~~~~
TxHash: nchougstcpxokaicxzuvbaljttdanprejaffegqragbanbwzdlelshycpjbd
From: WRHOEDJCSNSWACSNWPRQWPBXQIOCAULOEJPCUDFDDFRLALPTIQUESAIELSNA
To: NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAML
Input type: 15
Amount: 0
Tick: 18480032
Extra data size: 8
Extra data: 0100000000000000
MoneyFlew: N/A
~~~~~END-RECEIPT~~~~~
run ./qubic-cli [...] -checktxontick 18480032 nchougstcpxokaicxzuvbaljttdanprejaffegqragbanbwzdlelshycpjbd
to check your tx confirmation status
Your procedure invocation is scheduled for tick 18480032
. The core node will process your procedure call and update the state when the network reaches that tick.
After the procedure is processed, try calling the getStateNumber
function again to verify whether the state was actually updated.
C:\Users\Admin>qubic-cli.exe -seed ghromhommngqxjokdlnyjkaoxmjbnwqneiikevfkxfncftudczluvcl -nodeip 162.120.19.25 -nodeport 31841 -sendrawpacket 1000002ab25f5c7d0d0000000e000000 16
Sent 16 bytes
Received 16 bytes
1000002bb25f5c7d0100000000000000
If we remove the first 8 bytes, we get 0100000000000000
, which is 1
in decimal (Little Endian) — just as we expected.
You must use a seed with balance to successfully invoke the procedure. Also, adjust <AMOUNT>
if your contract requires a fee or cost.
There's a key difference between calling a function and invoking a procedure. Calling a procedure actually creates a custom transaction that sends Qubic to the contract address. The core will then schedule your procedure to run when the network reaches the tick which your transaction lives
On the other hand, calling a function simply opens a TCP connection, asks the node to run the function, and returns the output immediately.
tl;dr: Function calls are executed immediately, procedures are not.