EVM
Overview
zeknd DAppChains contain an Ethereum virtual machine (EVM) and allow you to deploy and run smart contracts that compile to EVM bytecode.
#Ethereum virtual machine
An EVM consists of a database and the interpreter for EVM bytecode.
The interpreter runs EVM bytecode and is specially designed for creating secure deterministic programs suitable for blockchains. The most popular language for coding EVM smart contracts is Solidity. However, any language that compiles to EVM bytecode can be run by the EVM interpreter.
The database is keyed by addresses for each of the programs that have been deployed on the EVM. The value contains the program's bytecode and any associated data.
#DAppChains and EVM
There are currently several ways to interact with the DAppChain's EVM:
A smart contact can be deployed on the initial startup of the blockchain.
The zeknd
mcommand line tool allows deploying a smart contract or calling a method on an already deployed contract.Another smart contract, either an EVM contract or a plugin contract, can call methods on an already deployed EVM contract.
In Go, you can use
go-zekndEvmContractobject.In TypeScript or JavaScript, you use the zeknd
-js'sEvmContractobject.
An EVM smart contract is deployed to a DAppChain in the form of compiled bytecode. Which makes the chain unaware of the parent language. Parameters to Solidity smart contract method calls are encoded with the Application Binary Interface (ABI) documented on the Solidity website. The ABI can get quite complex, however, Ethereum implementations should, as we see later, give function to support parameter generation.
#Deploy on Boot up.
Contracts can be deployed on a DAppChain on boot up, by putting the compiled code in the contracts directory and linking the genesis.json file.
Here is an example genesis file:
There are two contracts in the top array. The first is an EVM contract, and the second one is a plugin.
vm:The virtual machine used to run the contract. Currently, there are two options.pluginUser-created contracts.EVMcontract runs on DAppChains EVM.
formatThe nature of the smart contract's input file in the contracts directory.pluginUser plugin, can be produced bygo-zeknd.truffleSolidity program, compiled using truffles compiler.soliditySolidity program, compiled using solc.hexRaw Hex, for instance, solidity program compiled usingsolc -ooption .
nameThis name can be used to retrieve the address of the contract assigned by zeknd or the EVM.locationVersioned name of the file binary file located in the contracts directory. For Truffle and Solidity, it might be necessary to give the full path.
So in this example, the zeknd DAppChain will take the bytecode from the Truffle compilation of our SimpleStore Solidity contract. It will then deploy it on the chain's EVM. Confirmation and the contract's address will be available in zeknd's logging information.
#Deploy and run from the command line
The zeknd command line tool has three commands for interacting with the chain's EVM:
deployThis will deploy a smart contract in EVM bytecode onto the chain's EVM.callThis will call a method that can mutate the state on an already deployed EVM smart contract.static-callThis will call a read-only method on an already deployed EVM smart contract.
#Deploy
Use ./zeknd deploy to deploy a contract that can be compiled to EVM bytecode onto a DAppChains EVM:
The -a and -k flags are used to identify the user with public and private key address files.
-b gives the file where the raw EVM bytecode for the contract is held. This could be generated using a solidity compiler such as solc --bin -o. MySolProgram.sol
-n allows you to enter a name for your contract. This will act as a more user-friendly handle than the contract's address.
Example
If everything works, you should see something like:
The output contract address can be used to call a method on the contract in the call command. The unique transaction hash can be used to retrieve a receipt of the deployment transaction.
#call
The -a and -k flags are used to identify the user with public and private key address files.
-c requires the contract address. This could be one output from a previous call to \zeknd deploy or retrieved from the startup log.
-n is a name or label entered for the contract when it was deployed. Can be used as an alternative to the address
-i is the input string. For a solidity contract, this will be ABI encoded as described in the Solidity ABI documentation.
Example
On completion, this will return the transaction hash. This should be unique for each transaction call and it can be used to return a receipt of the transaction.
#static-call
Call a read-only method on a contract. Returns the method return value.
The -a and -k flags are used to identify the user with public and private key address files.
-c requires the contract address. This could be one output from a previous call to \zeknd deploy or retrieved from the startup log.
-n is a name or label entered for the contract when it was deployed. Can be used as an alternative to the address.
-i is the input string. For a solidity contract, this will be ABI encoded as described in the Solidity ABI documentation.
The address fields -a and -k are optional.
Example
#From a user plugin
Smart contracts deployed on a DAppChain's EVM can be called from user created plugins. The evmexample in go-zeknd shows how to achieve this.
Before continuing let's consider the various modules involved.
User application. This is the end user application that initiates transactions on the DAppChain.
DAppChain. Receives transactions from the user application and forwards to the appropriate contract to run. Also commits results to the blockchain.
Smart contracts. These are written by the user and deployed on the DAppChain. There are two main types.
Plugins. These can be written in any language supported by gRPC; go-zeknd allows easy use of contracts written in Go, and zeknd-js for JavaScript. The plugin is compiled into an executable that the DAppChain calls using gRPC.
EVM smart contracts. Solidity programs or any other code that compiles into EVM bytecode can be run by the DAppChain using its EVM.
Plugins can run other contracts including ones deployed on the EVM by calling back to the DAppChain using gRPC. The reverse, however, is not true. EVM deployed contracts can only interact within the EVM, this is to ensure that the EVM's results are deterministic.
#User code
The user provides two items of code:
the smart contracts and
the end application that makes use of the DAppChain.
In the following, we will assume that Go is being used for the end application and the smart contracts are written either in Go for plugins or Solidity for EVM. Refer to zeknd-js-quickstart for a JavaScript solution.
#Minimal plugin
First, let's look at the definition of a contract in go-zeknd:
and plugin.Meta is defined from a protobuf definition:
So all a contract needs is to implement the Meta function. However, to be usable as a plugin in a DAppChain there are a few other bits. Here is a minimal example:
Here are some points of interest:
First, the contract has to be
package main.Define our contract called
HelloWorldas a struct.Implement the
Meta()function, returning the contracts name and version number.The variable
Contractneeds to be defined. The functioncontract .MakePluginContractconverts our simple outline into an object that a DAppChain can communicate with.The main routine can then set the contract up as a working server.
Of course, our contract has no functionality so it can't do anything. The next step is to add some functionality. The MakePluginContract function can then use reflection to learn any new methods we give to our contract.
#Adding functions
So we've just added a simple function that just returns a fixed message. A couple of key points:
Either a
contract.StaticContextorcontract.Contextshould be the first parameter. It provides various methods that allow you to access the resources on the DAppChain. For example, view or modify the state database, or call other plugins.The user input in the second parameter and the first return value take the form of protobuf messages-
HelloRequestandHelloResponsein this example. These protobuf message structs need to be auto-generated from a language-neutral.protofile. See below.The input and output protobuf message parameters need to be coordinated with the calling application. As the protobuf message data structures are generated from language-independent
.protofiles, it does not matter if the calling application and the smart contract are written in different languages.
Below is an example of a suitable types .proto file for our Hello function:
A types.pb.go file that we can use can be built using the protoc-gen-gogo plugin for proto, with a command like:
#Call smart contract
The following code fragment shows how to call the Hello function of our Hello World example in Go using functions from go-zeknd:
Here's what our code does:
Create a client that can talk to our DAppChain using its URL.
Get a handle to our smart contract, from its name and address.
The wire type
HelloRequestandHelloResponsehave to match the input and output parameters of the contract's method we are calling.Call the
Hellomethod. We useStaticCallas theHellomethod has a static context.
#Calling Solidity contract
Now that we have had a quick review of implementing plugins, we can look at accessing smart contracts deployed on the DAppChain's EVM from a plugin.
First, let's assume we have deployed this simple Solidity contract on the DAppChain's EVM:
We will look at a simple plugin that wraps this solidity contract. So our plugin will have two functions- SetValue and GetValue. These functions will just pass data between the SimpleStore contract and the transaction initiator. As it wraps this SimpleStore we will call it EvmExample.
Here is the outline as for the EvmExample contract, with stubs added for the SetValue and GetValue methods:
The .proto file for generating the message declarations looks like:
Let's look at the SetValue function first. The function to call to run a smart contract on the EVM is:
The context is just passed through, for setting the output can just be a dummy object. We need to pass the address of the solidity contract, and the correct input.
The Context contains a Registry that allows us to get the address of a a contract from its name:
The input is passed straight through to the EVM and needs to be encoded as laid out in the Solidity ABI documentation.
#ABI encoding of parameters
So for our input we need to encode it to something like:
Don't panic, go-ethereum can help us out.
When you compile Solidity you not only get the bytecode that runs on the EVM, but you get an ABI. The ABI is a JSON object that describes the contract's interface. Here is the ABI for our SimpleStore:
We can use "github.com/ethereum/go-ethereum/accounts/abi" and this ABI string to encode our input. The key function is abi.JSON
Here we have the SimpleContract ABI in the SimpleStoreABI variable. We could either read it in from a file or hard code into the source.
The Pack method takes the function signature and a list of the arguments and returns the encoded input.
#Putting it together
Now we know how to get the input and contact address. At this point, we can give an example of our SetValue method. Note that error checking removed for clarity.
This function could be called in Go using go-zeknd with:
The GetValue function works in a similar fashion. We now have to unwrap the output from the solidity contract and return it in a WrapValue message. StaticCallEvm is used as get. It is a view or a constant function.
#EvmContract
go-zeknd and zeknd-js provide help for communicating with a running DAppChain using an RPC client.
#go-zeknd
This works in much the same way as described for go-zeknd Contract
#Connecting to a Solidity contract on a DAppChain
To connect to an existing solidity smart contact running on a DAppChain EVM we can use:
#Deploying a Solidity contract to a DAppChain
We can also deploy a new smart contract to a running DAppChain EVM. For this we need the contract's bytecode.
A Solidity contract can be converted to byte code using the Solidity compiler using something like this:
hex.DecodeString can be used to convert a hex string to a []byte array. We can then use the client.DeployContract methode to deploy our contract and return an EVMContract handle. The second return parameter is a transaction hash that can be used to retrieve a receipt of the transaction using the TxHash query.
#Retrieving Solidity contract's code
You can retrieve the runtime bytecode for a deployed solidity contract using the DAppChains QueryInterface method GetCode:
The runtime code is the initial contract's binary with the code for starting and constructing the contract removed as its no longer needed.
#Writing to a Solidity contract on a DAppChain
Writing and reading to a smart contract deployed on a DAppChain's EVM works in a similar way to writing and reading to non-EVM plugins. The main difference is that the function signature and input parameters need to be converted to bytecode using ABI encoding. You can use the go-ethereum abi.JSON function to encode the input using your contract's ABI which you can get from solc --abi -o. MySolidiityProgram.sol
EvmContract's Call method is used for methods that mutate the DAppChain's state.
The Call method returns a transaction hash. You can use the transaction hash to retrieve more information about the contract using the GetEvmTxReceipt method. This returns a transcation receipt, vm.EvmTxReceipt object.
#Reading from a Solidity contract on a DAppCahin
To get information from an EVM smart contract you need to call a view method using the EvmContract's staticCall. This returns the result in an ABI encoded []byte. As for other EVM methods, the function signature and input arguments are ABI encoded. The caller field in StaticCall is optional and using an empty zeknd.Address is fine.
#zeknd-js
In JavaScript and TypeScript, you can Call methods contracts deployed on the EVM of a DAppChain in a similar way as for non-EVM plugins. This is outlined in the zeknd-js quickstart
#Connecting to a Solidity contract on a DAppChain
We use the EvmContract class instead of the Contract class. So the zeknd-js quick-start getEvmContract could look like:
#Writing to a Solidity contract on a DAppChain
Calling an EVM smart contract's method that mutates the state works the same as writiing data to a DAppChain. The main difference in the case of an EvmContract is that the input takes the format of an ABI encoded array.
The return value is a transaction hash. You can use the transaction hash to retrieve more information about the contract using the GetEvmTxReceipt method. This returns a EvmTxReceipt object:
#Reading from a Solidity contract on a DAppCahin
To get information from an EVM smart contract you need to call a view method using the EvmContract's staticCall. This returns the result in an ABI encoded []byte. As for other EVM methods, the function signature and input arguments are ABI encoded.
#Transaction hash
Writing to a DAppChain using a Call transactions that can modify the state returns a transaction hash. This is a unique hash of the transaction details. No two contracts should return the same hash. It can be used to retrieve details of the transaction.
#Transaction receipt
Details of each EVM call transaction are stored on the zekndchain and can be accessed using the transaction hash.
The zeknd chain QueryService has the method TxReceipt(txHash []byte) ([]byte, error) which returns the receipt in a protobuf form. go-zeknd and zeknd-js provide an API for this query.
go-zeknd:func (c *DAppChainRPCClient) GetEvmTxReceipt(txHash []byte) (vm .EvmTxReceipt, error)
zeknd-js: async getTxReceiptAsync(txHash: Uint8Array): Promise<EvmTxReceipt | null>
Details of the transaction receipt objects follow.
TransactionIndex
transaction number this block
BlockHash
Hash of the last block
BlockNumber
Block height
CumulativeGasUsed
Currently not used
GasUsed
Currently not used
ContractAddress
Address of the contract called
Logs
Events, encoded as an array of Event protobufs
LogsBloom
Not used
Status
1 = success or 0 = failier
Last updated