The the Hello World program was the first example program in the book The C Programming Language, by Kernighan and Ritchie (page 2). This book is still the best book for learning C. My own favorite program for introducing a computer language is the Boom program. The Boom program uses a loop to count down from ten to zero, prints Boom! and then exits.
In this project we write a Boom program in Solidity programming language to run on the Ethereum Virtual Machine. We then deploy and execute the program to various Ethereum block chains, including,
Solidity is a high level language for writing programs for the Ethereum Virtual Machine (EVM).
The code for this is found on github/the_real_blockchain.
The Ethereum blockchain is a blockchain. A blockchain is a ledger of transactions maintained in sequence as a data structure by a distributed computation. The transactions are organized into blocks, and the blocks are chained together, thus maintaining an agreed upon sequence of transactions.
The distributed computation uses an incentivized consensus protocol to both add to the blockchain, and to validate that all transactions in the blockchain satisfy some common notion of validity. A blockchain is always immutable (in a probabilistic sense, since otherwise the problem is impossible).
The interplay of these elements allow users of the blockchain to rely on the sequence and validity of the transactions while making very few trust assumptions.
An Ethereum blockchain is a sequence of transitions of a world state. The transitions result from either the execution of contracts by the EVM using the blockchain state, or transfers between Externally Owned Accounts (EOA). Any transaction can be verified as correct, because the a blockchain is immutable. The sequence of state transitions from the chain's inception up to any point can be recomputed by the distrustful observer.
The analog of a Bitcom miner is an Ethereum node. The consensus protocol was originally a Proof of Work protocol, but at The Merge it was replaced by a Proof of Stake protocol.
To handle the world state, contracts are object instances that have instance variables and methods. Contracts are given an address on creation, and messages are sent to that address, along with some gas and optionally and amount of ether, denominated in Wei.
The gas is required to pay for the computation. It will be resolved to a payment in ETH, and subtracted from the EOA of who initiated the transaction. Both contracts and EOA can receive ether. In our Boom contract, the count down we will charge a certain amount of ETH for each count. When the count reaches zero, all the accumulated ETH will be sent to the creator of the contract.
The count down method will check the amount of ETH that was sent, and reject if too little was sent, and reimburse the difference if too much was sent. An ethereum transaction is a transaction (in the sense of a database transaction — an all or nothing bundle of state changes). In the case of too little ETH, the contract will initiate a roll back, canceling all changes to the state, including the ETH transfer, although still using whatever gas was required.
In the case of too much ETH being sent, the contract will initiate a send of the excess either back to the sender. If for whatever this fails, the contract will initiate a roll back returning all the ETH to the sender. The EVM will still charge for the gas used.
These roll backs are provided in the Solidity language as,
Note that the ETH cost for max gas is locked at the start of the computation, sort of like a lawyer taking a retainer. What is unused is refunded. If the computation runs out of gas, it is reverted and the cost is covered by the retained ETH amount.
Programming on a typical machine presents us with a very uniform memory model of an indexed sequence of bytes. The reality is that a CPU has gradations of memory. There is a hierarchy of memory that spans from the CPU registers, through the caches, the RAM and (in cases of exceptional pressure on virtual memory) block device resident swap space. However, even advanced programmers need to pay very little attention to this.
Not so in Solidity, where various categories of storage have different capabilities and costs. There are the following data-locations to specify,
For a contract to receive funds, it must have a method marked payable. This is one of three mutually exclusive state-mutability specifiers,
// contracts/Boom.sol // author: bjr // last-update: 9 aug 2024 // SPDX-License-Identifier: UNLICENSED // ^ means 0.9 etc also acceptable (~ means 0.8.25 also acceptable) pragma solidity ^0.8.24; // place this script in ./contracts/ // and compile with npx hardhat compile import "hardhat/console.sol"; contract Boom { uint256 public count; address payable public owner; uint256 constant public TOP_COUNT = 10; uint256 constant public STAKE = (1 ether)/1000; event BoomEvent(address indexed boomer, address indexed owner, string message); constructor() { owner = payable(msg.sender); count = 10; } function getCount() external view returns (uint256) { return count; } function getFunds() external view returns (uint256) { return address(this).balance; } // Function to increment count by 1 function countDown() external payable returns (uint256) { uint256 stake = msg.value; require(stake>=STAKE,'Stake 0.001 ETH'); uint256 d = stake-STAKE; if (d>0) { (bool success,) = (msg.sender).call{value: d}(""); require(success, "Failed to reimburse sender Ether"); } count -= 1; if (count<=0) { uint256 t = address(this).balance; (bool success,) = owner.call{value: t}(""); require(success, "Failed to send boomed Ether"); count = 10 ; // event emit BoomEvent(address(this), owner, "Boom!"); } return 0; } }
$ npx hardhat nodeThe script to deploy and test is in the following box,
// scripts/boom-test-deploy.js // author: bjr // last-update: 13 aug 2024 const CONTRACT_CLASS = 'Boom' async function main () { const myAccount = (await ethers.getSigners())[0].address; const anAccount = (await ethers.getSigners())[1].address; const ContractClass = await ethers.getContractFactory(CONTRACT_CLASS); const contractInstance = await ContractClass.deploy(); await contractInstance.waitForDeployment(); const contractAddress = await contractInstance.getAddress(); console.log(`Deployed ${CONTRACT_CLASS} to ${contractAddress}`); const boom = contractInstance ; let count = await boom.getCount(); let accountFunds = await ethers.provider.getBalance(myAccount); let contractFunds = await boom.getFunds(); for (let i=0;i<10;i++) { await boom.countDown({value:ethers.parseEther("0.001")}); count = await boom.getCount(); accountFunds = (await ethers.provider.getBalance(myAccount)); contractFunds = await boom.getFunds(); console.log(`count: ${count}`); console.log(`my funds: ${ethers.formatEther(accountFunds)}`); console.log(`contract funds: ${ethers.formatEther(contractFunds)}`); } accountFunds = (await ethers.provider.getBalance(anAccount)); console.log(`other account funds: ${ethers.formatEther(accountFunds)}`); } main().then( () => process.exit(0) ).catch( error => { console.error(error); process.exit(1);} );
Here is the run of the script.
% npx hardhat run --network localhost scripts/boom-test-deploy.js Deployed Boom to 0xB1c05b498Cb58568B2470369FEB98B00702063dA count: 9 my funds: 9999.971367501201049543 contract funds: 0.001 count: 8 my funds: 9999.970340499200833527 contract funds: 0.002 count: 7 my funds: 9999.969313497200617511 contract funds: 0.003 count: 6 my funds: 9999.968286495200401495 contract funds: 0.004 count: 5 my funds: 9999.967259493200185479 contract funds: 0.005 count: 4 my funds: 9999.966232491199969463 contract funds: 0.006 count: 3 my funds: 9999.965205489199753447 contract funds: 0.007 count: 2 my funds: 9999.964178487199537431 contract funds: 0.008 count: 1 my funds: 9999.963151485199321415 contract funds: 0.009 count: 10 my funds: 9999.972112407199008791 contract funds: 0.0 other account funds: 10000.0
author: burton rosenberg
created: 10 aug 2024
update: 14 aug 2024