Notes on Blockchain, Solidity, and Full Stack Web3 Development with JavaScript -part 3
- Follow me on twitter: twitter.com/RebeloAaron
All this solidity code that we write on remix, when compiled, it compiles it down to the EVM( Ethereum Virtual Machine). EVM is a standard on how to deploy smart contracts to Ethereum like blockchains. If a blockchain uses a type of EVM, you can deploy solidity code to.
Remix Storage Factory
Moving forward we will be working with 3 solidity files.
- StorageFactory.sol -> deploy and interact with other contracts.(SimpleStorage)
- SimpleStorage.sol -> a Simple Storage
- AddingStorage.sol
Importing contracts into other contracts.
We can have contracts deploy other contracts for us. And then go ahead and interact with those contracts from other contracts. Contracts interacting with other contracts is an essential part of working with solidity and working with smart contracts. The ability of contracts to be able to interact with other contracts is called composability. This is important when you need different financial products to work with each other
to import a contract you've written, its pretty much similar to using import "./filename.sol" in JavaScript. The advantage of writing contracts in different files and importing them is we can have different solidity versions for different files, provided the base solidity file has the lower compatible version. A single file of solidity can hold multiple different contracts.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./SimpleStorage.sol";
contract StorageFactory {
//data type is the contract name.
SimpleStorage[] public simpleStorageArray;
// Create a function that can Deploy SimpleStorage contract.
//basically add values in the SimpleStorage contract
function createSimpleStorageContract() public {
//public so anyone can call it.
//create a contract and save it to a variable.
SimpleStorage simpleStorage = new SimpleStorage() // new contractName
// new is a way solidity knows that its going to deploy a contract.
// value is saved in simpleStorage.
simpleStorageArray.push(simpleStorage)
}
}
// in the above example only address of the storage is saved.
Interacting with other contracts
assume we want to call a store() "from SimpleStorage.sol" from our StorageFactory.sol contract. Basically, how do we call a function of another contract, from another contract?
In order to interact with any contract you need two things.
- Address -> Address of the contract
- ABI -> Application Binary Interface, it basically tells you what methods allow you to interact with the contract. i.e. all the inputs/outputs. When you compile a contract, it prepackages your ABI.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./SimpleStorage.sol";
contract StorageFactory {
SimpleStorage[] public simpleStorageArray;
function createSimpleStorageContract() public {
SimpleStorage simpleStorage = new SimpleStorage()
simpleStorageArray.push(simpleStorage)
}
function sfStore(uint256 _simpleStoreIndex, uint _simpleStoreNumber){
// you find the given contract via address,
// save it in object , and call the function associated to it.
SimpleStorage simpleStorage=SimpleStorage(simpleStorageArray[_simpleStoreIndex])
// you basically pass the address in SimpleStorage.
simpleStorage.store(_simpleStoreNumber)
}
}
Inheritance & Overrides
Suppose you have a contract, and you want the features of a contract, you don't have to copy paste the entire contract, you can just inherit the contract.
- 1> import "./SimpleStorage.sol"
- 2>
contract ExtraStorage is SimpleStorage{ // ExtraStorage is a copy of SimpleStorage // i.e ExtraStorage a child copy of SimpleStorage. }
now what if you don't like one of the functions present in SimpleStorage, and you want to modify it in the ExtraStorage contract.
To solve this problem we use something called Overriding
There are 2 key points to keep in mind.
- 1) The method to be overridden needs a virtual keywork (parent contract)
function store(uint256 _favNumber) public virtual { favNo=_favNumber;}
- 2) we need to write override to a function in the child contract we want to override
function store(uint256 _favNumber) public override {favNo=_favNumber}
Sidenotes
Every transaction has the following fields.
- Nonce: transaction count for that account
- Gas Price: price per unit of gas
- Gas Limit: max gas that this tx can use.
- To: address that the tx is send to
- Value: amount of wei to send
- Data: what to send to the To address
To ensure a function in a contract can receive and send Ether, we set a modifier called payable,
function receive() payable {}
function send() payable {}
by using the keywork payable, we get access to the amount of Eth. send/recieved in the field msg.value
require(msg.value>1e18,'didnt send enough ETH.'); //1e18 = 1*10**18=1eth
require() method is to check the condition of the msg.value > 1e18
its basically used in reverting, i.e if the condition is not met, all further transactions don't spend gas, you can think of it as a break statement.
If you need something to happen in your transaction, and want the whole transaction to fail if it does not happen, use require()
assume we want a random number. Since each node can get a different value, hence we can never reach consensus. Chainlink is a method of getting external data in a decentralized manner. In other words all nodes can end up getting the same information.
Link Token are used to pay node operators for retrieving data for smart contracts.
Chainlink Data Feeds are the quickest way to connect your smart contract to real world market prices of assets.
Chainlink VRF is a way to get random values in a given smart contract.
Keeper-compatible is a way to do decentralized event driven computation. eg, if a trigger hits, do something