This article was prepared by our CTO Valeriy Dubrava and edited by Vladimir.
Smart contracts introduction
Smart contract in Ethereum is a program running in the environment of the EVM (Ethereum virtual machine). Each contract has a unique address, the balance of funds (ETHs), state (permanent memory) and a set of functions available for the call (or no functions).
Code, balance and contract status is stored in the blockchain.
The user can call the function of the contract by the transaction. Or make a call without transaction, in case if the function does not changes the state of the contract or someone else’s balance.
Contract’s method can have any implementation, and is limited only by gas, required to execute each instruction. The contract may can change own balance, can refer to other contracts, may accept funds from other network members, etc.
Only the user can initiate the transaction which starts contract execution. So, it’s user’s responsibility to worry about contract’s execution.
The first obvious solution is a web-service which initiates network transaction (call the contract) at the right time.
However, this solution seems wrong because of centralized structure. In case of web-server unavailability (due to hacks, non-payments, connections issues, etc.) contracts are not executed. it is unacceptable.
That’s why our team has invented the Joule system, which is implemented on the basis of smart contracts and has decentralized architecture.
Joule is a smart contract, which allows you to register a call to any contract for a particular time. Joule provides the ability to call Ethereum contracts for any user.
For user’s motivation Joule pays the bonus for every call. Payment consists of 2 parts: reward for the call & gas compensation spent on the call. Reward is paid by WISH (in the current version is not implemented).
Joule can be called simultaneously by several users. In this case, reward will only get the first caller.
At any time, the user (using the method of smart contract or the web interface) can find out what contracts are in available for the call and when the call should be made. The reward and the volume of gas required for execution of the contract is also available from the Joule’s contract.
Joule ensures that the contract will not called before specified time.
During the development of complex smart contracts (like Joule) it’s necessary to solve many technical problems. One of main problems — storage of multiple contract’s addresses and time for their call (and other parameters). Getting the first contract in the queue should be done in constant time (and shall not depend of the number of registered contracts).
There is a need of algorithms with minimal gas consumption. For example, it is impossible to apply solutions that have greater complexity than O(ln(n)). In addition to the cost of gas there is still a limit to the amount of gas. For example, if the next element search algorithm is used with complexity O(n/2), and iterate over each element will require 50000 of gas, after adding 185 element of the contract will not be able to perform a search (at the current limit of 4 600 000 gas transaction).
To store the state (except balance), the contract has access to a key-value store. The key in the repository can be any 256-bit number. In fact, before saving, EVM calculates the checksum from the key using the keccak (sha-3) algorithm. The value is also a 256-bit number (the compiler allows you to put a larger value into the state by adding an offset parameter to the key). Thus, the contract can store 2 ^ 256 unique values of 256 bits each, which is many orders of magnitude greater than the needs.
The key-value store can be considered as a random-access memory area with a dimension of 2 ^ 256. In this case, the key is a reference to the memory location. The data structure necessary to store call registration consists of the following fields: contract address, time for the call, gas amount needed for call and the cost of the gas. All this data can be accommodated in 256 bits (32 bytes):
Data storage restrictions:
Address — 20 bytes (address in the network ethereum).
Timestamp — 4 bytes, the standard value of Unix Time, valid from 1970–01–01 00:00:00 to 2106–02–07T09: 28: 15 (for unsigned values). In simple words — the maximum of registration — 2106 year. In the future, you can increase this value to 6812 by using an extra byte from the field to the gas value.
Gas is 4 bytes. Joule (excluding contract execution) uses 50,000 gas on average.
Gas Price — 4 bytes. Estimated at registration cost of gas. This value is stored in gwei (10 ^ 9 wei), so the minimum size is 0.000000001 ether, and the maximum is 4.294967296 ether.
A smart contract supports two storage structures: an array with index access and a mapping with access by key. But in fact the array is stored as mapping, where the key is an index, and does not give any advantages in terms of access speed. In addition, inserting elements into the middle of the array requires a shift in all elements and this may require an unacceptable amount of gas. Therefore, a solution based on linked lists is better suited for this task.
To store the list in the relationship, the following technology is used: the value of the record (32 bytes) is treated as a key (or a reference, if analogous to C ++) to the next value.
The image on the left shows the registration records R and the function for obtaining the key K (R) = R as a string of values. On the right is a key-value store in the form of a table. It can be seen that the value of R0 is not used, due to the fact that it is only the key to the next value. In the contract, there is no way to read the key from the relationship. To do this, the table has a zero record (Head), which always indicates the value of the beginning of the chain.
The order of the records in the chain is the reverse of the chronological order for the instantaneous receipt of the next one to the call of the contract. Time-identical registrations are ordered one by one in the order of the addition.
To maintain a list of registrations in the sorted order, entries must be inserted according to the date. To speed up the search for the insertion point of a new value, an index was developed on the basis of the tree.
Searching for items on the tree gives a constant value of complexity that does not depend on the number of elements in the tree O (1). There is a worse scenario, which in the current implementation is 168 busts. The tree balance can be selected by changing the number of levels and multipliers.
The last level, similar to the previous one, contains the values of the maximum and minimum timestamps.
The contract code in the blockchain can’t be changed. So in case of making changes, it is necessary to post a new version of the contract. At the same time, it is necessary to transfer data (state) from the old version. It’s not simple task, since the state of the contact is not accessible from the outside. And if the contract does not initially have functions for accessing the state, then the transfer is impossible. Similar to the state, funds from the contract can not be transferred without the functions prepared in advance for this purpose.
To address these problems, Joule’s contract is ideologically divided into several separate contracts:
The Joule ecosystem consists of the following contracts:
Joule — core contract that implements the key logic of registering and calling contracts
Proxy — contract with the function of the front controller, to access the functions of the Joule. Proxy accepts registrations, calls, and calls from registered contracts take place from its address. Any interaction with Joule from the user side (regardless of the user role) is through Proxy.
Vault — rewards storage, to compensate for calls.
Register — a contract that stores a chain of records with registrations.
Index — contract for quick insertion of new contracts
State — a contract that implements basic storage functions. Used by contracts Index and Register.
This architecture allows to change the Joule logic without losing data. We do not need to transfer or copy data. No need to make mechanisms for transferring funds (which may be vulnerable to hacker attacks).
Using the Proxy contract makes it easy to integrate with the Joule system for external developers. They do not need to change the address of the contract, if changes occur in Joule. It will be easier to check in registered contracts that the call comes from Joule.
Any user who has an address and enough funds to make a call can get reward from Joule.
To do this, it is sufficient at the right time to make a transaction with a call to Joule (method invoke or invokeOnce).
The moment for the call can be easily determined using the getTop method. This method returns the current state of the queue in Joule: the time of the next call, the minimum gas, the amount of compensation for each contract, and other values.
The amount of gas per transaction that the miner specifies must be not less than the value specified in the invokeGas field for the next contract. Gas can be set with a reserve — unused gas will return back (funds for it will not be written off). There are times when there are several contracts ready for the call. In this case, it is better to put gas equal to the amount of gas of all contracts. Then (in the case of the invoke method) several contracts will be called by one transaction, and the miner will receive a reward for each.
When registering a contract, the developer sets the estimated value of the gas cost (but not less than the value set at a minimum). In order to call, the miner should select the amount of gas so that his transaction passes faster than others (if any), but it was not more expensive than the amount of compensation. The value of the gas and the amount of compensation can be get by the getTop method.
In the case of a successful call, Joule publishes the Invoked event, where you can find all your successful calls and find out the exact amount of the reward.
Registration of the contracts
If you need to call the contract at the appointed time — then the best solution to this is Joule.
To register a contract call for a specific time, you must call the register method with the following parameters:
Address — the address of the contract to be called.
Timestamp — time in the unix timestamp format in which the call must be made. It is important to understand that Joule guarantees that the call will not be made before this moment.
GasLimit is the maximum value of the gas that will be given to the call. It is better to specify the value with a margin, so that there is no situation that the call of the contract will result in an error due to a shortage of gas.
GasPrice — the estimated cost of gas for the call of the contract.
In the transaction to Joule, together with the register call, it is necessary to transfer Ethers as a reward for the call. The exact amount can be determined using the getPrice method.
The contract must implement the check method. If premature calls may violate the logic of the contract or create a vulnerability, you should add a check that the call was from Joule. If the contract is already on the network, and there is no possibility to add a check method to it, then you can use the contract-proxy that implements the necessary method and calls the target contract. Then, when registering with Joule, you must specify the address of the contract-proxy.
In case of successful registration, Joule publishes the Registered event, with which you can see all your registrations.
Both types of interaction can be realized directly, working with the contract through the client of the Ethereum network, for example Parity or Geth (Mist).
Also for convenience of work Joule has a web interface that allows you to make a call or register your contract using only a browser with an extension installed in it, such as Metamask or without any extension, for example, via MEW.