Solidity in Depth

Solidity

Solidity is a high-level smart contract language similar to JavaScript which allows you to develop contracts and compile to EVM bytecode. It is currently the flagship and the most popular language of Ledgerium. Smart Contracts are compiled using the solidity compiler and turn into binary format called bytecode which is deployed to the blockchain. This bytecode gets executed by the Ethereum Virtual Machine (EVM).

Writing a contract

A "Hello World" program in Solidity is of even less use than in other languages, but still:

pragma solidity ^0.5.1;
contract HelloWorld {
function helloWorld() external pure returns (string memory) {
return "Hello, World!";
}
}

This contract will send greet message after its construction.

Compiling a contract

Compilation of solidity contracts can be accomplished via a number of mechanisms.

More information on solc and compiling Solidity contract code can be found here.

Compiling a simple contract

Create and deploy a contract

Before you begin this section, make sure you have both an unlocked account as well as some funds.

You will now create a contract on the blockchain by sending a transaction to the empty address with the EVM code from the previous section as data.

This can be accomplished much easier using the various online Solidity realtime compiler or the Remix IDE program.

var primaryAddress = eth.accounts[0]
var abi = [{ constant: false, inputs: { name: 'a', type: 'uint256' } }]
var MyContract = eth.contract(abi)
var contract = MyContract.new(arg1, arg2, ..., {from: primaryAddress, data: EVMByteCodeFromPreviousSection})

All binary data is serialised in hexadecimal form. Hex strings always have a hex prefix 0x.

Note that arg1, arg2, ... are the arguments for the contract: constructor, in case it accepts any. If the contract does not require any constructor arguments then these arguments can be omitted.

It is worth pointing out that this step requires you to pay for execution. Your balance on the account (that you put as sender in the from field) will be reduced according to the gas rules of the EVM once your transaction makes it into a block. After some time, your transaction should appear included in a block confirming that the state it brought about is a consensus. Your contract now lives on the blockchain.

The asynchronous way of doing the same looks like this:

MyContract.new([arg1, arg2, ...,]{from: primaryAccount, data: EVMCode}, function(err, contract) {
if (!err && contract.address)
console.log(contract.address);
});

Interacting with a contract

Interaction with a contract is typically done using an abstraction layer such as the eth.contract() function which returns a javascript object with all of the contract functions available as callable functions in javascript.

The standard way to describe the available functions of a contract is the ABI definition. This object is an array which describes the call signature and returns values for each available contract function.

var invoiceCon = eth.contract(contract.info.abiDefinition);
var invoiceContract = InvoiceCon.at(address);

Now all the function calls specified in the ABI are made available on the contract instance. You can just call those methods on the contract instance in one of two ways.

invoiceContract.multiply.sendTransaction(3, {from: address})
"0x12345"
invoiceContract.multiply.call(3)
21

When called using sendTransaction the function call is executed via sending a transaction. This will cost XLG to send and the call will be recorded forever on the blockchain. The return value of calls made in this manner is the hash of the transaction.

When called using call the function is executed locally in the EVM and the return value of the function is returned with the function. Calls made in this manner are not recorded on the blockchain and thus, cannot modify the internal state of the contract. This manner of call is referred to as a constant function call. Calls made in this manner do not cost any XLG.

You should use call if you are interested only in the return value and use sendTransaction if you only care about side effects on the state of the contract.

In the example above, there are no side effects, therefore sendTransaction only burns gas and increases the entropy of the universe.

Contract metadata

In the previous sections, we explained how you create a contract on the blockchain. Now we will deal with the rest of the compiler output, the contract metadata or contract info.

// get the contract info for contract address to do manual verification
var info = admin.getContractInfo(address) // lookup, fetch, decode
var source = info.source;
var abiDef = info.abiDefinition

How to use smart contracts

source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"
// compile with solc
contract = eth.compile.solidity(source).test
// create contract object
var MyContract = eth.contract(contract.info.abiDefinition)
// extracts info from contract, save the json serialisation in the given file,
contenthash = admin.saveInfo(contract.info, "~/dapps/shared/contracts/test/info.json")
// send off the contract to the blockchain
MyContract.new({from: primaryAccount, data: contract.code}, function(error, contract){
if(!error && contract.address) {
// calculates the content hash and registers it with the code hash in `HashReg`
// it uses address to send the transaction.
// returns the content hash that we use to register a url
admin.register(primaryAccount, contract.address, contenthash)
// here you deploy ~/dapps/shared/contracts/test/info.json to a url
admin.registerUrl(primaryAccount, hash, url)
}
});

Testing contracts and transactions

Often you need to resort to a low-level strategy of testing and debugging contracts and transactions. This section introduces some debug tools and practices you can use. In order to test contracts and transactions without real-world consequences, you best test it on a private blockchain. This can be achieved with configuring an alternative network id (select a unique integer) and/or disable peers.

It is recommended a practice that for testing you use an alternative data directory and ports so that you never even accidentally clash with your live running node (assuming that runs using the defaults. Starting your geth within VM debug mode with profiling and highest logging verbosity level is recommended:

geth --datadir ~/dapps/testing/00/ --port 30310 --rpcport 8110 --networkid 4567890 --nodiscover --maxpeers 0 --vmdebug --verbosity 6 --pprof --pprofport 6110 console 2~/dapp/testint/00/00.log

Before you can submit any transactions, you need set up your private test chain. See test-networks.

// create account. will prompt for password
personal.newAccount();
// name your primary account, will often use it
primary = eth.accounts[0];
// check your balance (denominated in XLG)
balance = web3.fromWei(eth.getBalance(primary), "XLG");
// assume an existing unlocked primary account
primary = eth.accounts[0];
// mine 10 blocks to generate XLG
// starting miner
miner.start(4);
// sleep for 10 blocks (this can take quite some time).
admin.sleepBlocks(10);
// then stop mining (just not to burn heat in vain)
miner.stop();
balance = web3.fromWei(eth.getBalance(primary), "XLG");

After you create transactions, you can force process them with the following lines:

miner.start(1);
admin.sleepBlocks(1);
miner.stop();

You can check your pending transactions with:

// shows transaction pool
txpool.status
// number of pending txs
eth.getBlockTransactionCount("pending");
// print all pending txs
eth.getBlock("pending", true).transactions

If you submitted contract creation transaction, you can check if the desired code actually got inserted in the current blockchain:

txhash = eth.sendTansaction({from:primary, data: code})
//... mining
contractaddress = eth.getTransactionReceipt(txhash);
eth.getCode(contractaddress)