Table of contents
Overview
Welcome. I'm here to share my learnings.
Today we are going to develop, deploy a smart contract by understanding the code through breakdowns. It's totally okay if you are beginner. You can start your journey right here ๐.
I'm a soothsayer. I leave my sayings at the end of blog and they are much important
Building Contract EherBank
You proposed a web3 solution for existing banking system and we are working on it.
Let's Develop a Bank contract EtherBank
which allows users to deposit and withdraw ether.
As always, first think about to the requirements and functionalities to be implemented and outline them
Outline
Our store must be capable of tracking balances
โโโโ To store the amount of ether with a particular person (wallet) we can use
- mapping(address => uint256)
Functions
โโโโ deposit
| - retrieve the account balance of caller address
| - add the funds to the account balance
| - update the account balance with new balance
โโโโ withdraw
| - check if account has sufficient funds to withdraw
| - Initiate the transaction
| - update the account balance to 0 on successful transaction.
Code
1 pragma solidity 0.8.11;
2
3 import "@openzeppelin/contracts/utils/Address.sol";
4 import "hardhat/console.sol";
5
6 contract EtherBank {
7 using Address for address payable;
8
9 mapping(address => uint) public balances;
10
11 function deposit() external payable {
12 balances[msg.sender] += msg.value;
13 }
14
15 function withdraw() external {
16 uint256 balance = balances[msg.sender];
17 require(balance > 0, "Withdrawl amount exceeds available balance.");
18
19 console.log("");
20 console.log("EtherBank balance: ", address(this).balance);
21 console.log("Attacker balance: ", balance);
22 console.log("");
23
24 payable(msg.sender).sendValue(balance);
25 balances[msg.sender] = 0;
26 }
27
28 function getBalance() external view returns (uint) {
29 return address(this).balance;
30 }
31 }
Breakdown
Compiler version
pragma solidity 0.8.11;
Import statements
import "@openzeppelin/contracts/utils/Address.sol";
import "hardhat/console.sol";
OpenZeppelin Contracts helps you minimize risk by using battle-tested libraries of smart contracts for Ethereum and other blockchains.
Libraries A library in Solidity is a different type of smart contract that contains reusable code
A library Address.sol
is used to use secure standard functions related to the address
type. more
Hardhat is an Ethereum development environment. We can get solidity stack traces, logging features and much more.
We are able to use console.log
by importing hardhat's console.sol
library, so we can log to console output.
Contract and state variables
A contract named EtherBank
is defined with a state variable that maps a user address to their balances.
contract EtherBank {
...
mapping(address => uint) public balances;
...
}
Mapping acts like a hash table or dictionary in any other language. These are used to store the data in the form of key-value pairs
Account | Balance |
1 | 5 eth |
2 | 8 eth |
Payable addresses and functions
using Address for address payable;
It specifies payable addresses can now use all the functions provided by Address.sol library.
address vs address payable
You can use .transfer(..)
and .send(..)
on address payable, but not on address.
You can use a low-level .call(..)
on both address
and address payable
, even if you attach value.
Prerequisites (functionalities)
Global Variables
The
msg
global variables in particular are special global variables that contain properties which allow access to the blockchain.msg.sender
is always the address where the current (external) function call came frommsg.value
contains the amount of wei (ether / 1e18) sent in the transaction
Payable functions
In solidity a function that can send and receive Ether is given with
payable
modifier
External functions
External functions are part of the contract interface. They can be called from other contracts and via transactions. An external function f() cannot be called internally
1. Deposit
function deposit() external payable {
balances[msg.sender] += msg.value;
}
The code is self explanatory.
It need to be a payable
function as it receiving ether.msg.sender
is the one who invokes deposit function.msg.value
is the amount provided to deposit by the user.
And we added new funds to the old and maintaining the track of it in balances
2. Withdraw
function withdraw() external {
uint256 balance = balances[msg.sender];
require(balance > 0, "Withdraw amount exceeds available balance.")
payable(msg.sender).sendValue(balance);
balances[msg.sender] = 0; }
Require statement
To withdraw amount one should have balance > 0.
The check can be implemented using require()
statements in solidity.
If condition met, it permits for next instruction else it reverts all the changes to initial stage.
Type conversion
msg.sender
is of type address. To use transfer/send we should convert it into address payable
. It is done by payable(address)
.
.sendValue()
comes from the library Address.sol that we imported from OpenZeppelin utils
Transfer eth to owner
The sendValue()
function when executed invokes the external receive()
/fallback()
function that was deployed in users contract. (read further)
We transfered the amount to account user. So we can then set his balance to 0
in our transaction state.
3. GetBalance
function getBalance() external view returns (uint) {
return address(this).balance;
}
This is a utility function to get the total amount holed by the contract.
this
refers to the contract address and .balance
returns the amount of eth in the address given.
Deploy
Let's deploy it, so that any user can interact with our EthBank
contract.
Deploying contract states that the contract was mined by miners and verified by validators and the contract settles as one of the transaction in a block of transactions.
I use remix ide
Upon deployment it provides us contract address 0xd9145CCE52D386f254917e481eB44e9943F39138
. I've fed eth to my contract with 3 users
and in total 12 ether
Building User Contract
User is capable of depositing and withdrawing eth.
Prerequisites
Fallback function
Every Ethereum smart contract byte code contains the so-called default fallback function which has the following default implementation.
contract EveryContract {
function () public {}
}
This default fallback function can contain arbitrary code if the developer overrides the default implementation. If it is overridden as payable, the smart contract can accept ether. The function is executed whenever ether is transferred to the contract
Transferring ether
Aside from calling payable methods, Solidity supports three ways of transferring ether between wallets and smart contracts.
These supported methods of transferring ether are send(), transfer() and call.value().
The methods differ by how much gas they pass to the transfer for executing other methods (in case the recipient is a smart contract), and by how they handle exceptions.
address.send() | address.transfer() | address.call.value() | |
Adjustable gas | no | no | yes |
Gas limit | 2300 | 2300 | all/settable |
Behaviout on error | return false | Throw exception | return false |
In short, whenever transferring methods are executed the external fallback functions are executed behind. fallback functions can be overridden.
Code
1 // SPDX-License-Identifier: MIT
2 pragma solidity 0.8.17;
3 import "hardhat/console.sol";
4 import "./EtherBank.sol";
5
6 contract User {
7 EtherBank public immutable etherBank;
8
9 constructor(address etherBankAddress) {
10 etherBank = EtherBank(etherBankAddress);
11 }
12
13 function transact() external payable {
14 etherBank.deposit{value: msg.value}();
15 etherBank.withdraw();
16 }
17
18 fallback() external payable {}
19
20 receive() external payable {
21 console.log("received payment");
27 }
28
29 function getBalance() external view returns (uint) {
30 return address(this).balance;
31 }
32 }
Breakdown:
Using EtherBank in another contract
import "./EtherBank.sol";
As we have developed the code locally we can import it like any other solidity file. (we can also use interfaces to achieve this)
User Contract
contract User {
EtherBank public immutable etherBank;
constructor(address etherBankAddress) {
etherBank = EtherBank(etherBankAddress);
}
...
}
Contract user is defined with etherBank
state variable (similar to class variable) which stores address of EtherBank deployed contract and acts as an instance so the functionalities can be used using it (similar to objects in any other oop language).
Constructor is a special function that was called only once (i.e., called during deployment) and sets the etherBank
as an instance of EtherBank contract. We pass the contract address to the constructor as parameter. In my case it is 0xd9145CCE52D386f254917e481eB44e9943F39138
Now we can use etherBank
to call any of the EtherBank contract functions.
Transaction logic
It shows how we deposit and withdraw eth.
function transact() external payable {
etherBank.deposit{value: msg.value}();
etherBank.withdraw();
}
etherBank
can call deposit
and withdraw
functions.
deposit
must be provided with some value we sent it through .deposit{value: msg.value}();
it updates our balance with given amount.
withdraw
when called executes the instructions and when payable(msg.sender).sendValue(balance);
triggers it invokes the fallback/receive
function.
fallback/receive functions
.sendValue(balance)
invokes receive function
fallback() external payable {}
receive() external payable {
console.log("received eth");
}
we can confirm withdrawal by console output "received eth"
Deploy:
Observe the image. When I choose transact with 2 ether as deposit value it added to the EtherBank and then the withdrawal function executed which invokes receive()
(confirm it by console output "received eth")
Congratulations ๐โจ. You have developed a Ethereum Transaction organization. You became most popular with 10million eth balance.
Always better together๐ฅน๐ twitter
"Believe in yourself. You are enough" - caroline ghosn
Saying ๐
๐ฒ Flash news: A famous Ethereum Transaction organization lost 3.6m eth