Key-Value contract tutorial with C# and Casper .NET SDK
This example is based on the Key-Value contract tutorial available in the Casper Network web site.
Using the Casper .NET SDK, we'll show you how to:
- Deploy the key-value contract example to the blockchain.
- Call an entry point in the contract to store a value with a given named key.
- Read the previously stored value.
Read first the Counter tutorial to prepare your environment and one account with enough $CSPR to make deploys.
Note: This tutorial uses the legacy
Deploymodel for transactions. Starting with SDK v3.x, you can also use the newTransactionV1model for Casper 2.0 networks. See Working with TransactionV1 for details on how to useTransaction.SessionBuilderandTransaction.ContractCallBuilder.
NOTE: We use a local network in this example. Learn here how to install your own local network. Alternatively, you can easily adapt the code in this example to use the casper-test network.
Step 1. Deploy the key-value storage contract
Clone the GitHub repository and build the contract following the instructions in the README file.
git clone https://github.com/casper-ecosystem/kv-storage-contract
cd kv-storage-contract
make build-contract
As a result you will get the contract compiled at target/wasm32-unknown-unknown/release/. Copy the contract.wasm file
to your working directory.
Next, use the ContractDeploy deploy template to prepare a Deploy object. Sign it and send it to the network.
public static async Task<HashKey> DeployKVStorageContract()
{
var wasmFile = "./contract.wasm";
var wasmBytes = System.IO.File.ReadAllBytes(wasmFile);
var deploy = DeployTemplates.ContractDeploy(
wasmBytes,
myAccountPK,
300_000_000_000,
chainName);
deploy.Sign(myAccount);
var response = await casperSdk.PutDeploy(deploy);
string deployHash = response.GetDeployHash();
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120));
var deployResponse = await casperSdk.GetDeploy(deployHash, tokenSource.Token);
var execResult = deployResponse.Parse().ExecutionResults.First();
Console.WriteLine("Deploy COST : " + execResult.Cost);
var contractHash = execResult.Effect.Transforms.First(t =>
t.Type == TransformType.WriteContract).Key;
Console.WriteLine("Contract key: " + contractHash);
return (HashKey)contractHash;
}
The deployment of the contract has added to the account a couple of named keys. We can retrieve our account information to see them:
public async static Task GetAccountInfo()
{
var response = await casperSdk.GetAccountInfo(myAccountPK);
File.WriteAllText("res_GetAccountInfo.json", response.Result.GetRawText());
}
In the response we can identify the named key kvstorage_contract. It contains the hash of the contract.
We'll use it in a later step to call the contract to store a value.
Print a beautified json using jq like this:
cat res_GetAccountInfo.json | jq
{
"api_version": "1.0.0",
"stored_value": {
"Account": {
"account_hash": "account-hash-989ca079a5e446071866331468ab949483162588d57ec13ba6bb051f1e15f8b7",
"named_keys": [
{
"name": "kvstorage_contract",
"key": "hash-0aaab9926971ac1564d6057d269aff4b93e621c788e2cfc2e9fbe48fac345285"
},
{
"name": "kvstorage_contract_hash",
"key": "uref-eafede874c50f50841e589825d41a6ff9c2abe12434f52d9a85816379f1acefd-007"
}
],
"main_purse": "uref-ea6dd32086d7878460a042357c8332d2d19251bb21f5ad809fc048855e167e9b-007",
"associated_keys": [
{
"account_hash": "account-hash-989ca079a5e446071866331468ab949483162588d57ec13ba6bb051f1e15f8b7",
"weight": 1
}
],
"action_thresholds": {
"deployment": 1,
"key_management": 1
}
}
},
"merkle_proof": "010..."
}
Step 2. Store an integer value in the contract named keys
We can call now the contract and store a key-value pair. In the code below, we use the ContractCall deploy template
to call the store_i32 entry point in the kvstorage_contract.
public async static Task StoreI32()
{
var namedArgs = new List<NamedArg>()
{
new NamedArg("name", "I32MinValue"),
new NamedArg("value", int.MinValue)
};
await StoreKeyValue("store_i32", namedArgs, "res_StoreI32.json");
}
private async static Task StoreKeyValue(string entryPoint, List<NamedArg> namedArgs, string saveToFile)
{
var deploy = DeployTemplates.ContractCall("kvstorage_contract",
entryPoint,
namedArgs,
myAccountPK,
1_000_000_000,
chainName);
deploy.Sign(myAccount);
var response = await casperSdk.PutDeploy(deploy);
var deployHash = response.GetDeployHash();
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120));
var deployResponse = await casperSdk.GetDeploy(deployHash, tokenSource.Token);
if (saveToFile != null)
File.WriteAllText(saveToFile, deployResponse.Result.GetRawText());
}
Note that for this deploy we're paying 1 $CSPR. Check in the json response how much the actual cost is:
cat res_StoreI32.json | jq | grep "cost"
Step 3. Read the number stored
Combining the name of the contract and the named key we can form a path and query the network to get the value:
public async static Task<CLValue> ReadStoredValue(string namedKey)
{
var accountKey = new AccountHashKey(myAccountPK);
var rpcResponse = await casperSdk.QueryGlobalState(accountKey, null,
$"kvstorage_contract/{namedKey}");
Console.WriteLine(rpcResponse.Result.GetRawText());
File.WriteAllText($"res_ReadStoredValue_{namedKey}.json", rpcResponse.Result.GetRawText());
return rpcResponse.Parse().StoredValue.CLValue;
}
// Main method
//
var clValue = await ReadStoredValue("I32MinValue");
var number = clValue.ToInt32();
Console.WriteLine("Value: " + number);
Step 4. Store a string value calling the contract by its hash key
In the step 2 we stored a value using the contract named key. That works only when the deploy is sent by the same account that deployed the contract. To store a value from a different account, we use the contract hash key instead:
public async static Task StoreString(HashKey contractHash = null)
{
var namedArgs = new List<NamedArg>()
{
new NamedArg("name", "WorkingOn"),
new NamedArg("value", "Casper .NET SDK")
};
var deploy = DeployTemplates.ContractCall(contractHash,
"store_string",
namedArgs,
myAccountPK,
500_000_000,
chainName);
deploy.Sign(myAccount);
var response = await casperSdk.PutDeploy(deploy);
var deployHash = response.GetDeployHash();
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120));
var deployResponse = await casperSdk.GetDeploy(deployHash, tokenSource.Token);
File.WriteAllText("res_StoreString.json", deployResponse.Result.GetRawText());
}
// Main method
//
await StoreString(contractHash);
Step 5. Read the string value using the contract key
Similarly, we can read the stored string with the contract key and the named key:
public async static Task<CLValue> ReadStoredValue(string namedKey, GlobalStateKey contractHash)
{
var queryResponse = await casperSdk.QueryGlobalState(contractHash, null, namedKey);
File.WriteAllText($"res_ReadStoredValue_{namedKey}.json", queryResponse.Result.GetRawText());
return queryResponse.Parse().StoredValue.CLValue;
}
// Main method
//
var clValue = await ReadStoredValue("WorkingOn", contractHash);
Console.WriteLine("Value: " + (string)clValue);
Note that in this case we're querying the network without our public key. The public key was needed in step 3 to recover the contract hash from a named key. Now, we use the contract hash directly.
Step 6. Store and read different CLValue types
So far, we've stored an integer and a string in the contract named keys. The code in Program.cs contains
methods to store many of the other CLValues. We encourage you to test some of them now.