VeChain Modifications

A developers guide to the main modifications implemented in VeChain when compared to Ethereum.

Introduction

The purpose of this document is to explore and explain the main modifications implemented in VeChain when compared to Ethereum.

Some quick notes;

  • VeChain is a modified hard fork of Ethereum

  • EVM compatible, forked from 1.8 Constantinople

  • Target Solidity compiler is Istanbul

Two Token Design

VeChain has a two token design in comparison to Ethereum's single token design. The primary motivation behind the two token design is to provide a predictable economic model.

VeChain Token or VET is the primary token, which is as the medium of exchange for on-chain goods and services. VeThor Token or VTHO is the secondary token used to pay gas, which is used to pay transaction fees. VTHO is automatically generated by holding VET. The current VTHO generation rate is 0.000432VTHO per unit of VET.

Ethereum has a single token design, Ether (ETH), which acts as both the medium of exchange and the gas token. To perform transactions on Ethereum the user needs to hold and spend ETH. As the price of ETH accrues value it becomes more costly to perform transactions and as the blockchain becomes more popular, congestion results in higher transaction fees. This results in the cost of using the Ethereum being unpredictable and often, costly.

In comparison, when using VeChain the user is only required to hold the gas token VTHO when performing a transaction.

Fee Delegation

Fee delegation is a feature which allows users to perform transactions without having to purchase or hold either asset, VET or VTHO. This feature allows a user to delegate the transaction fees associated with using a blockchain to a willing delegator.

As a developer, when using the hardhat plugin, you can provide the delegate options in the hardhat.config.js. All subsequent transactions created from the plugin will have the transactions fees paid for by the configured delegate.

delegate: {
  url: "delegate_url",
  signer : "delegator_address"
},

You can also use the fee delegation options when using web3-providers-connex by passing the delegate arguments in the provider's constructor.

const net = new SimpleNet("http://127.0.0.1:8669")
const wallet = new SimpleWallet()
const driver = await Driver.connect(net)
const connexObj = new Framework(driver)
// connexObj is an instance of Connex
const provider = new Provider({connex: connexObj, net: new SimpleNet("http://127.0.0.1:8669"), delegate: {url: "delegate_url", signer: "delegator_address"}})

Transaction Differences

There are six main differences in terms of transactions between VeChain and Ethereum.

Transaction Fields

The structure of a VeChain transaction can be seen below and a more detailed explanation of each field here.

// transaction.go
type Transaction struct {
	body body
}

type body struct {
	ChainTag     byte			
	BlockRef     uint64
	Expiration   uint32
	Clauses      []*Clause
	GasPriceCoef uint8
	Gas          uint64
	DependsOn    *thor.Bytes32 `rlp:"nil"`
	Nonce        uint64
	Reserved     reserved
	Signature    []byte
}

Transaction Clauses

In VeChain a single transaction can have multiple tasks / clauses. This feature essentially enables a VeChain transaction to have multiple recipients or multiple contract calls or a mixture of the two. This enables batching multiple operations into one larger transaction with multiple clauses. A detailed view of a clause structure can be found here.

In order to utilize the clauses feature one has to pass a signed raw transaction to the eth_sendRawTransaction function when using web3-providers-connex. The following code is an example of that:

const tx = new Transaction(body)
// Transaction with two clauses
const clauses =  [{
        to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',
        value: 12345,
        data: '0x'
    },
    {
        to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',
        value: 67890,
        data: '0x'
    }]
    
    let body = {
        chainTag: 0xF6,
        blockRef: '0x0000000000000000',
        expiration: 32,
        clauses: clauses,
        gasPriceCoef: 128,
        gas: Transaction.intrinsicGas(clauses),
        dependsOn: null,
        nonce: 12345678
    }
    
    const signingHash = tx.signingHash()
    const priv = mnemonic.derivePrivateKey(["denial", "kitchen", "pet", "squirrel", "other", "broom", "bar", "gas", "better", "priority", "spoil", "cross"])
    tx.signature = secp256k1.sign(signingHash, priv)
    const raw = tx.encode()
    
    // Send Raw Transaction 
    const raw_txid = await provider.request({
        method: 'eth_sendRawTransaction',
        params: ["0x"+raw.toString("hex")]
    })

Note that in the previous example we are using the default seed that is hard-coded in the Thor solo node which is pre-funded. You must change this to your seed depending on the use case. Also, in order to be able to run this code you have to use thor-devkit as well as web3-providers-connex in an empty project.

Transaction Dependency

Transaction dependency is a feature of VeChain that enables the ordering of transactions at the consensus level. To implement this feature, use the DependsOn field in the transaction structure which stores the id of the transaction that the current transaction depends on. Note that the DependsOn transaction has to be included in a block in order for our transaction to go through, which is what enforces the ordering of transactions. To use this feature using web3-providers-connex you have to construct the raw transaction manually and populate the DependsOn field. In the previous example this field was set to null, but you can easily change it to the id of any transaction that is included in a block.

Transaction life-cycle Control

VeChain transactions have another characteristic, which is that a user can specify the earliest time a transaction can be processed using the blockRef field which represents the earliest block that transaction can be included in. Users are also able to set the opposite, i.e. the latest time a transaction is allowed to be processed using the expiration field. These fields should be used together since the sum of expiration and the first four bytes of blockRef depict the height of the last block the transaction is allowed to be included in.

Hash Function

Another notable difference in design between VeChain and Ethereum is the use of different hash functions. VeChain uses blake2b for hashing whereas Ethereum uses keccak-256. This means that developers should be aware that while VeChain has lots of similarities to Ethereum, anything that requires the use of a hash function like deriving addresses or transaction IDs will be different across the two chains even if the input data is identical.

Nonce

VeChain uses a hash approach to compute an unsigned transaction nonce which is determined by the transaction sender. Transaction uniqueness is achieved by defining the transaction nonce as a 64-bit unsigned integer that is determined by the transaction sender. Given a transaction, it computes two hashes, the hash of the recursive length prefix (RLP) encoded transaction data without the signature and the hash of the previously computed hash concatenated with the sender's account address. Whereas, Ethereum uses an incrementing nonce, which is determined by the number of transactions the account has sent so far.

This minor difference has a big impact. The uniqueness of a transaction is completely dependent on its content and whether it will be processed by the network is purely determined by whether the transaction id has existed on-chain, rather than by the state status of the sender's account, it's nonce, as well as all the pending transactions from that account. This greatly simplifies the developers job for interacting with the blockchain and handling transaction failures.

Getting Started: VeChain Testnet

  • Get the latest VeChain browser based wallet, VeWorld.

  • Get some testnet assets, VET and VTHO, through the faucet.

  • Use the energy station if you need to convert VET to VTHO or vice verse.

Last updated