-
Notifications
You must be signed in to change notification settings - Fork 1
Writing Smart Contracts
Smart contracts are pieces of code live on the blockchain. All smart contract have access to a shared data storage in the form of a postgres database. They can use this to add, modify or remove data according to the rules they set up. For some example contracts see Example Smart Contracts.
Smart contracts on Validana exist of 7 components:
- type: The type/name of the smart contract. A hash of the contract code is used to identify contracts, but as this is not very human friendly for identifying you can give them a name instead.
- version: The version of the smart contract.
- description: A description of what the contract does.
- template: Describes with what parameters the code should be called. If the wrong parameters are passed it will be rejected before reaching the code, ensuring no unintended side effects occur.
- init: A piece of code that will executed when the contract is first added to the blockchain. This can for example be used to create database tables or one time changes to the data format.
- code: The code that will be executed when a contract is called. Typically this is used to add, update or remove data from the database. If no code is provided it will only execute the init when creating the contract, but the contract itself cannot be executed.
- validanaVersion: For what version of Validana this contract has been created.
The template has the following format:
{
"paramName": { "type": string, "desc": string, "name": string },
"param2Name": ...
}
The paramName and param2Name here are the variables/parameters to call the contract with. Name and desc are a human friendly name and description of what that parameter means. Type enforces the data format for that parameter. The following types exist:
- bool: A boolean.
- int: A whole number.
- uint: A whole number, at least 0.
- float: A decimal number.
- addr: A blockchain address.
- json: Any json. In contract of validanaVersion 1 it should instead be a string that can be parsed with JSON.parse().
- base64: A base64 string.
- hex: A hex string.
- id: A hex string of length 32 (thus 16 bytes of data). Not available for contracts of validanaVersion 1.
- hash: A hex string of length 64 (thus 32 byes of data).
- str (or any other type name not listed above): A string.
- Any of the above types with postfix Array (capital sensitive): An array of said type.
- Any of the above types with postfix ? (except for the Array types): said type or undefined, to add optional properties. Not available for contracts of validanaVersion 1.
The code/init should be javascript code to be executed when calling/creating the contract. The exact version of javascript depends on the node.js version that the processor runs, which is at least 7.6. See node.green for the exact functions that are available for each version. It is recommended to not use the latest version functions as to not force all nodes to the latest version. Optionally code can return a string as a result.
Aside from that there are a few extra (global) functions/variables available when executing smart contracts:
- (code only) payload: object: An object with the format defined by the contract template.
- from: string: The blockchain address of the user executing the contract.
- processor: string: The blockchain address of the processor.
- transactionId: string: The id of the transaction (length 32 hex string). Guaranteed to be unique.
- block: number: The block this transaction will be in.
- previousBlockHash: string: The hash of the previous block (length 64 hex string). Can be used as a source of pseudo-randomness.
- previousBlockTimestamp: number: Time (milliseconds since unix epoch) that the last block was processed.
- currentBlockTimestamp: number: Time (milliseconds since unix epoch) that this block started being processed.
- sha1/sha256/sha512/md5/ripemd160(string | buffer): buffer: Hashing functions.
- isValidAddress(string | buffer): boolean: Check if a given string or buffer is a valid blockchain address.
- addressAsString(string | buffer): string: Turn an address into string format.
- addressAsBuffer(string | buffer): buffer: Turn an address into binary format. Take note when comparing binary data to use .equals() instead of ===.
- async query(string, Array): { rowCount: number | null, rows: Array} Execute a database query. The first parameter is the query to execute, the second parameter the arguments for the parameterized query. Returns the amount of rows update and/or a list of rows that were requested. MUST be awaited.
- reject(string): void: Reject a blockchain transaction, for example if the user does not have permission to edit the data they want to edit. Any changes will be rolled back.
There are also various javascript functions that are NOT available, mainly to enforce determinism:
- try catch
- If using query() you can use .catch().
- JSON.parse() and encode/decodeURI(Component) will return undefined instead of throwing on errors.
- Any error that arises causes the transaction to be invalid.
- Date.now(), new Date() without arguments and any other Date function that depends on the local timezone.
- You can use currentBlockTimestamp for an approximation of the current time.
- Math.random()
- Any functions that depend on the current locale. (For example String.localeCompare)
- Async functions, such as setTimeout()
- Postgres queries containing localtime, current_date, now(), random(), etc.
- Any query other than select/insert/update/delete and alter/delete/drop table/index/type.
- Most of the pg_catalog and information_schema tables, as well as everything not created inside smart contracts.
- Sequences/serial as well, due to non-deterministic gaps they create.
- Global properties of RegExp, Function.toString(), Buffer.allocUnsafe, modifying global objects.
- Anything not allowed by "use strict", except for Validana version 1 contracts.