Define and Execute Methods with Parameters via the CLI
Useful Applets will invariably have methods that accept arguments, such as "to whom some token should be transferred?", "which domain to register?", and "how many tokens to transfer?".
In this tutorial we'll see how to define methods with parameters and how to pass arguments when invoking such methods using the Weilliptic CLI.
Preparations
This tutorial assumes that
- you have access to the WeilChain.
- you have completed the Basic Weil Applet Creation tutorial, as we will improve the Applet created there, and have a shell opened at the root of that project;
- you have completed the Weil Applet Deployment using wcli tutorial, as we will use wcli in a similar way.
Updating the Counter Applet
We will add a method to set the counter to an arbitrary value, bigger or equal to 0.
To add the method to the Counter Applet, edit the counter.widl
file to match the following.
interface Counter {
query func get_count() -> uint;
mutate func increment();
mutate func set_value(val: uint)
}
Observe that since the method will update the counter, it is defined as mutate
.
Observe also that the only the last method definition is not ended by ;
.
Now regenerate the server-side bindings file using the WIDL command:
- Rust
- Go
- AssemblyScript
- CPP
widl generate counter.widl server rust
The updated bindings.rs
should include the new method set_value
, in the Counter
trait, and its skeleton.
use serde::{Deserialize, Serialize};
use weil_macros::{constructor, mutate, query, smart_contract, WeilType};
pub trait Counter {
fn new() -> Result<Self, String>
where
Self: Sized;
async fn get_count(&self) -> usize;
async fn increment(&mut self);
async fn set_value(&mut self, val: usize);
}
#[derive(Serialize, Deserialize, WeilType)]
pub struct CounterContractState {
// define your contract state here!
}
#[smart_contract]
impl Counter for CounterContractState {
#[constructor]
fn new() -> Result<Self, String>
where
Self: Sized,
{
unimplemented!();
}
#[query]
async fn get_count(&self) -> usize {
unimplemented!();
}
#[mutate]
async fn increment(&mut self) {
unimplemented!();
}
#[mutate]
async fn set_value(&mut self, val: usize) {
unimplemented!();
}
}
widl generate counter.widl server go
The updated contract.go
should include the new method, setValue
and its skeleton.
...
// mutate
func (s *Counter) SetValue(uint) error {
return nil
}
Not supported yet.
Copy the following file manually for now.
widl generate counter.widl server cpp
The updated bindings.hpp
should include the new method, setValue
and its skeleton.
#include "external/nlohmann.hpp"
//define your counter state
class Counter {
public:
int value;
Counter(int initialValue) {}
Counter() : {}
int getCount() const {
return 0;
}
void increment() {
return;
}
void setValue(){
return;
}
};
// Serialization functions for Counter
inline void to_json(nlohmann::json& j, const Counter& c) {
return;
}
inline void from_json(const nlohmann::json& j, Counter& c) {
return;
}
Next, copy the new pieces of code to the contract implementation to the corresponding locations and fill in the logic. You need to do this because the compiler only updates the skeleton, not the implementation of the Applet.
- Rust
- Go
- AssemblyScript
- CPP
...
#[mutate]
fn set_value(&mut self, val: usize) {
self.count = val
}
...
...
// mutate
func (obj *CounterContractState) SetValue(val uint32) {
obj.Val = val
}
...
setValue(value: u64): void{
this.counter = value
}
@json
export class setValueArgs {
val: u64;
constructor() {
this.val = 0
}
}
...
export function set_value(): void {
const stateAndArgs = Runtime.stateAndArgs<CounterContractState, setValueArgs>()
const state = (stateAndArgs.elements[0] as JSONWrapper<CounterContractState>).inner;
const args = (stateAndArgs.elements[1] as JSONWrapper<setValueArgs>).inner;
state.setValue(args.val)
const resultValue = "ok"
const weilvalue = WeilValue.newWithStateAndOkValue(state, resultValue)
const result = Result.Ok<WeilValue<CounterContractState,string>, WeilError>(weilvalue);
Runtime.setStateAndResult(result);
}
...
// mutate
void setValue(int newValue) {
value = newValue;
}
...
Server side binding generation isn't supported yet.
You need to update method_kind_data
and add the and set_value()
manually.
...
//export method_kind_data
void method_kind_data() {
std::map<std::string, std::string> method_kind_mapping;
method_kind_mapping["get_count"]= "query";
method_kind_mapping["increment"]= "mutate";
method_kind_mapping["set_value"]= "mutate";
nlohmann::json json_object = method_kind_mapping;
std::string serialized_string = json_object.dump();
weilsdk::Runtime::setResult(serialized_string,0);
}
struct setValueArgs{
int val;
};
inline void to_json(nlohmann::json& j, const setValueArgs& s) {
j = nlohmann::json{{"val", s.val}};
}
inline void from_json(const nlohmann::json& j, setValueArgs& s) {
int _val = j.at("val");
c.val = _val;
}
...
void set_value() {
std::pair<std::string,std::string> serializedStateAndArgs = weilsdk::Runtime::stateAndArgs();
weilsdk::StateArgsValue sav;
nlohmann::json stateJson = nlohmann::json::parse(serializedStateAndArgs.first);
from_json(stateJson,smart_contract_state);
nlohmann::json argsJson = nlohmann::json::parse(serializedStateAndArgs.second);
if(argsJson.is_discarded()){
weilsdk::MethodError me = weilsdk::MethodError("set_value", "invalid_args");
std::string err = weilsdk::WeilError::MethodArgumentDeserializationError(me);
weilsdk::Runtime::setStateAndResult(std::variant<weilsdk::WeilValue,weilsdk::WeilError> {err});
return;
}
setValueArgs s;
from_json(argsJson,s);
smart_contract_state.setValue(s.val);
nlohmann::json j2;
to_json(j2,smart_contract_state);
std::string serializedSmartContractState = j2.dump();
weilsdk::WeilValue wv;
wv.new_with_state_and_ok_value(serializedSmartContractState, "Ok");
weilsdk::Runtime::setStateAndResult(std::variant<weilsdk::WeilValue,weilsdk::WeilError> {wv});
}
Execute the CLI
Assuming that the Weilliptic CLI binary is in the PATH
, execute the following:
WC_PATH=~/.weilliptic WC_PRIVATE_KEY=~/.weilliptic weil_cli
Execute command connect -h <sentinel-node>
The following response should be seen.
{"message":"Connected successfully to <sentinel-node>.","status":"Ok"}
Deploying the Applet
To deploy the updated Counter Applet, execute the following in the CLI:
deploy --file-path /root/code/counter/target/wasm32-unknown-unknown/release/counter.wasm --widl-file /root/code/counter/counter.widl
The contract is deployed an new contract_address
is returned, different from any previously deployed.
{
"batch_author":"pod-75a62520-0.weilliptic.default.svc.cluster.local-8000",
"batch_id":"21d39243c5f1abdf3b530963ba5d0209afa66df9f6dcf902583fe0f6e4eb7e3b",
"block_height":953,
"contract_address":"7b226...227d",
"creation_time":"2024-09-30T20:30:26Z",
"status":"Finalized",
"tx_idx":0,
"txn_result":"{\"Ok\":\"null\"}",
"txn_ticket":{"Ok":"7b226...227d"}
}
Run the execute
command to see the current counter value
execute --name 7b226...227d --method get_count
The result should be the following
{
"batch_author":"",
"batch_id":"",
"block_height":0,
"creation_time":"",
"status":"Finalized",
"tx_idx":0,
"txn_result":"{\"Ok\":\"0\"}"
}
Let's set the value of the counter to 10 using the set_value
method:
execute -n 7b226...227d --method set_value --method-args '{"val": 10}'
{
"batch_author":"pod-7bad3bac-0.weilliptic.default.svc.cluster.local-8000",
"batch_id":"f8d22d5400f5b00e6fb9d934ce20b0a64ac10eecdfbe173ef63ffbfa0ed96789",
"block_height":39486,
"creation_time":"2024-09-30T21:54:06Z",
"status":"Finalized",
"tx_idx":0,
"txn_result":"{\"Ok\":\"null\"}"
}
followed by another call to get_count
execute -n 7b226...227d --method get_count
{
"batch_author":"",
"batch_id":"",
"block_height":0,
"creation_time":"",
"status":"Finalized",
"tx_idx":0,
"txn_result":"{\"Ok\":\"10\"}"
}
Observe that this time txn_result
has value 10
.
Next steps
Congratulations! You have seen how to define and deploy simple Applets. Next you should understand how to define more complex Applet state and methods.