1. Units
1.1. Common units
wei
is the smallest subdenomination of Ether (1wei
== 1)gwei
is the commonly used subdenomination to describe gas price (1gwei
== 1e9)ether
unit (1ether
== 1e18)
Note:
In Solidity, we will use integers for calculations and the language does not support thefloat
type. The float representation issue causes rounding errors (rounding
) that create logical holes for attack.
1.2. Time units
- 1 == 1
seconds
- 1
minutes
== 60seconds
- 1
hours
== 60minutes
- 1
days
== 24hours
- 1
weeks
== 7days
Example
function f(uint start, uint daysAfter) public {
if (block.timestamp >= start + daysAfter * 1 days) {
// ...
}
}
2. Global Variables
2.1. Block and Transaction Properties
blockhash(uint blockNumber) returns (bytes32)
: hash of the given block whenblocknumber
is one of the 256 most recent blocks; otherwise returns zeroblock.basefee (uint)
: current block’s base feeblock.chainid (uint)
: current chain idblock.coinbase (address payable)
: current block miner’s addressblock.gaslimit (uint)
: current block gaslimitblock.number (uint)
: current block numberblock.timestamp (uint)
: current block timestamp as seconds since unix epochgasleft() returns (uint256)
: remaining gasmsg.data (bytes calldata)
: complete calldatamsg.sender (address)
: sender of the message (current call)msg.sig (bytes4)
: first four bytes of the calldata (i.e. function identifier)msg.value (uint)
: number of wei sent with the messagetx.gasprice (uint)
: gas price of the transactiontx.origin (address)
: sender of the transaction (full call chain)
2.2. Error handling
assert(bool condition)
: causes a Panic error and thus state change reversion if the condition is not met - to be used for internal errors.require(bool condition)
: reverts if the condition is not met - to be used for errors in inputs or external components.require(bool condition, string memory message)
: reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.revert()
: abort execution and revert state changesrevert(string memory reason)
: abort execution and revert state changes, providing an explanatory string
2.3. Members of Address Types
<address>.balance (uint256)
: balance of the Address in Wei<address>.code (bytes memory)
: code at the Address (can be empty)<address>.codehash (bytes32)
: the codehash of the Address<address payable>.transfer(uint256 amount)
: send given amount of Wei to Address, reverts on failure, forwards 2300 gas stipend, not adjustable<address payable>.send(uint256 amount) returns (bool)
: send given amount of Wei to Address, returns false on failure, forwards 2300 gas stipend, not adjustable<address>.call(bytes memory) returns (bool, bytes memory)
: issue low-level CALL with the given payload, returns success condition and return data, forwards all available gas, adjustable<address>.delegatecall(bytes memory) returns (bool, bytes memory)
: issue low-level DELEGATECALL with the given payload, returns success condition and return data, forwards all available gas, adjustable<address>.staticcall(bytes memory) returns (bool, bytes memory)
: issue low-level STATICCALL with the given payload, returns success condition and return data, forwards all available gas, adjustable
2.4. Contract-related keywords
this
: The current contract, explicitly convertible to Addresssuper
: A contract one level higher in the inheritance hierarchyselfdestruct(address payable recipient)
: Destroy the current contract, sending its funds to the given Address and end execution. Note that selfdestruct has some peculiarities inherited from the EVM:- the receiving contract’s receive function is not executed.
- the contract is only really destroyed at the end of the transaction and revert s might “undo” the destruction.
3. Expressions and Control Structures
3.1. Supported keywords
There is: if
, else
, while
, do
, for
, break
, continue
, return
, try
/catch
with the usual semantics known from C or JavaScript.
3.2. Function Calls
We can call function
of 1 contract
from another contract
. We have an example below with 2 contracts Caller
and Callee
.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Callee {
uint public x;
uint public value;
function setX(uint _x) public returns (uint) {
x = _x;
return x;
}
function setXandSendEther(uint _x) public payable returns (uint, uint) {
x = _x;
value = msg.value;
return (x, value);
}
}
contract Caller {
function setX(Callee _callee, uint _x) public {
uint x = _callee.setX(_x);
}
function setXFromAddress(address _addr, uint _x) public {
Callee callee = Callee(_addr);
callee.setX(_x);
}
function setXandSendEther(Callee _callee, uint _x) public payable {
(uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);
}
}
3.3. Create new contract with keyword new
We can use keyword new
to create a new contract. AdvancedStorage.sol
example will explain this in more details.
4. Advanced Storage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AdvancedStorage {
// Declare the address of a vault manager
address public vaultManager;
// Declare an error type for unauthorized access
error OwnableUnauthorizedAccount(address account);
// Constructor is a function that runs when the contract is initialized
constructor() {
// Assign the address of the deployer to the vault manager variable
vaultManager = msg.sender;
}
// Declare the InvestmentVault Struct data type
struct InvestmentVault {
uint256 investmentDuration; // Thời gian đầu tư
int256 returnOnInvestment; // % lãi suất trả về
bool initialized; // Đã khởi tạo
address identityCard; // Địa chỉ thẻ thông tin
}
// Declare a variable with the InvestmentVault type
InvestmentVault private investmentVault;
// This function initializes the investment vault
function setInitialInvestmentVault(uint256 daysAfter, int256 _returnOnInvestment, address _vaultOwner) public {
// We check if the initiator is the vaultManager
if (msg.sender != vaultManager) {
// This reverts all actions and reverts the transaction
revert OwnableUnauthorizedAccount(msg.sender);
}
// Declare the investment duration
uint256 _investmentDuration = block.timestamp + daysAfter * 1 days;
// Create a new identity card for the customer
CustomerIdentityCard customerIdentityCard = new CustomerIdentityCard(_vaultOwner);
// Assign the address of the vault owner/customer to the mapping with the vault information
investmentVault = InvestmentVault({investmentDuration: _investmentDuration, returnOnInvestment: _returnOnInvestment, initialized: true, identityCard: address(customerIdentityCard)});
}
// Function to change the return on investment
function editReturnOnInvestment(int256 _newReturnOnInvestment) public {
// require keyword works similarly to if and revert above
require (msg.sender == vaultManager, "Unauthorized Manager");
// Change the value of the interest rate
investmentVault.returnOnInvestment = _newReturnOnInvestment;
}
// Function to return investmentVault information
function retrieveInvestmentVault() public view returns (InvestmentVault memory _investmentVault) {
return investmentVault;
}
// Function to return the address of the IdentityCard
function retrieveCustomerInformation() public view returns (address) {
return CustomerIdentityCard(investmentVault.identityCard).customer();
}
}
// Contract that stores the address of the vault owner
contract CustomerIdentityCard {
// declares a variable to store the address of the customer
address public customer;
// initialize the contract and assign the address of the customer
constructor(address _customer) {
customer = _customer;
}
}
Objectives
By the end of this lesson, you'll understand: