EVM
Last updated
Last updated
zeknd DAppChains contain an Ethereum virtual machine (EVM) and allow you to deploy and run smart contracts that compile to EVM bytecode.
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.
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 zekndm
command 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-
zeknd EvmContract
object.
In TypeScript or JavaScript, you use the zeknd-js
's EvmContract
object.
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) . The ABI can get quite complex, however, Ethereum implementations should, as we see later, give function to support parameter generation.
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.
plugin
User-created contracts.
EVM
contract runs on DAppChains EVM.
format
The nature of the smart contract's input file in the contracts directory.
plugin
User plugin, can be produced by go-
zeknd.
truffle
Solidity program, compiled using truffles compiler.
solidity
Solidity program, compiled using solc.
hex
Raw Hex, for instance, solidity program compiled using solc -o
option .
name
This name can be used to retrieve the address of the contract assigned by zeknd or the EVM.
location
Versioned 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.
The zeknd command line tool has three commands for interacting with the chain's EVM:
deploy
This will deploy a smart contract in EVM bytecode onto the chain's EVM.
call
This will call a method that can mutate the state on an already deployed EVM smart contract.
static-call
This will call a read-only method on an already deployed EVM smart contract.
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.
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
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.
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.
The address fields -a and -k are optional.
Example
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.
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.
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 HelloWorld
as a struct.
Implement the Meta()
function, returning the contracts name and version number.
The variable Contract
needs to be defined. The function contract .MakePluginContract
converts 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.
So we've just added a simple function that just returns a fixed message. A couple of key points:
Either a contract.StaticContext
or contract.Context
should 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 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 .proto
files, 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:
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 HelloRequest
and HelloResponse
have to match the input and output parameters of the contract's method we are calling.
Call the Hello
method. We use StaticCall
as the Hello
method has a static context.
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:
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
:
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.
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.
go-
zeknd and zeknd-js
provide help for communicating with a running DAppChain using an RPC client.
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
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
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
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.
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
-i is the input string. For a solidity contract, this will be ABI encoded as described in the .
-i is the input string. For a solidity contract, this will be ABI encoded as described in the .
The user input in the second parameter and the first return value take the form of - HelloRequest
and HelloResponse
in this example. These protobuf message structs need to be auto-generated from a language-neutral .proto
file. See below.
The input is passed straight through to the EVM and needs to be encoded as laid out in the .
We can use "github.com/ethereum/go-ethereum/accounts/abi" and this ABI string to encode our input. The key function is
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 . You can use the go-ethereum
function to encode the input using your contract's ABI which you can get from solc --abi -o. MySolidiityProgram.sol
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 . The caller field in StaticCall
is optional and using an empty zeknd.Address
is fine.
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 .