woman in blue jacket lookup at the canyon top in antelope canyon

Solidity for JavaScript Developer

What is Solidity?

Solidity is a high-level language for implementing smart contracts in Ethereum environment. Solidity is statically typed, supports inheritance, libraries and complex user-defined types among other features.

The contracts you create with Solidity is immutable once you deployed it. So most of the times you need to run many tests with your contract to ensure that it is run without bugs.

From the docs: Contracts in Solidity are similar to classes in JavaScript (I know... it's not a real class). They contain persistent data in state variables, and functions that can modify these variables.

Calling a function on a different contract (instance) will perform an EVM function call and thus switch the context such that state variables in the calling contract are inaccessible. A contract and its functions need to be called for anything to happen. There is no “cron” concept in Ethereum to call a function at a particular event automatically.

Why?

The main reason I chose this path is simple. Writing code in blockchain is like writing art. You need to really carefully craft your code because:

  • You care about your user.
  • You care about your code.

What that means is when you code in solidity and deploy it, it is immutable. You will not be able to patch it or add more features to it. The only way to update it is to create a new contract. So your code must be bug-resistant, go through a lot of tests, and fully optimize so your user can save gas costs.

Flash Tutorial

In this post, we don't deep dive into Solidity. If you want to know more about it, you can always check the full documentation on their website. We are going to get to know only the basics of it so you can get a bird view vision of what it is and how to write it.

Default

  • Functions are public by default.
  • Comment is similar to JavaScript: `// comment`.
  • Use camelCase.
  • Use semi-colon (yeah I know...).

Pragma

Version pragma - declaration of the version of the Solidity compiler this code should use.

// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0 <0.9.0;

Variables and Data Types

State variables are permanently stored in contract storage. you can also save it in memory and it will get erased between functions called. If you put it on the storage it means they're written to the Ethereum blockchain. Think of them like writing to a DB. Another store is calldata which is same like memory but only for external functions.

List of Data Types:

  • string: similar to JS, you can use either single or double quotes.
  • uint: unsigned integer. Default to uint256.
  • int: signed integer, Default to int256.
  • address: special data type refers to the address of the account (20 bytes value).
  • address payable: same as address but with additional members `transfer` and ` send`.
  • mappings: key-value pair: `mapping (uint => string) greet`, uint is the key type and string is the value type.
  • arrays: same as JS, use [], but it can be fixed or dynamic.
    • fixed array: `uint[2] numArr`
    • dynamic array: `uint[] dynamicNumArr`
  • bool: boolean value: true, false.
  • structs: complex data type (I think this one is like Go (golang) struct).

Struct example

struct Person {
string name;
uint age;
}
// create variable based on Person:
Person satoshi = Person("satoshi", 42);
Person[] people;
people.push(satoshi);

Reference data type: arrays, structs, mappings.

Typecasting

You can also do typecasting similar to JS.

uint8 a = 5;
uint b = 6;
uint8 c = a * uint8(b);
// address to address payable
payable(myAddress);

In the example above we typecast `b` from uint to uint8, so we can do the calculation safely and store it in uint8 c, otherwise it will throw an error.

Global Variables

Solidity has global variables. This is some of it:

  • msg.sender: sender of the message (current call).
  • msg.value: number of wei sent with the message.
  • block.gaslimit: current block gas limit.
  • block.number: current block number.
  • block.timestamp: current block timestamp.
  • require: abort execution and revert state changes if condition is false.
  • keccak256: compute the Keccak-256 hash of the input.

Operators

Operator in Solidity is similar to JavaScript, but with few exceptions like Solidity don't have `===`, which is Nice!

Conditional

Writing if-else in Solidity is similar to JS.

if (x == 42) {
old();
}

Loops

Solidity for loops syntax is similar to JS, but you need to use uint instead of let or var for the iteration number.

uint[] memory evenNumber = new uint[](5);
for (uint i = 0; i < 10; i++) {
if (i % 2 == 0) {
evenNumber.push(i);
}
}
// evenNumber will be [0, 2, 4, 6, 8]

Functions

The function's syntax is similar to TypeScript (you need to declare parameters type and its return value type) but with different syntax.

function multiply(uint x, uint y) pure view returns(uint) {
return x * y;
}

Solidity functions are public by default. Which means everyone or every contract can access it. So it is best practice to specify it as private.

Function parameters by convention start with _, like `uint _data`.

List of functions visibility specifiers and modifiers:

  • public: default attributes.
  • private: to make a function private, by convention add prefix _ to the function name. Private functions can't be inherited.
  • internal: same as private except it can be inherited.
  • external: same as public except it can ONLY be called outside the contract. Not by other functions in the contract.
  • view: function doesn't alter any values or write anything.
  • pure: function not accessing any data in the app.
  • returns (): function has a return value.

Hash Function

Solidity built-in hash function: keccak256, a version of SHA3. Hash function basically maps an input into a random 256-bit hexadecimal number. Keccak256 expects a single parameter of type bytes.

keccak256(abi.encodePacked("abe"));

The abi.encodePacked is also considered a global variable.

Returns Multiple Values

Solidity allows you to do multiple return values. Not like JavaScript where you must return an array or object to do that.

Example:

function multipleGreet() returns(string memory en, id, sv) {
return ("hi", "hai", "hej");
}
function getMultipleGreet() {
string memory english;
string memory indonesian;
string memory swedish;
// get those multiple return value
(english, indonesian, swedish) = multipleGreet();
// or only get the indonesian greet
(, indonesian, ) = multipleGreet();
}

So it is like using array or object destructuring but with different syntax.


Events


Events are way for your contract to communicate that something happens on the blockchain to your app front-end, which can be 'listening' for certain events and taking action when they happen.

event IntegersAdded(uint x, uint y, uint result);
function greet() public {
// lot of codes...
emit IntegersAdded(8, 9, 17);
}

Require

Require is a function that will throw an error and stop executing when some conditions is not true.

function isOld(uint _x) public {
require(_x == 42);
// if x is 42 the code below get executed
}
// So require is like having if and throw
if (x !== 42) throw new Error
// Or JS developer usually use return
if (x !== 42) return;

Inheritance

Contract can also be inherited just like class. You can access almost everything from Parent contract except specify as private. The way contracts inherit is by using is keyword instead of extends like in JS class.

contract Doge {}
contract BabyDoge is Doge {}

The difference is that contracts can be inherited from multiple contracts. Not just one. Now that's somehow cool or complicated?

Import

Import file syntax is similar to JS but you don't need to name it (please correct me if I'm wrong).

