A smart contract is a computer program or a transaction protocol which is intended to automatically execute, control or document legally relevant events and actions according to the terms of a contract or an agreement. The objectives of smart contracts are the reduction of need in trusted intermediators, arbitrations and enforcement costs, fraud losses, as well as the reduction of malicious and accidental exceptions.
Quras smart contract is written in C#, before developing the smart contract, developer need the following steps before development:
- A windows computer with visual studio installed;
- Install QurasContractPlugIn on visual studio:
- Publish quras compiler:
Download quras-smartcontract-compiler on Github, open the solution with Visual Studio, then publish the quras_msil_compiler.
After you config it, press “Publish” to publish it.
Then in the target location you can get the "quras-msil-compile.exe".
- Config the quras msil compiler
In control panel->System->Advanced system, click Environment Variables. In the section System Variables, find the PATH environment variable and select it. Click Edit. If the PATH environment variable does not exist, click New. In the Edit System Variable (or New System Variable) window, specify the path of “publish” to the PATH environment variable. Then Click OK.
After you config the path, restart the cmd, when you enter “quras-msil-compile”, if you see this, it means you config it successfully. After configuring the compiler, the visual studio must be restarted.
Create a Quras contract:
1. Click file -> create -> project.
2. Select QurasSmartContract in the list then click "Next".
3. Input your project name then click "Create".
4. Install Quras.SmartContract.Framework to your project.
Install this framework by "NuGet"
Compile the Quras contract:
Click Build -> Build Solutions to compile the .qsb file.
When the compilation is done, Hello.qsb Quras smart contract file is generated in the bin/Debug directory of the project. Hello.abi.json is a description file that contains descriptions of the scripthash, entry, parameters and return values of this contract. Then you can deploy the Hello.qsb file to blockchain by windows wallet or JSLib.
Here shows a basic hello world smart contract sample, it writes “Hello”, “World” to storage:
Every Smart Contract inherits from the SmartContract class in Quras framework. The Quras namespace is the API to interact with blockchain and manipulate persistent data storage. These APIs are divided into two parts:
- Blockchain API. The contract can access all data in the blockchain through blockchain API, like blocks and transactions.
- Persistent storage API. All contracts that are deployed on Quras chain have storage space which can only be accessed by the contract itself. Those API can access the data in the contract.
Contract property
In the class of contract, the static readonly or const is the constant contract property that can’t be changed. For example, when you want to define an owner of a contract or a factor number which will be used in asset transfer later, you can define those constants as below:
// Represents owner of this contract,Usually should be the contract creator public static readonly byte[] Owner = "Dqf3UKe1f5FBWduGxHp8RMqP29dL6DgGS1".ToScriptHash(); // A constant number private const ulong factor = 10000;
These properties defined are constants that can be used in the methods of a smart contract, and no matter if smart contract is running on any instance, these properties keep the same value.
In addition, developers can define static methods in a contract and return a constant value, which lets the end-user call this method to get the constant value when they try to query the contract. For example, when you create a token, you need to define a name that everyone can check with this method.
public static string Name()=> "name of the token";
Storage property
When you develop a smart contract, you may need to store application data on the blockchain. When a contract is created or when a transaction invokes it, the contract’s code can read or write its storage. All data stored in the storage of a smart contract are automatically persisted between invocations of the smart contract. Full nodes of quras store the state of every smart contract on the chain.
Quras has provided a data access interface based on key-value. Data records may be read or deleted from or written to the smart contracts using keys. Besides, smart contracts may get or send their storage contexts to other contracts, and trust other contracts to manage their storage areas. In C# development, smart contract can use the storage class to read/write the persistent storage The storage class is a static class and does not require a constructor. For example, if you want to store the total supply of your token into storage:
//Key is totalSupply and value is 100000000 Storage.Put(Storage.CurrentContext, "totalSupply", 100000000);
CurrentContext returns the current store context. After getting the store context, the object can be passed as an argument to other contracts, allowing other contracts to perform read/write operations on the persistent store of the current contract.
Storage works well for storing primitive values or you can use a StorageMap which is for storing structured data, this will store the entire container in a single key in smart contract storage.
//Get the totalSupply in the storageMap. The Map is used an entire container with key name "contract" StorageMap contract = Storage.CurrentContext.CreateMap(nameof(contract)); return contract.Get("totalSupply").AsBigInteger();
Data type
When develop smart contracts by C#, you can’t use full C# features due to the difference between QVM and .net.
We can only use limited C# features into an *.qsb file.
QVM provides the following basic types:
- ByteArray
- Integer
- Boolean
- Array
- Struct
- Map
- Interface
The basic types that can be directly generated from qsb code are only:
- ByteArray (Both Integer and Boolean are represented by ByteArray)
- Array
- Struct
- Map
The basic types of C# are:
- Int8 int16 int32 int64 uint8 uint16 uint32 uint64
- float double
- Boolean
- Char String
Main method
Main function is the entry point of a smart contract. In the main function, the user can call other functions according to the different entry points.
Trigger
Trigger is a mechanism that triggers the execution of smart contracts. There are four triggers introduced in the Quras smart contract, the most used are Verification and Application.
Verification trigger
Verification trigger is for calling the contract as a verification function, which can accept multiple params and return a Boolean value, indicating the validity of a transaction or block. When trying to transfer tokens from one address to another, a verification contract is triggered. All the nodes that received the transaction verify the sending address’s contract. If the return value is true, the transfer is done successfully. If return false, the transfer is failed. Therefore, the verification trigger determines whether a transaction will be relayed to the rest of the network. If return false, it means the transaction will not be packaged into the blockchain and the transaction failed.
public static bool Main(byte[] signature) { if (Runtime.Trigger == TriggerType.Verification) { if (/*condition A*/) return true; else return false; } }
Application trigger
Application trigger is for invoking the contract as a verification function, which can accept multiple params, change the blockchain status, and return values of any type. Unlike the verification trigger which is triggered by a transfer, an application trigger is triggered by a special InvocationTransaction. If an application calls a smart contract, an Invocation Transaction is created, signed and broadcast to the blockchain. After the Invocation Transaction is confirmed, the smart contract is executed by the consensus node. Since the application contract is executed after InvocationTransaction is confirmed, the transaction is recorded in the blockchain no matter if the execution is succeed or not. Usually in a smart contract, both verification trigger and application trigger can be caught and developers have to handle the trigger.
public static Object Main(string operation, params object[] args) { if (Runtime.Trigger == TriggerType.Verification) { if (/*Condition A*/) return true; else return false; } if (Runtime.Trigger == TriggerType.Application) { if (operation == "FunctionA") return FunctionA(args); } } // There is a smart contract entry point and redirected from main method public static bool FunctionA(params object[] args) { //some code }
CheckWitness
In some cases, if you want to validate if an address invoking your contract code is really the address you want. The Runtime.CheckWitness method accepts a single parameter which represents the address that you would like to validate against the address used to invoke the contract code. It verifies that the transactions / block of the calling contract has validated the required script hashes. Usually this method is used to check whether an specified address is the contract caller, and then the address can be used to do storage change or something else. Here we use the Runtime.CheckWitness function. Then we try to fetch the domain owner first to see if the domain already exists in the storage. If not, we can store our domain->owner pair using the Storage.Put method.
private static bool Register(string domain, byte[] owner){ if (!Runtime.CheckWitness(owner)) return false; byte[] value = Storage.Get(Storage.CurrentContext, domain); if (value != null) return false; Storage.Put(Storage.CurrentContext, domain, owner); return true; }
Similar to the register method, the Delete function checks the owner first and if it exists and it is the same as the one who invoked the contract, delete the pair using the Storage.Delete method. This method is leaving as a question at the end of this part.
Events
In smart contracts, events are used to communicate that something happened between the blockchain with your app, which can be 'listening' for certain events and take action when they happen. You can use this to update an external database, do analytics, or update an UI. For example, in the token system, the events transfer should be called when the user invokes the transfer function.
//Should be called when the caller transfers a token. public static event transfer(byte[] from, byte[] to, BigInteger amount)
Deploy a smart contract
When you finish writing a smart contract, you can deploy it to quras blockchain, then it can be used by other users or invoked by other contracts.
First you need to compile it to .qsb file, then deploy it by GUI(quras windows wallet) or use JSLib:
var address = 'Do27ycn5urnJnWnNboiDh5i5PkAEFmvehd'; // The address that launch the smart contract. var privKey = '02bf9e9964a3c0421ad5a8dde06f848977c514fd5cc638434d567a05b87ade39'; // The private key. var script = '53c56b6c766b00527ac46c766b51527ac461616168124d6f64756c652e52756e74696d652e4c6f6761616819' + '4d6f64756c652e53746f726167652e476574436f6e746578740e48656c6c6f2046756e6374696f6e05576f726c6461527268124d' + '6f64756c652e53746f726167652e50757461516c766b52527ac46203006c766b52c3616c7566'; // Script that is compiled. var param = '07'; // Smart contract parameter types. var returns = 5; // Smart contract return type var needStorage = true; var scName = 'HelloWorld'; var version = '1.0.0.1'; var author = 'qurasuser'; var mail = 'hello@quras.io'; var description = 'My First SC using JS Library.'; Quras.api.qurasDB.deploySmartContract(Quras.CONST.QURAS_NETWORK.MAIN, address, privKey, script, param, returns, needStorage, scName, version, author, mail, description, 490) .then((data) => { console.log(data) }) .catch((error) => { console.log(error) })
Invoking a smart contract
To invoke another contract from one contract, you need to add a statement in C# using Appcall and the script hash of the contract to invoke, and then you can invoke it in the code.
[Appcall("XXXXXXXXXX")]//ScriptHash public static extern int AnotherContract(string arg); public static void Main() { AnotherContract("Hello"); }