Skip to content

Example Smart Contracts

Wouter den Bakker edited this page Feb 27, 2020 · 4 revisions

Here are some example smart contracts you can use in a test setup. Follow the Setup to start the processor. The easiest way to add the contract with the Validana Client example. Enter your chosen prefix and connect to the Validana Server. As private key you must use the private key of the processor, as only the processor is allowed to create or delete contracts. After a contract is created you need to reload the page to view them. (Note that if you don't set VSERVER_CACHING to false you may need to wait up to 5 minutes for caches to clear.)

Object tracking

Lets say we want to make it possible to track objects. Only the processor is allowed to create new objects, but after that anyone is free to transfer ownership of said object. For this we create 2 smart contracts. One to create new objects and one to transfer ownership. In addition we do not want people to own more that one 'chair' object as supply is limited.

Create object contract:

  • type: Create Object
  • version: 1.0
  • description: Create a new object.
  • template:
{
	"objectId":{"type":"uint","desc":"The id of the object to create.","name":"Object ID"},
	"objectDescription":{"type":"str","desc":"Description of the object.","name":"Description"},
        "receiver":{"type":"addr","desc":"Who should be the owner of this object?","name":"Receiver"}
}
  • init:
//A blockchain address is 26-35 characters, so we use a varchar here.
await query("CREATE TABLE IF NOT EXISTS objects (object_id BIGINT PRIMARY KEY NOT NULL, " +
    "owner VARCHAR(35) NOT NULL, description VARCHAR(64) NOT NULL);", []);
  • code:
//Only processor is allowed to create objects.
if (from !== processor) {
    return reject("Only processor may make objects.");
}
//We limit the description length. (
if (payload.objectDescription.length > 64) {
    return reject("Description too long");
}
//Make sure user only has 1 chair.
if (payload.objectDescription.toLowerCase().includes("chair")) {
    //Find what objects this owner already has.
    const userObjects = await query("SELECT description FROM objects WHERE owner = $1;", [payload.receiver]);
    for (const object of userObjects.rows) {
        if (object.description.toLowerCase().includes("chair")) {
            return reject("You already have a chair, no more chairs for you :(");
        }
    }
}
//Add the new object to this owner
const newObject = await query("INSERT INTO objects (object_id, owner, description) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;",
    [payload.objectId, payload.receiver, payload.objectDescription]);
//We consider it a failure.
if (newObject.rowCount === 0) {
    return reject("Object with id did already exist.");
}
//We had a success.
return "Success";
  • validanaVersion: 2

Go ahead and fill these values in the Validana Client example. It will automatically base64 encode the init and code. A few things to note here. We use the special variables 'from' and 'processor', which are available for smart contracts, to verify the caller of the smart contract is indeed the processor. We use the 'payload' object to retrieve the data the contract was called with. The template will ensure these values are available and of the given type, however we must still validate the length of the description, as the template imposes no restraints on that. We use the query() method to retrieve and store values in the database, properly parameterized to avoid sql injections. As try-catch is not allowed we instead use 'ON CONFLICT DO NOTHING' and check if there has been at least 1 row inserted. We use reject() to notify when a transaction failed.

If you reload the Validana Client example and enter 'Create Object 1.0' as the contract you can now create objects. Generate a few keys+addresses and try creating the object with the different keys and receiver addresses.

Transfer object contract:

  • type: Transfer Object
  • version: 1.0
  • description: Transfer an object to a new owner.
  • template:
{
	"objectId":{"type":"uint","desc":"The id of the object to transfer.","name":"Object ID"},
        "receiver":{"type":"addr","desc":"Who should be the new owner of this object?","name":"Receiver"}
}
  • init: Leave this empty.
  • code:
//Add the new object to this owner
const transfer = await query("UPDATE objects SET owner = $1 WHERE object_id = $2 AND owner = $3;",
    [payload.receiver, payload.objectId, from]);
//We consider it a failure.
if (transfer.rowCount === 0) {
    return reject("Object with id does not exist or user is now owner of the object.");
}
//We had a success.
return "Success";

Go ahead and add this contract as well and try it out.

Transfer object contract v2:

You may have noticed it is now possible for a user to own multiple chairs. If a user already owns a chair and another user transfers a chair to them. This may not be what we want.

We can check if there is someone who owns multiple chairs with the following database query: (If you set up the processor with docker-compose) you can use docker ps to find the container id of the database and docker exec -it CONTAINER_ID psql -U postgres -d blockchain to connect to the database.) SELECT owner, count(*) FROM objects WHERE description ILIKE '%chair%' GROUP BY owner HAVING COUNT(*) > 1; Say we found that user '1Fjd3yHcYiZntSnByFWoYC15udPiD1KC7s' has two chairs. Next we check who send them the chair: SELECT sender, payload->'objectId' AS object_id FROM basics.transactions WHERE contract_type = 'Transfer Object' AND receiver = '1Fjd3yHcYiZntSnByFWoYC15udPiD1KC7s'; Say we find that user '1Jhq8xuHpCo17UrBJifSDzcVZ3MW9QAEfz' send them object 8: the chair. Now it is time to create a new contract to correct this.

  • type: Transfer Object
  • version: 2.0
  • description: Transfer an object to a new owner.
  • template:
{
	"objectId":{"type":"uint","desc":"The id of the object to transfer.","name":"Object ID"},
        "receiver":{"type":"addr","desc":"Who should be the new owner of this object?","name":"Receiver"}
}
  • init:
await query("UPDATE objects SET owner = '1Jhq8xuHpCo17UrBJifSDzcVZ3MW9QAEfz' WHERE object_id = 8;", []);
  • code:
const objectToTransfer = await query("SELECT owner, description FROM objects WHERE object_id = $1;", [payload.objectId]);
if (objectToTransfer.rows.length === 0 || objectToTransfer.rows[0].owner !== from) {
    return reject("Object with id does not exist or user is now owner of the object.");
}
if (objectToTransfer.rows[0].description.toLowerCase().includes("chair")) {
    const ownsChair = await query("SELECT count(*) FROM objects WHERE owner = $1 AND "+
        "description ILIKE '%chair%';", [payload.receiver]);
    if (ownsChair.rows[0].count > 0) {
            return reject("Receiver already owns a chair.");
    }
} 

//Add the new object to this owner
await query("UPDATE objects SET owner = $1 WHERE object_id = $2;", [payload.receiver, payload.objectId]);

//We had a success.
return "Success";

Go ahead and create this new contract. Now as a final step execute the contract 'Delete Contract' and fill in 'Transfer Object 1.0', which will automatically be replaced with the hash of the contract.

Clone this wiki locally