import "./dogecoin.sol"
contract BabyDoge is DogeCoin {}

Interface

Interface is a way to interact / talk to another contract on the blockchain that we don't own. We can do that because the data is stored openly on the blockchain. But we can only interact with functions that have visibility public or external.

Get The Interface

For example: consider this contract is available in the blockchain.

contract Greeter {
mapping(address => string) myGreet;
function setGreet(string memory _greet) public {
myGreet[msg.sender] = _greet;
}
function getGreet(address _myAddress) public view returns(string) {
return myGreet[_myAddress];
}
}

Now we want to get our special greet in our contract.

contract GreetInterface {
function getGreet(address _myAddress) public view returns(string);
}

That's it. we don't need to declare the whole function that we want to interact with. Only the top line of the function will do.

Using The Interface

Now how to use it? We can use it like this:

// initialize it and save to a variable
GreetInterface theGreet = GreetInterface("how ya doing?");
string favGreet = theGreet.getGreet(msg.sender)

Common Contract

What it means is that this contract pattern is so common that almost most contracts use it as their parent contract and inherit their first contract from it (I learned this from cryptozombies). This contract applies the concept of ownership, so only the owner can call some contract to ensure security (this is too ambiguous).

Ownable

/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @return the address of the owner.
*/
function owner() public view returns(address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns(bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}

Other Topics :

These are the topics that worth looking for that didn't mention in this blog.

  • Modifier: we only cover pure and view. You could also create custom modifier.
  • Payable.
  • Library.
  • ERC20.

Other Resource

  • Official Solidity Docs.
  • Metamask: Chrome Extension Brining Ethereum To Your Browser.
  • Truffle: Smart Contract Dev - Testing - Deployment Framework.
  • Ganache: Quickly Fire Up A Personal Ethereum Blockchain To Test
  • Web3JS: Ethereum JavaScript API Which Connects To Generic JSON RPC
  • Infura: Provides Instant & Scalable API Access To The Ethereum & IPFS’s.
  • Hardhat: Ethereum development environment for professionals.
  • EtherJS: Ethereum Wallet Implementation.
  • OpenZeppelin: Implementation Standards For ERC20 & ERC721.
  • Etherscan: Block Explorer & Analytics Platform For Ethereum.

Project boilerplate:

  • Hardhat boilerplate - Hardhat, Etherjs, React, Bootstrap.
  • My custom boilerplate - is a fork from Hardhat boilerplate but the frontend built with Vite and supports Typescript.

Go back